diff options
161 files changed, 5047 insertions, 2005 deletions
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 16af0719d9dd..30f4ad4c5c69 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -69,6 +69,7 @@ static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip"; static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip"; static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; +static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip"; static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip"; static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"; @@ -358,10 +359,10 @@ void BootAnimation::findBootAnimationFile() { const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1; static const char* bootFiles[] = - {playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE, + {APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; static const char* shutdownFiles[] = - {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; + {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE, ""}; for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) { if (access(f, R_OK) == 0) { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 2899d49cf179..495a09f2e99a 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -48,6 +48,7 @@ import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; import "frameworks/base/core/proto/android/stats/intelligence/enums.proto"; import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; +import "frameworks/base/core/proto/android/stats/location/location_enums.proto"; import "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.proto"; import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto"; import "frameworks/base/core/proto/android/stats/style/style_enums.proto"; @@ -301,6 +302,7 @@ message Atom { ContentCaptureServiceEvents content_capture_service_events = 207; ContentCaptureSessionEvents content_capture_session_events = 208; ContentCaptureFlushed content_capture_flushed = 209; + LocationManagerApiUsageReported location_manager_api_usage_reported = 210; } // Pulled events will start at field 10000. @@ -6485,3 +6487,60 @@ message AppOps { // while the app was in the background (only for trusted requests) optional int64 trusted_background_duration_millis = 9; } + +/** + * Location Manager API Usage information(e.g. API under usage, + * API call's parameters). + * Logged from: + * frameworks/base/services/core/java/com/android/server/LocationManagerService.java + */ +message LocationManagerApiUsageReported { + + // Indicating if usage starts or usage ends. + optional android.stats.location.UsageState state = 1; + + // LocationManagerService's API in use. + // We can identify which API from LocationManager is + // invoking current LMS API by the combination of + // API parameter(e.g. is_listener_null, is_intent_null, + // is_location_request_null) + optional android.stats.location.LocationManagerServiceApi api_in_use = 2; + + // Name of the package calling the API. + optional string calling_package_name = 3; + + // Type of the location provider. + optional android.stats.location.ProviderType provider = 4; + + // Quality of the location request + optional android.stats.location.LocationRequestQuality quality = 5; + + // The desired interval for active location updates, in milliseconds. + // Bucketized to reduce cardinality. + optional android.stats.location.LocationRequestIntervalBucket bucketized_interval = 6; + + // Minimum distance between location updates, in meters. + // Bucketized to reduce cardinality. + optional android.stats.location.SmallestDisplacementBucket + bucketized_smallest_displacement = 7; + + // The number of location updates. + optional int64 num_updates = 8; + + // The request expiration time, in millisecond since boot. + // Bucketized to reduce cardinality. + optional android.stats.location.ExpirationBucket + bucketized_expire_in = 9; + + // Type of Callback passed in for this API. + optional android.stats.location.CallbackType callback_type = 10; + + // The radius of the central point of the alert + // region, in meters. Only for API REQUEST_GEOFENCE. + // Bucketized to reduce cardinality. + optional android.stats.location.GeofenceRadiusBucket bucketized_radius = 11; + + // Activity Importance of API caller. + // Categorized to 3 types that are interesting from location's perspective. + optional android.stats.location.ActivityImportance activiy_importance = 12; +} diff --git a/core/java/android/app/admin/DevicePolicyEventLogger.java b/core/java/android/app/admin/DevicePolicyEventLogger.java index 44ea21881e88..95a797392940 100644 --- a/core/java/android/app/admin/DevicePolicyEventLogger.java +++ b/core/java/android/app/admin/DevicePolicyEventLogger.java @@ -22,9 +22,10 @@ import android.stats.devicepolicy.nano.StringList; import android.util.StatsLog; import com.android.framework.protobuf.nano.MessageNano; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import java.util.Arrays; + /** * A wrapper for logging managed device events using {@link StatsLog}. * <p/> @@ -45,7 +46,7 @@ import com.android.internal.util.Preconditions; * @see StatsLog * @hide */ -public final class DevicePolicyEventLogger { +public class DevicePolicyEventLogger { private final int mEventId; private int mIntValue; private boolean mBooleanValue; @@ -71,7 +72,6 @@ public final class DevicePolicyEventLogger { /** * Returns the event id. */ - @VisibleForTesting public int getEventId() { return mEventId; } @@ -87,7 +87,6 @@ public final class DevicePolicyEventLogger { /** * Returns the generic <code>int</code> value. */ - @VisibleForTesting public int getInt() { return mIntValue; } @@ -103,7 +102,6 @@ public final class DevicePolicyEventLogger { /** * Returns the generic <code>boolean</code> value. */ - @VisibleForTesting public boolean getBoolean() { return mBooleanValue; } @@ -119,7 +117,6 @@ public final class DevicePolicyEventLogger { /** * Returns the time period in milliseconds. */ - @VisibleForTesting public long getTimePeriod() { return mTimePeriodMs; } @@ -162,11 +159,13 @@ public final class DevicePolicyEventLogger { } /** - * Returns the generic <code>String[]</code> value. + * Returns a copy of the generic <code>String[]</code> value. */ - @VisibleForTesting public String[] getStringArray() { - return mStringArrayValue; + if (mStringArrayValue == null) { + return null; + } + return Arrays.copyOf(mStringArrayValue, mStringArrayValue.length); } /** @@ -188,7 +187,6 @@ public final class DevicePolicyEventLogger { /** * Returns the package name of the admin application. */ - @VisibleForTesting public String getAdminPackageName() { return mAdminPackageName; } diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl index db9fcb6abdfa..1b84f2987a8f 100644 --- a/core/java/android/content/rollback/IRollbackManager.aidl +++ b/core/java/android/content/rollback/IRollbackManager.aidl @@ -30,9 +30,9 @@ interface IRollbackManager { String callerPackageName, in IntentSender statusReceiver); // Exposed for use from the system server only. Callback from the package - // manager during the install flow when user data can be restored for a given + // manager during the install flow when user data can be backed up and restored for a given // package. - void restoreUserData(String packageName, in int[] userIds, int appId, long ceDataInode, + void snapshotAndRestoreUserData(String packageName, in int[] userIds, int appId, long ceDataInode, String seInfo, int token); // Exposed for test purposes only. diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index ab630fd7467b..82d4d1d10d7e 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -472,8 +472,12 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public final void initializeInternal(IBinder token, int displayId, + public final void initializeInternal(@NonNull IBinder token, int displayId, IInputMethodPrivilegedOperations privilegedOperations) { + if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) { + Log.w(TAG, "The token has already registered, ignore this initialization."); + return; + } mPrivOps.set(privilegedOperations); InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); updateInputMethodDisplay(displayId); diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java index 4b2b4c35b292..7a85dcbfc830 100644 --- a/core/java/android/net/DnsResolver.java +++ b/core/java/android/net/DnsResolver.java @@ -16,7 +16,7 @@ package android.net; -import static android.net.NetworkUtils.getDnsNetId; +import static android.net.NetworkUtils.getDnsNetwork; import static android.net.NetworkUtils.resNetworkCancel; import static android.net.NetworkUtils.resNetworkQuery; import static android.net.NetworkUtils.resNetworkResult; @@ -333,7 +333,7 @@ public final class DnsResolver { final Object lock = new Object(); final Network queryNetwork; try { - queryNetwork = (network != null) ? network : new Network(getDnsNetId()); + queryNetwork = (network != null) ? network : getDnsNetwork(); } catch (ErrnoException e) { executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); return; @@ -433,7 +433,7 @@ public final class DnsResolver { final FileDescriptor queryfd; final Network queryNetwork; try { - queryNetwork = (network != null) ? network : new Network(getDnsNetId()); + queryNetwork = (network != null) ? network : getDnsNetwork(); queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType, flags); } catch (ErrnoException e) { diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index a640f83ea5d2..d0f54b4c7f2d 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -158,10 +158,9 @@ public class NetworkUtils { /** * DNS resolver series jni method. - * Attempts to get netid of network which resolver will - * use if no network is explicitly selected. + * Attempts to get network which resolver will use if no network is explicitly selected. */ - public static native int getDnsNetId() throws ErrnoException; + public static native Network getDnsNetwork() throws ErrnoException; /** * Get the tcp repair window associated with the {@code fd}. diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index deb9eba1bc99..77d367f0d933 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -113,8 +113,7 @@ public class Build { /** * A hardware serial number, if available. Alphanumeric only, case-insensitive. - * For apps targeting SDK higher than {@link Build.VERSION_CODES#O_MR1} this - * field is set to {@link Build#UNKNOWN}. + * This field is always set to {@link Build#UNKNOWN}. * * @deprecated Use {@link #getSerial()} instead. **/ diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 9436633c0c4b..73e0e4b2eb8b 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1301,7 +1301,7 @@ public abstract class Window { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public boolean shouldCloseOnTouch(Context context, MotionEvent event) { final boolean isOutside = - event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) + event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event) || event.getAction() == MotionEvent.ACTION_OUTSIDE; if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) { return true; diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 14fe6ab7df1c..1b0a458b3fbc 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -113,5 +113,37 @@ public final class SystemUiDeviceConfigFlags { */ public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; + // Flags related to Assistant Handles + + /** + * (String) Which behavior mode for the Assistant Handles to use. + */ + public static final String ASSIST_HANDLES_BEHAVIOR_MODE = "assist_handles_behavior_mode"; + + /** + * (long) How long, in milliseconds, to display Assist Handles when showing them temporarily. + */ + public static final String ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS = + "assist_handles_show_and_go_duration_ms"; + + /** + * (long) How long, in milliseconds, to wait before displaying Assist Handles temporarily after + * hiding them. + */ + public static final String ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS = + "assist_handles_shown_frequency_threshold_ms"; + + /** + * (long) How long, in milliseconds, for teaching behaviors to wait before considering the user + * taught. + */ + public static final String ASSIST_HANDLES_LEARN_TIME_MS = "assist_handles_learn_time_ms"; + + /** + * (int) How many times for teaching behaviors to see the user perform an action to consider it + * taught. + */ + public static final String ASSIST_HANDLES_LEARN_COUNT = "assist_handles_learn_count"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java index 1436aed5129a..049f952fceb8 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java @@ -132,4 +132,21 @@ public final class InputMethodPrivilegedOperationsRegistry { } } } + + /** + * Check the given IME token registration status. + * + * @param token IME token + * @return {@code true} when the IME token has already registered + * {@link InputMethodPrivilegedOperations}, {@code false} otherwise. + */ + @AnyThread + public static boolean isRegistered(IBinder token) { + synchronized (sLock) { + if (sRegistry == null) { + return false; + } + return sRegistry.containsKey(token); + } + } } diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 00e0e3a74d70..08aa1d97fa1c 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -304,13 +304,19 @@ static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobjec jniSetFileDescriptorOfFD(env, javaFd, -1); } -static jint android_net_utils_getDnsNetId(JNIEnv *env, jobject thiz) { - int dnsNetId = getNetworkForDns(); - if (dnsNetId < 0) { - throwErrnoException(env, "getDnsNetId", -dnsNetId); +static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) { + unsigned dnsNetId = 0; + if (int res = getNetworkForDns(&dnsNetId) < 0) { + throwErrnoException(env, "getDnsNetId", -res); + return nullptr; } + bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS; - return dnsNetId; + static jclass class_Network = MakeGlobalRefOrDie( + env, FindClassOrDie(env, "android/net/Network")); + static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V"); + return env->NewObject( + class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass); } static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { @@ -369,7 +375,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, - { "getDnsNetId", "()I", (void*) android_net_utils_getDnsNetId }, + { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/proto/android/stats/location/location_enums.proto b/core/proto/android/stats/location/location_enums.proto new file mode 100644 index 000000000000..553c01c5d0dd --- /dev/null +++ b/core/proto/android/stats/location/location_enums.proto @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.stats.location; +option java_outer_classname = "LocationStatsEnums"; + + +// APIs from LocationManagerService +enum LocationManagerServiceApi { + API_UNKNOWN = 0; + API_REQUEST_LOCATION_UPDATES = 1; + API_ADD_GNSS_MEASUREMENTS_LISTENER = 2; + API_REGISTER_GNSS_STATUS_CALLBACK = 3; + API_REQUEST_GEOFENCE = 4; + API_SEND_EXTRA_COMMAND = 5; +} + +enum UsageState { + USAGE_STARTED = 0; + USAGE_ENDED = 1; +} + +// Type of location providers +enum ProviderType { + PROVIDER_UNKNOWN = 0; + PROVIDER_NETWORK = 1; + PROVIDER_GPS = 2; + PROVIDER_PASSIVE = 3; + PROVIDER_FUSED = 4; +} + +// Type of Callback passed in for this API +enum CallbackType { + CALLBACK_UNKNOWN = 0; + // Current API does not need a callback, e.g. sendExtraCommand + CALLBACK_NOT_APPLICABLE = 1; + CALLBACK_LISTENER = 2; + CALLBACK_PENDING_INTENT = 3; +} + +// Possible values for mQuality field in +// frameworks/base/location/java/android/location/LocationRequest.java +enum LocationRequestQuality { + QUALITY_UNKNOWN = 0; + ACCURACY_FINE = 100; + ACCURACY_BLOCK = 102; + ACCURACY_CITY = 104; + POWER_NONE = 200; + POWER_LOW = 201; + POWER_HIGH = 203; +} + +// Bucketized values for interval field in +// frameworks/base/location/java/android/location/LocationRequest.java +enum LocationRequestIntervalBucket { + INTERVAL_UNKNOWN = 0; + INTERVAL_BETWEEN_0_SEC_AND_1_SEC = 1; + INTERVAL_BETWEEN_1_SEC_AND_5_SEC = 2; + INTERVAL_BETWEEN_5_SEC_AND_1_MIN = 3; + INTERVAL_BETWEEN_1_MIN_AND_10_MIN = 4; + INTERVAL_BETWEEN_10_MIN_AND_1_HOUR = 5; + INTERVAL_LARGER_THAN_1_HOUR = 6; +} + +// Bucketized values for small displacement field in +// frameworks/base/location/java/android/location/LocationRequest.java +// Value in meters. +enum SmallestDisplacementBucket { + DISTANCE_UNKNOWN = 0; + DISTANCE_ZERO = 1; + DISTANCE_BETWEEN_0_AND_100 = 2; + DISTANCE_LARGER_THAN_100 = 3; +} + +// Bucketized values for expire_in field in +// frameworks/base/location/java/android/location/LocationRequest.java +enum ExpirationBucket { + EXPIRATION_UNKNOWN = 0; + EXPIRATION_BETWEEN_0_AND_20_SEC = 1; + EXPIRATION_BETWEEN_20_SEC_AND_1_MIN = 2; + EXPIRATION_BETWEEN_1_MIN_AND_10_MIN = 3; + EXPIRATION_BETWEEN_10_MIN_AND_1_HOUR = 4; + EXPIRATION_LARGER_THAN_1_HOUR = 5; + EXPIRATION_NO_EXPIRY = 6; +} + +// Bucketized values for radius field in +// frameworks/base/location/java/android/location/Geofence.java +// Value in meters. +enum GeofenceRadiusBucket { + RADIUS_UNKNOWN = 0; + RADIUS_BETWEEN_0_AND_100 = 1; + RADIUS_BETWEEN_100_AND_200 = 2; + RADIUS_BETWEEN_200_AND_300 = 3; + RADIUS_BETWEEN_300_AND_1000 = 4; + RADIUS_BETWEEN_1000_AND_10000 = 5; + RADIUS_LARGER_THAN_100000 = 6; + RADIUS_NEGATIVE = 7; +} + +// Caller Activity Importance. +enum ActivityImportance { + IMPORTANCE_UNKNOWN = 0; + IMPORTANCE_TOP = 1; + IMPORTANCE_FORGROUND_SERVICE = 2; + IMPORTANCE_BACKGROUND = 3; +} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index fb585693c8a5..2beef4250441 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1276,9 +1276,14 @@ meanings. --> <integer name="config_defaultRingVibrationIntensity">2</integer> + <!-- Whether to use the strict phone number matcher by default. --> <bool name="config_use_strict_phone_number_comparation">false</bool> - <bool name="config_use_strict_phone_number_comparation_for_russian">true</bool> + <!-- Whether to use the strict phone number matcher in Russia. --> + <bool name="config_use_strict_phone_number_comparation_for_russia">true</bool> + + <!-- Whether to use the strict phone number matcher in Kazakhstan. --> + <bool name="config_use_strict_phone_number_comparation_for_kazakhstan">true</bool> <!-- Display low battery warning when battery level dips to this value. Also, the battery stats are flushed to disk when we hit this level. --> @@ -3306,6 +3311,10 @@ (which normally prevents seamless rotation). --> <bool name="config_allowSeamlessRotationDespiteNavBarMoving">false</bool> + <!-- Controls whether hints for gestural navigation are shown when the device is setup. + This should only be set when the device has gestural navigation enabled by default. --> + <bool name="config_showGesturalNavigationHints">false</bool> + <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index e0ab6c871700..5363ef920886 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -59,9 +59,7 @@ <!-- Height of the bottom navigation bar frame in landscape --> <dimen name="navigation_bar_frame_height_landscape">@dimen/navigation_bar_frame_height</dimen> - <!-- The height of the navigation gesture area; if the size is larger than the navigation bar - frame width/height, then the difference is the spacing from the navigation bar window to - the area that detects gestures. --> + <!-- The height of the navigation gesture area if the gesture is starting from the bottom. --> <dimen name="navigation_bar_gesture_height">@dimen/navigation_bar_frame_height</dimen> <!-- Height of the bottom navigation / system bar in car mode. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 3d2d9693d62b..4af05f699073 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -313,7 +313,8 @@ <java-symbol type="bool" name="config_ui_enableFadingMarquee" /> <java-symbol type="bool" name="config_enableHapticTextHandle" /> <java-symbol type="bool" name="config_use_strict_phone_number_comparation" /> - <java-symbol type="bool" name="config_use_strict_phone_number_comparation_for_russian" /> + <java-symbol type="bool" name="config_use_strict_phone_number_comparation_for_russia" /> + <java-symbol type="bool" name="config_use_strict_phone_number_comparation_for_kazakhstan" /> <java-symbol type="bool" name="config_single_volume" /> <java-symbol type="bool" name="config_voice_capable" /> <java-symbol type="bool" name="config_requireCallCapableAccountForHandle" /> @@ -2880,6 +2881,7 @@ <java-symbol type="bool" name="config_allowSeamlessRotationDespiteNavBarMoving" /> <java-symbol type="dimen" name="config_backGestureInset" /> <java-symbol type="color" name="system_bar_background_semi_transparent" /> + <java-symbol type="bool" name="config_showGesturalNavigationHints" /> <!-- EditText suggestion popup. --> <java-symbol type="id" name="suggestionWindowContainer" /> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index b90104899e55..628926231c65 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1473,14 +1473,10 @@ easier. <item name="colorError">@color/error_color_device_default_light</item> <item name="colorEdgeEffect">@color/edge_effect_device_default_light</item> - <!-- Add divider that matches material --> + <!-- Add white nav bar with divider that matches material --> <item name="navigationBarDividerColor">@color/navigation_bar_divider_device_default_settings</item> - - <!-- Add transparent nav and status bars with light icons to support drawing edge-to-edge - for Q gestural navigation--> - <item name="navigationBarColor">@android:color/transparent</item> + <item name="navigationBarColor">@android:color/white</item> <item name="windowLightNavigationBar">true</item> - <item name="statusBarColor">@android:color/transparent</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index b3856d501c72..a640122f2b32 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -196,6 +196,7 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.WATCH_APPOPS"/> + <permission name="android.permission.UPDATE_DEVICE_STATS"/> </privapp-permissions> <privapp-permissions package="com.android.providers.telephony"> diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index 98c990a71601..1fc056c3652f 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -273,8 +273,12 @@ public final class Outline { } /** - * Sets the Constructs an Outline from a + * Sets the Outline to a * {@link android.graphics.Path#isConvex() convex path}. + * + * @param convexPath used to construct the Outline. As of + * {@link android.os.Build.VERSION_CODES#Q}, it is no longer required to be + * convex. */ public void setConvexPath(@NonNull Path convexPath) { if (convexPath.isEmpty()) { @@ -282,10 +286,6 @@ public final class Outline { return; } - if (!convexPath.isConvex()) { - throw new IllegalArgumentException("path must be convex"); - } - if (mPath == null) { mPath = new Path(); } diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 0a2894945dc8..16c8b8923074 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -161,8 +161,7 @@ void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { // Recording Canvas draw operations: Bitmaps // ---------------------------------------------------------------------------- -SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint, - sk_sp<SkColorFilter> colorSpaceFilter) { +SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint) { bool fixBlending = false; bool fixAA = false; if (paint) { @@ -172,23 +171,13 @@ SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint, fixAA = paint->isAntiAlias(); } - if (fixBlending || fixAA || colorSpaceFilter) { + if (fixBlending || fixAA) { SkPaint& tmpPaint = paint.writeable(); if (fixBlending) { tmpPaint.setBlendMode(SkBlendMode::kDstOut); } - if (colorSpaceFilter) { - if (tmpPaint.getColorFilter()) { - tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter( - tmpPaint.refColorFilter(), std::move(colorSpaceFilter))); - } else { - tmpPaint.setColorFilter(std::move(colorSpaceFilter)); - } - LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter()); - } - // disabling AA on bitmap draws matches legacy HWUI behavior tmpPaint.setAntiAlias(false); } @@ -198,7 +187,7 @@ SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint, void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) { sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImage(image, left, top, filterPaint(paint), bitmap.palette()); + mRecorder.drawImage(image, left, top, filterBitmap(paint), bitmap.palette()); // if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means // it is not safe to store a raw SkImage pointer, because the image object will be destroyed // when this function ends. @@ -212,7 +201,7 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, con concat(matrix); sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImage(image, 0, 0, filterPaint(paint), bitmap.palette()); + mRecorder.drawImage(image, 0, 0, filterBitmap(paint), bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique()) { mDisplayList->mMutableImages.push_back(image.get()); } @@ -225,7 +214,7 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImageRect(image, srcRect, dstRect, filterPaint(paint), + mRecorder.drawImageRect(image, srcRect, dstRect, filterBitmap(paint), SkCanvas::kFast_SrcRectConstraint, bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() && !dstRect.isEmpty()) { @@ -263,7 +252,7 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch filteredPaint.writeable().setFilterQuality(kLow_SkFilterQuality); } sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImageLattice(image, lattice, dst, filterPaint(std::move(filteredPaint)), + mRecorder.drawImageLattice(image, lattice, dst, filterBitmap(std::move(filteredPaint)), bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index afeccea3fb70..c42cea33211e 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -90,7 +90,7 @@ private: */ void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height); - PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter); + PaintCoW&& filterBitmap(PaintCoW&& paint); }; } // namespace skiapipeline diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 2541982e5a5a..2d6cd242c702 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4274,38 +4274,6 @@ public class AudioManager { } } - /** - * Indicate A2DP source or sink active device change and eventually suppress - * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. - * This operation is asynchronous but its execution will still be sequentially scheduled - * relative to calls to {@link #setBluetoothHearingAidDeviceConnectionState(BluetoothDevice, - * int, boolean, int)} and - * {@link #handleBluetoothA2dpDeviceConfigChange(BluetoothDevice)}. - * @param device Bluetooth device connected/disconnected - * @param state new connection state (BluetoothProfile.STATE_xxx) - * @param profile profile for the A2DP device - * (either {@link android.bluetooth.BluetoothProfile.A2DP} or - * {@link android.bluetooth.BluetoothProfile.A2DP_SINK}) - * @param a2dpVolume New volume for the connecting device. Does nothing if - * disconnecting. Pass value -1 in case you want this field to be ignored - * @param suppressNoisyIntent if true the - * {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent. - * @return a delay in ms that the caller should wait before broadcasting - * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent. - * {@hide} - */ - public void handleBluetoothA2dpActiveDeviceChange( - BluetoothDevice device, int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { - final IAudioService service = getService(); - try { - service.handleBluetoothA2dpActiveDeviceChange(device, - state, profile, suppressNoisyIntent, a2dpVolume); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** {@hide} */ public IRingtonePlayer getRingtonePlayer() { try { diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 58c6be9be4c7..5645ba5d7dac 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1794,15 +1794,13 @@ public class ExifInterface { // Set thumbnail image offset and length setThumbnailData(inputStream); mIsSupportedFile = true; - } catch (IOException e) { + } catch (IOException | OutOfMemoryError e) { // Ignore exceptions in order to keep the compatibility with the old versions of // ExifInterface. mIsSupportedFile = false; - if (DEBUG) { - Log.d(TAG, "Invalid image: ExifInterface got an unsupported image format file" - + "(ExifInterface supports JPEG and some RAW image formats only) " - + "or a corrupted JPEG file to ExifInterface.", e); - } + Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file" + + "(ExifInterface supports JPEG and some RAW image formats only) " + + "or a corrupted JPEG file to ExifInterface.", e); } finally { addDefaultValuesForCompatibility(); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index a790441aa36e..71f52a1b7d8e 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -178,9 +178,6 @@ interface IAudioService { void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device); - void handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device, - int state, int profile, boolean suppressNoisyIntent, int a2dpVolume); - @UnsupportedAppUsage AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer); diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index 710b503d97fd..0b1ae34dccc4 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -220,7 +220,6 @@ public class MediaFile { case "audio/mpegurl": case "application/x-mpegurl": case "application/vnd.apple.mpegurl": - case "video/x-ms-asf": case "audio/x-scpls": return true; default: diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java index b5c4cca12ff7..8ee929e77899 100644 --- a/media/java/android/media/MediaHTTPConnection.java +++ b/media/java/android/media/MediaHTTPConnection.java @@ -38,6 +38,7 @@ import java.net.URL; import java.net.UnknownServiceException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; /** @hide */ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { @@ -83,6 +84,10 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { private final static int HTTP_TEMP_REDIRECT = 307; private final static int MAX_REDIRECTS = 20; + // The number of threads that are currently running disconnect() (possibly + // not yet holding the synchronized lock). + private final AtomicInteger mNumDisconnectingThreads = new AtomicInteger(0); + @UnsupportedAppUsage public MediaHTTPConnection() { CookieHandler cookieHandler = CookieHandler.getDefault(); @@ -155,19 +160,24 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { @Override @UnsupportedAppUsage public void disconnect() { - HttpURLConnection connectionToDisconnect = mConnection; - // Call disconnect() before blocking for the lock in order to ensure that any - // other thread that is blocked in readAt() will return quickly. - if (connectionToDisconnect != null) { - connectionToDisconnect.disconnect(); - } - synchronized (this) { - // It's unlikely but possible that while we were waiting to acquire the lock, another - // thread concurrently started a new connection; if so, we're disconnecting that one - // here, too. - teardownConnection(); - mHeaders = null; - mURL = null; + mNumDisconnectingThreads.incrementAndGet(); + try { + HttpURLConnection connectionToDisconnect = mConnection; + // Call disconnect() before blocking for the lock in order to ensure that any + // other thread that is blocked in readAt() will return quickly. + if (connectionToDisconnect != null) { + connectionToDisconnect.disconnect(); + } + synchronized (this) { + // It's possible that while we were waiting to acquire the lock, another thread + // concurrently started a new connection; if so, we're disconnecting that one + // here, too. + teardownConnection(); + mHeaders = null; + mURL = null; + } + } finally { + mNumDisconnectingThreads.decrementAndGet(); } } @@ -224,11 +234,36 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { boolean noProxy = isLocalHost(url); while (true) { + // If another thread is concurrently disconnect()ing, there's a race + // between them and us. Therefore, we check mNumDisconnectingThreads shortly + // (not atomically) before & after writing mConnection. This guarantees that + // we won't "lose" a disconnect by creating a new connection that might + // miss the disconnect. + // + // Note that throwing an instanceof IOException is also what this thread + // would have done if another thread disconnect()ed the connection while + // this thread was blocked reading from that connection further down in this + // loop. + if (mNumDisconnectingThreads.get() > 0) { + throw new IOException("concurrently disconnecting"); + } if (noProxy) { mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY); } else { mConnection = (HttpURLConnection)url.openConnection(); } + // If another thread is concurrently disconnecting, throwing IOException will + // cause us to release the lock, giving the other thread a chance to acquire + // it. It also ensures that the catch block will run, which will tear down + // the connection even if the other thread happens to already be on its way + // out of disconnect(). + if (mNumDisconnectingThreads.get() > 0) { + throw new IOException("concurrently disconnecting"); + } + // If we get here without having thrown, we know that other threads + // will see our write to mConnection. Any disconnect() on that mConnection + // instance will cause our read from/write to that connection instance below + // to encounter an instanceof IOException. mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS); // handle redirects ourselves if we do not allow cross-domain redirect diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java index 7f23ba576fbb..9b643ad3c086 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java @@ -25,7 +25,6 @@ import android.hardware.Camera.PictureCallback; import android.hardware.Camera.PreviewCallback; import android.hardware.Camera.ShutterCallback; import android.os.ConditionVariable; -import android.os.Environment; import android.os.Looper; import android.test.ActivityInstrumentationTestCase; import android.test.suitebuilder.annotation.LargeTest; @@ -159,7 +158,7 @@ public class CameraTest extends ActivityInstrumentationTestCase<MediaFrameworkTe if (rawData != null) { int rawDataLength = rawData.length; File rawoutput = new File( - Environment.getExternalStorageDirectory().toString(), "/test.bmp"); + mContext.getExternalFilesDir(null).getPath(), "/test.bmp"); FileOutputStream outstream = new FileOutputStream(rawoutput); outstream.write(rawData); Log.v(TAG, "JpegPictureCallback rawDataLength = " + rawDataLength); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java index 84153d6089e9..bd236a69fe84 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java @@ -22,10 +22,11 @@ import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.hardware.Camera.PictureCallback; import android.hardware.Camera.ShutterCallback; -import android.os.Environment; import android.util.Log; import android.view.SurfaceHolder; +import androidx.test.InstrumentationRegistry; + import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -70,7 +71,8 @@ public class CameraTestHelper { try { Log.v(TAG, "JPEG picture taken"); fos = new FileOutputStream(String.format("%s/%s/%s-%d.jpg", - Environment.getExternalStorageDirectory(), CAMERA_STRESS_IMAGES_DIRECTORY, + InstrumentationRegistry.getInstrumentation().getTargetContext() + .getExternalFilesDir(null).getPath(), CAMERA_STRESS_IMAGES_DIRECTORY, CAMERA_STRESS_IMAGES_PREFIX, System.currentTimeMillis())); fos.write(data); } catch (FileNotFoundException e) { @@ -95,7 +97,8 @@ public class CameraTestHelper { public void setupCameraTest() { // Create the test images directory if it doesn't exist File stressImagesDirectory = new File(String.format("%s/%s", - Environment.getExternalStorageDirectory(), CAMERA_STRESS_IMAGES_DIRECTORY)); + InstrumentationRegistry.getInstrumentation().getTargetContext() + .getExternalFilesDir(null).getPath(), CAMERA_STRESS_IMAGES_DIRECTORY)); if (!stressImagesDirectory.exists()) { stressImagesDirectory.mkdir(); } @@ -129,7 +132,8 @@ public class CameraTestHelper { public void cleanupTestImages() { try { File stressImagesDirectory = new File(String.format("%s/%s", - Environment.getExternalStorageDirectory(), CAMERA_STRESS_IMAGES_DIRECTORY)); + InstrumentationRegistry.getInstrumentation().getTargetContext() + .getExternalFilesDir(null).getPath(), CAMERA_STRESS_IMAGES_DIRECTORY)); File[] stressImages = stressImagesDirectory.listFiles(); for (File f : stressImages) { f.delete(); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java index 0340cec7432d..0ae640dd7910 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java @@ -54,7 +54,6 @@ import android.media.Image.Plane; import android.media.ImageReader; import android.media.ImageWriter; import android.os.Build; -import android.os.Environment; import android.os.Handler; import android.util.Log; import android.util.Pair; @@ -63,6 +62,8 @@ import android.view.Display; import android.view.Surface; import android.view.WindowManager; +import androidx.test.InstrumentationRegistry; + import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Array; @@ -128,7 +129,8 @@ public class CameraTestUtils extends Assert { private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER); protected static final String DEBUG_FILE_NAME_BASE = - Environment.getExternalStorageDirectory().getPath(); + InstrumentationRegistry.getInstrumentation().getTargetContext() + .getExternalFilesDir(null).getPath(); static { sTestLocation0.setTime(1199145600L); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java index 11327ca301f3..a26ee2d1a668 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java @@ -104,7 +104,6 @@ public class Camera2SwitchPreviewTest extends Camera2SurfaceViewTestCase { private static final double AE_COMPENSATION_ERROR_TOLERANCE = 0.2; // 5 percent error margin for resulting metering regions private static final float METERING_REGION_ERROR_PERCENT_DELTA = 0.05f; - private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath(); private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG); private static final int RECORDING_DURATION_MS = 3000; @@ -137,10 +136,12 @@ public class Camera2SwitchPreviewTest extends Camera2SurfaceViewTestCase { private int mVideoFrameRate; private Size mVideoSize; private long mRecordingStartTime; + private String mVideoFilePath; @Override protected void setUp() throws Exception { super.setUp(); + mVideoFilePath = mContext.getExternalFilesDir(null).getPath(); } @Override @@ -371,9 +372,9 @@ public class Camera2SwitchPreviewTest extends Camera2SurfaceViewTestCase { } // Configure preview and recording surfaces. - mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4"; + mOutMediaFileName = mVideoFilePath + "/test_video.mp4"; if (DEBUG_DUMP) { - mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_" + mOutMediaFileName = mVideoFilePath + "/test_video_" + cameraId + "_" + videoSz.toString() + ".mp4"; } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java index d1193deacecf..74244b9745f0 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java @@ -28,7 +28,6 @@ import java.util.concurrent.TimeUnit; import java.util.List; import android.hardware.Camera.Parameters; -import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.test.ActivityInstrumentationTestCase2; @@ -36,6 +35,8 @@ import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import android.view.SurfaceHolder; +import androidx.test.InstrumentationRegistry; + /** * Junit / Instrumentation test case for the following camera APIs: * - camera zoom @@ -85,7 +86,8 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram mCameraTestHelper = new CameraTestHelper(); File stressOutFile = new File(String.format("%s/%s", - Environment.getExternalStorageDirectory(), CAMERA_STRESS_OUTPUT)); + InstrumentationRegistry.getInstrumentation().getTargetContext() + .getExternalFilesDir(null).getPath(), CAMERA_STRESS_OUTPUT)); mOutput = new BufferedWriter(new FileWriter(stressOutFile, true)); mOutput.write(this.getName() + "\n"); } diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 7ee2f0a1db18..8fe4fecceeb5 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -384,6 +384,9 @@ void ASurfaceTransaction_setGeometry(ASurfaceTransaction* aSurfaceTransaction, transaction->setCrop(surfaceControl, static_cast<const Rect&>(source)); transaction->setFrame(surfaceControl, static_cast<const Rect&>(destination)); transaction->setTransform(surfaceControl, transform); + bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) == + NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY; + transaction->setTransformToDisplayInverse(surfaceControl, transformToInverseDisplay); } void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* aSurfaceTransaction, diff --git a/packages/CaptivePortalLogin/OWNERS b/packages/CaptivePortalLogin/OWNERS index d3836d4c6c57..52193eb2a12b 100644 --- a/packages/CaptivePortalLogin/OWNERS +++ b/packages/CaptivePortalLogin/OWNERS @@ -1,8 +1,5 @@ set noparent -codewiz@google.com -jchalard@google.com -junyulai@google.com lorenzo@google.com -reminv@google.com -satk@google.com +baligh@google.com +delphij@google.com diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index bda5743d27f7..23fb7b6c8ba2 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -485,7 +485,10 @@ public class CaptivePortalLoginActivity extends Activity { if (request.isForMainFrame()) { mMainFrameUrl = request.getUrl().toString(); } - return false; + // Be careful that two shouldOverrideUrlLoading methods are overridden, but + // shouldOverrideUrlLoading(WebView view, String url) was deprecated in API level 24. + // TODO: delete deprecated one ?? + return shouldOverrideUrlLoading(view, mMainFrameUrl); } // A web page consisting of a large broken lock icon to indicate SSL failure. diff --git a/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml b/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml deleted file mode 100644 index 918abd962dc2..000000000000 --- a/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<com.android.systemui.statusbar.car.privacy.OngoingPrivacyChip - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/car_privacy_chip" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_margin="@dimen/ongoing_appops_chip_margin" - android:layout_toStartOf="@+id/clock_container" - android:focusable="true" - android:gravity="center_vertical|end" - android:orientation="horizontal" - android:paddingEnd="@dimen/ongoing_appops_chip_side_padding" - android:paddingStart="@dimen/ongoing_appops_chip_side_padding" - android:visibility="visible"> - - <LinearLayout - android:id="@+id/icons_container" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical|start"/> -</com.android.systemui.statusbar.car.privacy.OngoingPrivacyChip>
\ No newline at end of file diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml index cae89c16a03e..925ccb4a162a 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml @@ -65,8 +65,6 @@ /> </FrameLayout> - <include layout="@layout/car_ongoing_privacy_chip"/> - <FrameLayout android:id="@+id/clock_container" android:layout_width="wrap_content" diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml index 55b0d875de41..0af74c4462a6 100644 --- a/packages/CarSystemUI/res/layout/notification_center_activity.xml +++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml @@ -38,7 +38,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:orientation="vertical" - android:paddingStart="@dimen/notification_shade_list_padding_bottom" + android:paddingBottom="@dimen/notification_shade_list_padding_bottom" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml index 7027ce37f670..0358357b9c1a 100644 --- a/packages/CarSystemUI/res/values/dimens.xml +++ b/packages/CarSystemUI/res/values/dimens.xml @@ -59,16 +59,6 @@ <dimen name="car_keyline_1">24dp</dimen> <dimen name="car_keyline_2">96dp</dimen> <dimen name="car_keyline_3">128dp</dimen> - <dimen name="privacy_chip_icon_height">36dp</dimen> - <dimen name="privacy_chip_icon_padding_left">0dp</dimen> - <dimen name="privacy_chip_icon_padding_right">0dp</dimen> - <dimen name="privacy_chip_icon_padding_top">0dp</dimen> - <dimen name="privacy_chip_icon_padding_bottom">0dp</dimen> - - <dimen name="privacy_chip_text_padding_left">0dp</dimen> - <dimen name="privacy_chip_text_padding_right">0dp</dimen> - <dimen name="privacy_chip_text_padding_top">0dp</dimen> - <dimen name="privacy_chip_text_padding_bottom">0dp</dimen> <dimen name="privacy_chip_icon_max_height">100dp</dimen> @@ -86,16 +76,6 @@ <dimen name="ongoing_appops_chip_bg_padding">4dp</dimen> <!-- Radius of Ongoing App Ops chip corners --> <dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen> - <!-- Start padding for the app icon displayed in the dialog --> - <dimen name="privacy_dialog_app_icon_padding_start">40dp</dimen> - <!-- End padding for the app opps icon displayed in the dialog --> - <dimen name="privacy_dialog_app_ops_icon_padding_end">40dp</dimen> - <!-- Top padding for the list of application displayed in the dialog --> - <dimen name="privacy_dialog_app_list_padding_top">20dp</dimen> - <!-- Top padding for the dialog container--> - <dimen name="privacy_dialog_container_padding_top">10dp</dimen> - <!-- Top padding for the dialog title--> - <dimen name="privacy_dialog_title_padding_start">10dp</dimen> <!-- Car volume dimens. --> <dimen name="car_volume_item_height">@*android:dimen/car_single_line_list_item_height</dimen> diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index b6b34c760594..7fbdc2ed844b 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -389,22 +389,18 @@ public class CarStatusBar extends StatusBar implements } } }); + + // Attached to the Handle bar to close the notification shade + GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext, + new HandleBarCloseNotificationGestureListener()); + mNavBarNotificationTouchListener = (v, event) -> { boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event); if (consumed) { return true; } - if (event.getActionMasked() == MotionEvent.ACTION_UP - && mNotificationView.getVisibility() == View.VISIBLE) { - if (mSettleClosePercentage < mPercentageFromBottom) { - animateNotificationPanel( - DEFAULT_FLING_VELOCITY, false); - } else { - animateNotificationPanel(DEFAULT_FLING_VELOCITY, - true); - } - } + maybeCompleteAnimation(event); return true; }; @@ -418,15 +414,7 @@ public class CarStatusBar extends StatusBar implements if (consumed) { return true; } - if (event1.getActionMasked() == MotionEvent.ACTION_UP - && mNotificationView.getVisibility() == View.VISIBLE) { - if (mSettleOpenPercentage > mPercentageFromBottom) { - animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); - } else { - animateNotificationPanel( - DEFAULT_FLING_VELOCITY, false); - } - } + maybeCompleteAnimation(event1); return true; } ); @@ -498,6 +486,13 @@ public class CarStatusBar extends StatusBar implements } return false; }); + + mHandleBar.setOnTouchListener((v, event) -> { + handleBarCloseNotificationGestureDetector.onTouchEvent(event); + maybeCompleteAnimation(event); + return true; + }); + mNotificationList = mNotificationView.findViewById(R.id.notifications); mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -612,6 +607,17 @@ public class CarStatusBar extends StatusBar implements setPanelExpanded(false); } + private void maybeCompleteAnimation(MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_UP + && mNotificationView.getVisibility() == View.VISIBLE) { + if (mSettleClosePercentage < mPercentageFromBottom) { + animateNotificationPanel(DEFAULT_FLING_VELOCITY, false); + } else { + animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); + } + } + } + /** * Animates the notification shade from one position to other. This is used to either open or * close the notification shade completely with a velocity. If the animation is to close the @@ -1054,8 +1060,10 @@ public class CarStatusBar extends StatusBar implements private static final int SWIPE_MAX_OFF_PATH = 75; private static final int SWIPE_THRESHOLD_VELOCITY = 200; - // Only responsible for open hooks. Since once the panel opens it covers all elements - // there is no need to merge with close. + /** + * Only responsible for open hooks. Since once the panel opens it covers all elements + * there is no need to merge with close. + */ private abstract class OpenNotificationGestureListener extends GestureDetector.SimpleOnGestureListener { @@ -1098,7 +1106,9 @@ public class CarStatusBar extends StatusBar implements protected abstract void openNotification(); } - // to be installed on the open panel notification panel + /** + * To be installed on the open panel notification panel + */ private abstract class CloseNotificationGestureListener extends GestureDetector.SimpleOnGestureListener { @@ -1172,7 +1182,9 @@ public class CarStatusBar extends StatusBar implements protected abstract void close(); } - // To be installed on the nav bars. + /** + * To be installed on the nav bars. + */ private abstract class NavBarCloseNotificationGestureListener extends CloseNotificationGestureListener { @Override @@ -1201,7 +1213,28 @@ public class CarStatusBar extends StatusBar implements } /** - * SystemUi version onf the notification manager that overrides methods such that the + * To be installed on the handle bar. + */ + private class HandleBarCloseNotificationGestureListener extends + GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, + float distanceY) { + calculatePercentageFromBottom(event2.getRawY()); + // To prevent the jump in the clip bounds while closing the notification shade using + // the handle bar we should calculate the height using the diff of event1 and event2. + // This will help the notification shade to clip smoothly as the event2 value changes + // as event1 value will be fixed. + int clipHeight = + mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY()); + setNotificationViewClipBounds(clipHeight); + return true; + } + } + + /** + * SystemUi version of the notification manager that overrides methods such that the * notifications end up in the status bar layouts instead of a standalone window. */ private class CarSystemUIHeadsUpNotificationManager extends CarHeadsUpNotificationManager { diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java deleted file mode 100644 index ead1de2bd352..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.appops.AppOpItem; -import com.android.systemui.appops.AppOpsController; -import com.android.systemui.plugins.ActivityStarter; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Layout defining the privacy chip that will be displayed in CarStatusRar with the information for - * which applications are using AppOpps permission fpr camera, mic and location. - */ -public class OngoingPrivacyChip extends LinearLayout implements View.OnClickListener { - - private Context mContext; - - private LinearLayout mIconsContainer; - private List<PrivacyItem> mPrivacyItems; - private static AppOpsController sAppOpsController; - private UserManager mUserManager; - private int mCurrentUser; - private List<Integer> mCurrentUserIds; - private boolean mListening = false; - PrivacyDialogBuilder mPrivacyDialogBuilder; - private LinearLayout mPrivacyChip; - private ActivityStarter mActivityStarter; - - protected static final int[] OPS = new int[]{ - AppOpsManager.OP_CAMERA, - AppOpsManager.OP_RECORD_AUDIO, - AppOpsManager.OP_COARSE_LOCATION, - AppOpsManager.OP_FINE_LOCATION - }; - - public OngoingPrivacyChip(Context context) { - super(context, null); - init(context); - } - - public OngoingPrivacyChip(Context context, AttributeSet attr) { - super(context, attr); - init(context); - } - - public OngoingPrivacyChip(Context context, AttributeSet attr, int defStyle) { - super(context, attr, defStyle); - init(context); - } - - public OngoingPrivacyChip(Context context, AttributeSet attr, int defStyle, int a) { - super(context, attr, defStyle, a); - init(context); - } - - private void init(Context context) { - mContext = context; - mPrivacyItems = new ArrayList<>(); - sAppOpsController = Dependency.get(AppOpsController.class); - mUserManager = mContext.getSystemService(UserManager.class); - mActivityStarter = Dependency.get(ActivityStarter.class); - mCurrentUser = ActivityManager.getCurrentUser(); - mCurrentUserIds = mUserManager.getProfiles(mCurrentUser).stream().map( - userInfo -> userInfo.id).collect(Collectors.toList()); - - mPrivacyDialogBuilder = new PrivacyDialogBuilder(context, mPrivacyItems); - } - - private AppOpsController.Callback mCallback = new AppOpsController.Callback() { - - @Override - public void onActiveStateChanged(int code, int uid, String packageName, boolean active) { - int userId = UserHandle.getUserId(uid); - if (mCurrentUserIds.contains(userId)) { - updatePrivacyList(); - } - } - }; - - @Override - public void onFinishInflate() { - mIconsContainer = findViewById(R.id.icons_container); - mPrivacyChip = (LinearLayout) findViewById(R.id.car_privacy_chip); - if (mPrivacyChip != null) { - mPrivacyChip.setOnClickListener(this); - setListening(true); - } - } - - @Override - public void onDetachedFromWindow() { - if (mPrivacyChip != null) { - setListening(false); - } - super.onDetachedFromWindow(); - } - - @Override - public void onClick(View v) { - updatePrivacyList(); - Handler mUiHandler = new Handler(Looper.getMainLooper()); - mUiHandler.post(() -> { - mActivityStarter.postStartActivityDismissingKeyguard( - new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0); - }); - } - - private void setListening(boolean listen) { - if (mListening == listen) { - return; - } - mListening = listen; - if (mListening) { - sAppOpsController.addCallback(OPS, mCallback); - updatePrivacyList(); - } else { - sAppOpsController.removeCallback(OPS, mCallback); - } - } - - private void updatePrivacyList() { - mPrivacyItems = mCurrentUserIds.stream() - .flatMap(item -> sAppOpsController.getActiveAppOpsForUser(item).stream()) - .filter(Objects::nonNull) - .map(item -> toPrivacyItem(item)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems); - - Handler refresh = new Handler(Looper.getMainLooper()); - refresh.post(new Runnable() { - @Override - public void run() { - updateView(); - } - }); - } - - private PrivacyItem toPrivacyItem(AppOpItem appOpItem) { - PrivacyType type; - switch (appOpItem.getCode()) { - case AppOpsManager.OP_CAMERA: - type = PrivacyType.TYPE_CAMERA; - break; - case AppOpsManager.OP_COARSE_LOCATION: - type = PrivacyType.TYPE_LOCATION; - break; - case AppOpsManager.OP_FINE_LOCATION: - type = PrivacyType.TYPE_LOCATION; - break; - case AppOpsManager.OP_RECORD_AUDIO: - type = PrivacyType.TYPE_MICROPHONE; - break; - default: - return null; - } - PrivacyApplication app = new PrivacyApplication(appOpItem.getPackageName(), mContext); - return new PrivacyItem(type, app, appOpItem.getTimeStarted()); - } - - // Should only be called if the mPrivacyDialogBuilder icons or app changed - private void updateView() { - if (mPrivacyItems.isEmpty()) { - mPrivacyChip.setVisibility(GONE); - return; - } - mPrivacyChip.setVisibility(VISIBLE); - setIcons(mPrivacyDialogBuilder); - - requestLayout(); - } - - private void setIcons(PrivacyDialogBuilder dialogBuilder) { - mIconsContainer.removeAllViews(); - dialogBuilder.generateIcons().forEach(item -> { - int size = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_height); - ImageView image = new ImageView(mContext); - image.setImageDrawable(item); - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(size, size); - - int leftPadding = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_padding_left); - int topPadding = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_padding_top); - int rightPadding = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_padding_right); - int bottomPadding = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_padding_bottom); - image.setLayoutParams(layoutParams); - image.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); - mIconsContainer.addView(image); - }); - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java deleted file mode 100644 index 5ec7a77cb305..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -import android.car.userlib.CarUserManagerHelper; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.util.Log; - -/** - * Class to hold the data for the applications that are using the AppOps permissions. - */ -public class PrivacyApplication { - private static final String TAG = "PrivacyApplication"; - - private Drawable mIcon; - private String mApplicationName; - - public PrivacyApplication(String packageName, Context context) { - try { - CarUserManagerHelper carUserManagerHelper = new CarUserManagerHelper(context); - ApplicationInfo app = context.getPackageManager() - .getApplicationInfoAsUser(packageName, 0, - carUserManagerHelper.getCurrentForegroundUserId()); - mIcon = context.getPackageManager().getApplicationIcon(app); - mApplicationName = context.getPackageManager().getApplicationLabel(app).toString(); - } catch (PackageManager.NameNotFoundException e) { - mApplicationName = packageName; - Log.e(TAG, "Failed to to find package name", e); - } - } - - /** - * Gets the application name. - */ - public Drawable getIcon() { - return mIcon; - } - - /** - * Gets the application name. - */ - public String getApplicationName() { - return mApplicationName; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java deleted file mode 100644 index 3b83e7cc0623..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -import android.content.Context; -import android.graphics.drawable.Drawable; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Helper class to build the {@link OngoingPrivacyDialog} - */ -public class PrivacyDialogBuilder { - - private Map<PrivacyType, List<PrivacyItem>> mItemsByType; - private PrivacyApplication mApplication; - private Context mContext; - - public PrivacyDialogBuilder(Context context, List<PrivacyItem> itemsList) { - mContext = context; - mItemsByType = itemsList.stream().filter(Objects::nonNull).collect( - Collectors.groupingBy(PrivacyItem::getPrivacyType)); - List<PrivacyApplication> apps = itemsList.stream().filter(Objects::nonNull).map( - PrivacyItem::getPrivacyApplication).distinct().collect(Collectors.toList()); - mApplication = apps.size() == 1 ? apps.get(0) : null; - } - - /** - * Gets the icon id for all the {@link PrivacyItem} in the same order as of itemList. - */ - public List<Drawable> generateIcons() { - return mItemsByType.keySet().stream().map(item -> item.getIconId(mContext)).collect( - Collectors.toList()); - } - - /** - * Gets the application object. - */ - public PrivacyApplication getApplication() { - return mApplication; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java deleted file mode 100644 index fca137392d74..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -/** - * Class for holding the data of each privacy item displayed in {@link OngoingPrivacyDialog} - */ -public class PrivacyItem { - - private PrivacyType mPrivacyType; - private PrivacyApplication mPrivacyApplication; - - public PrivacyItem(PrivacyType privacyType, PrivacyApplication privacyApplication, - long timeStarted) { - this.mPrivacyType = privacyType; - this.mPrivacyApplication = privacyApplication; - } - - /** - * Gets the application object. - */ - public PrivacyApplication getPrivacyApplication() { - return mPrivacyApplication; - } - - /** - * Gets the privacy type for the application. - */ - public PrivacyType getPrivacyType() { - return mPrivacyType; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java deleted file mode 100644 index 8955c87bbff4..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -import android.content.Context; -import android.graphics.drawable.Drawable; - -import com.android.systemui.R; - -/** - * Enum for storing data for camera, mic and location. - */ -public enum PrivacyType { - TYPE_CAMERA(R.string.privacy_type_camera, com.android.internal.R.drawable.ic_camera), - TYPE_LOCATION(R.string.privacy_type_location, R.drawable.stat_sys_location), - TYPE_MICROPHONE(R.string.privacy_type_microphone, R.drawable.ic_mic_white); - - private int mNameId; - private int mIconId; - - PrivacyType(int nameId, int iconId) { - mNameId = nameId; - mIconId = iconId; - } - - /** - * Get the icon Id. - */ - public Drawable getIconId(Context context) { - return context.getResources().getDrawable(mIconId, null); - } - - /** - * Get the name Id. - */ - public String getNameId(Context context) { - return context.getResources().getString(mNameId); - } -} diff --git a/packages/ExtServices/OWNERS b/packages/ExtServices/OWNERS new file mode 100644 index 000000000000..bf52f35ee6e0 --- /dev/null +++ b/packages/ExtServices/OWNERS @@ -0,0 +1,4 @@ +set noparent + +baligh@google.com +delphij@google.com diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index da3416b886ad..1b27b52f1fa1 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -638,7 +638,7 @@ public class ExternalStorageProvider extends FileSystemProvider { final String docId = DocumentsContract.getDocumentId(documentUri); try { final Bundle out = new Bundle(); - final Uri uri = Uri.fromFile(getFileForDocId(docId)); + final Uri uri = Uri.fromFile(getFileForDocId(docId, true)); out.putParcelable(DocumentsContract.EXTRA_URI, uri); return out; } catch (FileNotFoundException e) { diff --git a/packages/NetworkPermissionConfig/OWNERS b/packages/NetworkPermissionConfig/OWNERS new file mode 100644 index 000000000000..52193eb2a12b --- /dev/null +++ b/packages/NetworkPermissionConfig/OWNERS @@ -0,0 +1,5 @@ +set noparent + +lorenzo@google.com +baligh@google.com +delphij@google.com diff --git a/packages/NetworkStack/OWNERS b/packages/NetworkStack/OWNERS index 0e1e65df818f..52193eb2a12b 100644 --- a/packages/NetworkStack/OWNERS +++ b/packages/NetworkStack/OWNERS @@ -1,6 +1,5 @@ -codewiz@google.com -jchalard@google.com -junyulai@google.com +set noparent + lorenzo@google.com -reminv@google.com -satk@google.com +baligh@google.com +delphij@google.com diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 2fae0c703084..9bf44579127e 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -195,6 +195,7 @@ public class NetworkStackService extends Service { @Override public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) throws RemoteException { + checkNetworkStackCallingPermission(); updateSystemAidlVersion(cb.getInterfaceVersion()); final SharedLog log = addValidationLogs(network, name); final NetworkMonitor nm = new NetworkMonitor(mContext, cb, network, log); @@ -203,6 +204,7 @@ public class NetworkStackService extends Service { @Override public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException { + checkNetworkStackCallingPermission(); updateSystemAidlVersion(cb.getInterfaceVersion()); final IpClient ipClient = new IpClient(mContext, ifName, cb, mObserverRegistry, this); @@ -228,6 +230,7 @@ public class NetworkStackService extends Service { @Override public void fetchIpMemoryStore(@NonNull final IIpMemoryStoreCallbacks cb) throws RemoteException { + checkNetworkStackCallingPermission(); updateSystemAidlVersion(cb.getInterfaceVersion()); cb.onIpMemoryStoreFetched(mIpMemoryStoreService); } diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java index 6fbeeadb7e72..670138411bae 100644 --- a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java +++ b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java @@ -16,30 +16,56 @@ package com.android.server.util; +import static android.os.Binder.getCallingPid; import static android.os.Binder.getCallingUid; import android.os.Process; import android.os.UserHandle; +import java.util.concurrent.atomic.AtomicInteger; + /** * Utility class to check calling permissions on the network stack. */ public final class PermissionUtil { + private static final AtomicInteger sSystemPid = new AtomicInteger(-1); /** * Check that the caller is allowed to communicate with the network stack. * @throws SecurityException The caller is not allowed to communicate with the network stack. */ public static void checkNetworkStackCallingPermission() { - // TODO: check that the calling PID is the system server. final int caller = getCallingUid(); - if (caller != Process.SYSTEM_UID - && UserHandle.getAppId(caller) != Process.BLUETOOTH_UID - && UserHandle.getAppId(caller) != Process.PHONE_UID) { + if (caller == Process.SYSTEM_UID) { + checkConsistentSystemPid(); + return; + } + + if (UserHandle.getAppId(caller) != Process.BLUETOOTH_UID) { throw new SecurityException("Invalid caller: " + caller); } } + private static void checkConsistentSystemPid() { + // Apart from the system server process, no process with a system UID should try to + // communicate with the network stack. This is to ensure that the network stack does not + // need to maintain behavior for clients it was not designed to work with. + // Checking that all calls from a system UID originate from the same PID loosely enforces + // this restriction as if another system process calls the network stack first, the system + // server would lose access to the network stack and cause obvious failures. If the system + // server calls the network stack first, other clients would lose access as expected. + final int systemPid = getCallingPid(); + if (sSystemPid.compareAndSet(-1, systemPid)) { + // sSystemPid was unset (-1): this was the first call + return; + } + + if (sSystemPid.get() != systemPid) { + throw new SecurityException("Invalid PID for the system server, expected " + + sSystemPid.get() + " but was called from " + systemPid); + } + } + /** * Check that the caller is allowed to dump the network stack, e.g. dumpsys. * @throws SecurityException The caller is not allowed to dump the network stack. diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index f7132e368e94..d07bc327971a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3043,15 +3043,16 @@ public class SettingsProvider extends ContentProvider { // Increment the generation first, so observers always see the new value mGenerationRegistry.incrementGeneration(key); - if (isGlobalSettingsKey(key)) { + if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) { final long token = Binder.clearCallingIdentity(); try { - if (Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name)) { + if (Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name) + && isGlobalSettingsKey(key)) { // When the global kill switch is updated, send the // change notification for the location setting. notifyLocationChangeForRunningUsers(); } - notifyGlobalSettingChangeForRunningUsers(key, name); + notifySettingChangeForRunningUsers(key, name); } finally { Binder.restoreCallingIdentity(token); } @@ -3091,7 +3092,7 @@ public class SettingsProvider extends ContentProvider { } } - private void notifyGlobalSettingChangeForRunningUsers(int key, String name) { + private void notifySettingChangeForRunningUsers(int key, String name) { // Important: No need to update generation for each user as there // is a singleton generation entry for the global settings which // is already incremented be the caller. diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 94259416d274..91a8ab5f692f 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -58,6 +58,7 @@ android_library { "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", "androidx.dynamicanimation_dynamicanimation", + "androidx-constraintlayout_constraintlayout", "iconloader_base", "SystemUI-tags", "SystemUI-proto", @@ -111,6 +112,7 @@ android_library { "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", "androidx.dynamicanimation_dynamicanimation", + "androidx-constraintlayout_constraintlayout", "SystemUI-tags", "SystemUI-proto", "metrics-helper-lib", diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index c071b8b15a43..83acfa06976f 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -12,6 +12,7 @@ cwren@google.com dupin@google.com ethibodeau@google.com evanlaird@google.com +hyunyoungs@google.com jmonk@google.com jaggies@google.com jjaggi@google.com @@ -24,6 +25,8 @@ kprevas@google.com lynhan@google.com madym@google.com mankoff@google.com +mrcasey@google.com +mrenouf@google.com nbenbernou@google.com nesciosquid@google.com ngmatthew@google.com diff --git a/packages/SystemUI/res/drawable/corner_gesture_hint.xml b/packages/SystemUI/res/drawable/corner_gesture_hint.xml deleted file mode 100644 index 3f4abb0e0dd4..000000000000 --- a/packages/SystemUI/res/drawable/corner_gesture_hint.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2019 The Android Open Source Project - - 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:height="12dp" - android:width="12dp" - android:viewportWidth="12" - android:viewportHeight="12"> - - <path android:fillColor="#00000000" - android:pathData="M 1.18 10.65 C 1.18 5.58 5.41 1.18 10.65 1.18" - android:strokeColor="#000" - android:strokeLineCap="round" - android:strokeWidth="1.3" /> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_emergency_star.xml b/packages/SystemUI/res/drawable/ic_emergency_star.xml new file mode 100644 index 000000000000..148f250ddf11 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_emergency_star.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M9.321,2l0,5.359l-4.641,-2.68l-2.68,4.641l4.641,2.679l-4.641,2.68l2.68,4.641l4.641,-2.68l0,5.359l5.359,0l0,-5.359l4.641,2.68l2.68,-4.641l-4.641,-2.68l4.641,-2.679l-2.68,-4.641l-4.641,2.679l0,-5.359z" + android:strokeColor="#00000000" + android:fillColor="#EA4335" + android:strokeWidth="1"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_notifications_alert.xml b/packages/SystemUI/res/drawable/ic_notifications_alert.xml index eb7b8eeb9251..d53d698e1bcb 100644 --- a/packages/SystemUI/res/drawable/ic_notifications_alert.xml +++ b/packages/SystemUI/res/drawable/ic_notifications_alert.xml @@ -14,11 +14,11 @@ Copyright (C) 2018 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24.0dp" - android:height="24.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> <path - android:pathData="M7.58 4.08L6.15 2.65C3.75 4.48 2.17 7.3 2.03 10.5h2c.15-2.65 1.51-4.97 3.55-6.42zm12.39 6.42h2c-.15-3.2-1.73-6.02-4.12-7.85l-1.42 1.43c2.02 1.45 3.39 3.77 3.54 6.42zM18 11c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2v-5zm-6 11c.14 0 .27-.01.4-.04.65-.14 1.18-.58 1.44-1.18.1-.24.15-.5.15-.78h-4c.01 1.1.9 2 2.01 2z" - android:fillColor="#FF000000"/> -</vector> + android:fillColor="@android:color/white" + android:pathData="M18 17v-6c0-3.07-1.63-5.64-4.5-6.32V4c0-0.83-0.67-1.5-1.5-1.5s-1.5 0.67 -1.5 1.5v0.68C7.64 5.36 6 7.92 6 11v6H4v2h16v-2h-2zm-2 0H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6zm-6 3h4c0 1.1-0.9 2-2 2s-2-0.9-2-2zm12-9h-2c0-2.74-1.23-5.19-3.16-6.84l1.41-1.41C20.54 4.77 22 7.71 22 11zM5.75 2.75l1.41 1.41C5.23 5.81 4 8.26 4 11H2c0-3.29 1.46-6.23 3.75-8.25z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_notifications_silence.xml b/packages/SystemUI/res/drawable/ic_notifications_silence.xml index ff136eb44aac..a6cc81b5fdf7 100644 --- a/packages/SystemUI/res/drawable/ic_notifications_silence.xml +++ b/packages/SystemUI/res/drawable/ic_notifications_silence.xml @@ -14,15 +14,11 @@ Copyright (C) 2018 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24.0dp" - android:height="24.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> <path - android:pathData="M0 0h24v24H0z" - /> - <path - android:pathData="M20 18.69L7.84 6.14 5.27 3.49 4 4.76l2.8 2.8v.01c-.52.99-.8 2.16-.8 3.42v5l-2 2v1h13.73l2 2L21 19.72l-1-1.03zM12 22c1.11 0 2-.89 2-2h-4c0 1.11.89 2 2 2zm6-7.32V11c0-3.08-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68c-.15.03-.29.08-.42.12-.1.03-.2.07-.3.11h-.01c-.01 0-.01 0-.02.01-.23.09-.46.2-.68.31 0 0-.01 0-.01.01L18 14.68z" - android:fillColor="#FF000000" - /> -</vector> + android:fillColor="@android:color/white" + android:pathData="M12 22c1.1 0 2-0.9 2-2h-4c0 1.1 0.9 2 2 2zm4-6L2.81 2.81 1.39 4.22l4.85 4.85C6.09 9.68 6 10.33 6 11v6H4v2h12.17l3.61 3.61 1.41-1.41L16 16zm-8 1l0.01-6.16L14.17 17H8zm4-10.5c2.49 0 4 2.02 4 4.5v2.17l2 2V11c0-3.07-1.63-5.64-4.5-6.32V4c0-0.83-0.67-1.5-1.5-1.5s-1.5 0.67 -1.5 1.5v0.68c-0.78 0.18 -1.45 0.52 -2.04 0.95 L9.93 7.1c0.58-0.37 1.27-0.6 2.07-0.6z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml index 9f7744e3c1bf..23cb206085ea 100644 --- a/packages/SystemUI/res/drawable/ic_volume_media_bt.xml +++ b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml @@ -14,14 +14,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24.0dp" - android:height="24.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" android:tint="?android:attr/colorControlNormal"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M9,3l0.01,10.55C8.41,13.21 7.73,13 7.01,13C4.79,13 3,14.79 3,17c0,2.21 1.79,4 4.01,4S11,19.21 11,17V7h4V3H9zM7.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C9.01,18.1 8.11,19 7.01,19zM21,12.43L17.57,9h-0.6v4.55l-2.75,-2.75l-0.85,0.85L16.73,15l-3.35,3.35l0.85,0.85l2.75,-2.75V21h0.6L21,17.57L18.42,15L21,12.43zM18.17,11.3l1.13,1.13l-1.13,1.13V11.3zM19.3,17.57l-1.13,1.13v-2.26L19.3,17.57z"/> - -</vector> + android:fillColor="@android:color/white" + android:pathData="M9 3l0.01 10.55c-0.6-0.34-1.28-0.55-2-0.55C4.79 13 3 14.79 3 17s1.79 4 4.01 4S11 19.21 11 17V7h4V3H9zm12 9.43L17.57 9h-0.6v4.55l-2.75-2.75-0.85 0.85 L16.73 15l-3.35 3.35 0.85 0.85 2.75-2.75V21h0.6L21 17.57 18.42 15 21 12.43zm-2.83-1.13l1.13 1.13-1.13 1.13V11.3zm1.13 6.27l-1.13 1.13v-2.26l1.13 1.13z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml index 12e0f2ee02c9..2469ddc4860f 100644 --- a/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml +++ b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml @@ -14,14 +14,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:height="24dp" - android:viewportHeight="24.0" - android:viewportWidth="24.0" android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" android:tint="?android:attr/colorControlNormal"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M9,6.17L9,3h6v4h-4v1.17L9,6.17zM19.42,15L22,17.57l-0.8,0.8l-6.78,-6.78l0.8,-0.8l2.75,2.75V9h0.6L22,12.43L19.42,15zM19.17,13.55l1.13,-1.13l-1.13,-1.13V13.55zM17.21,17.21l3.98,3.98l-1.41,1.41l-3.98,-3.98l-0.58,0.58l-0.85,-0.85l0.58,-0.58L11,13.83V17c0,2.21 -1.78,4 -3.99,4S3,19.21 3,17c0,-2.21 1.79,-4 4.01,-4c0.73,0 1.41,0.21 2,0.55l0,-1.72L1.39,4.22l1.41,-1.41l13.56,13.56L17.21,17.21zM9.01,17c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2c0,1.1 0.9,2 2,2S9.01,18.1 9.01,17z"/> - -</vector> + android:fillColor="@android:color/white" + android:pathData="M9 6.17V3h6v4h-4v1.17l-2-2zM19.42 15L22 17.57l-0.8 0.8 -6.78-6.78 0.8 -0.8 2.75 2.75V9h0.6L22 12.43 19.42 15zm-0.25-1.45l1.13-1.13-1.13-1.13v2.26zm-1.96 3.66l3.98 3.98-1.41 1.41-3.98-3.98-0.58 0.58 -0.85-0.85 0.58 -0.58L11 13.83V17c0 2.21-1.78 4-3.99 4S3 19.21 3 17s1.79-4 4.01-4c0.73 0 1.41 0.21 2 0.55v-1.72L1.39 4.22 2.8 2.81l13.56 13.56 0.85 0.84z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/global_actions_column.xml b/packages/SystemUI/res/layout-land/global_actions_column.xml index 99a4e13d2064..98cb0d0d4c23 100644 --- a/packages/SystemUI/res/layout-land/global_actions_column.xml +++ b/packages/SystemUI/res/layout-land/global_actions_column.xml @@ -42,10 +42,10 @@ android:orientation="horizontal" android:layout_marginTop="@dimen/global_actions_grid_side_margin" android:translationZ="@dimen/global_actions_translate" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:paddingLeft="@dimen/global_actions_grid_vertical_padding" + android:paddingRight="@dimen/global_actions_grid_vertical_padding" + android:paddingTop="@dimen/global_actions_grid_horizontal_padding" + android:paddingBottom="@dimen/global_actions_grid_horizontal_padding" android:background="?android:attr/colorBackgroundFloating" /> <!-- For separated items--> @@ -55,10 +55,10 @@ android:layout_height="wrap_content" android:layout_marginLeft="@dimen/global_actions_grid_side_margin" android:layout_marginTop="@dimen/global_actions_grid_side_margin" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:paddingLeft="@dimen/global_actions_grid_vertical_padding" + android:paddingRight="@dimen/global_actions_grid_vertical_padding" + android:paddingTop="@dimen/global_actions_grid_horizontal_padding" + android:paddingBottom="@dimen/global_actions_grid_horizontal_padding" android:orientation="horizontal" android:background="?android:attr/colorBackgroundFloating" android:translationZ="@dimen/global_actions_translate" diff --git a/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml index 0f8613194959..5e7604c31731 100644 --- a/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml +++ b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml @@ -41,10 +41,10 @@ android:orientation="horizontal" android:layout_marginBottom="@dimen/global_actions_grid_side_margin" android:translationZ="@dimen/global_actions_translate" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:paddingLeft="@dimen/global_actions_grid_vertical_padding" + android:paddingRight="@dimen/global_actions_grid_vertical_padding" + android:paddingTop="@dimen/global_actions_grid_horizontal_padding" + android:paddingBottom="@dimen/global_actions_grid_horizontal_padding" android:background="?android:attr/colorBackgroundFloating" /> <!-- For separated items--> @@ -55,10 +55,10 @@ android:layout_height="wrap_content" android:layout_marginRight="@dimen/global_actions_grid_side_margin" android:layout_marginBottom="@dimen/global_actions_grid_side_margin" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:paddingLeft="@dimen/global_actions_grid_vertical_padding" + android:paddingRight="@dimen/global_actions_grid_vertical_padding" + android:paddingTop="@dimen/global_actions_grid_horizontal_padding" + android:paddingBottom="@dimen/global_actions_grid_horizontal_padding" android:orientation="horizontal" android:background="?android:attr/colorBackgroundFloating" android:translationZ="@dimen/global_actions_translate" diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml index 3928062e43d2..456553d404dc 100644 --- a/packages/SystemUI/res/layout/global_actions_grid.xml +++ b/packages/SystemUI/res/layout/global_actions_grid.xml @@ -1,73 +1,95 @@ <?xml version="1.0" encoding="utf-8"?> -<com.android.systemui.globalactions.GlobalActionsGridLayout +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@id/global_actions_view" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/global_actions_grid_root" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="horizontal" - android:theme="@style/qs_theme" - android:gravity="bottom | center_horizontal" android:clipChildren="false" android:clipToPadding="false" - android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" > - <LinearLayout + + <FrameLayout + android:id="@+id/global_actions_panel_container" + android:layout_width="match_parent" + android:layout_height="0dp" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@id/global_actions_view" + /> + + <com.android.systemui.globalactions.GlobalActionsGridLayout + android:id="@id/global_actions_view" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layoutDirection="ltr" + android:orientation="horizontal" + android:theme="@style/qs_theme" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:gravity="bottom | center_horizontal" android:clipChildren="false" android:clipToPadding="false" - android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" + android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" + android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" > - <!-- For separated items--> <LinearLayout - android:id="@+id/separated_button" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_marginLeft="@dimen/global_actions_grid_side_margin" - android:layout_marginRight="@dimen/global_actions_grid_side_margin" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" - android:orientation="vertical" - android:gravity="center" - android:translationZ="@dimen/global_actions_translate" - /> - <!-- Grid of action items --> - <com.android.systemui.globalactions.ListGridLayout - android:id="@android:id/list" - android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="vertical" - android:gravity="right" - android:layout_marginRight="@dimen/global_actions_grid_side_margin" - android:translationZ="@dimen/global_actions_translate" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:layout_width="wrap_content" + android:layoutDirection="ltr" + android:clipChildren="false" + android:clipToPadding="false" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" > + <!-- For separated items--> <LinearLayout + android:id="@+id/separated_button" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:layoutDirection="locale" - /> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:layoutDirection="locale" + android:layout_height="match_parent" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:orientation="vertical" + android:gravity="center" + android:translationZ="@dimen/global_actions_translate" /> - <LinearLayout + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:visibility="gone" - android:layoutDirection="locale" - /> - </com.android.systemui.globalactions.ListGridLayout> - </LinearLayout> + android:orientation="vertical" + android:gravity="right" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> -</com.android.systemui.globalactions.GlobalActionsGridLayout> + </com.android.systemui.globalactions.GlobalActionsGridLayout> +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/invocation_lights.xml b/packages/SystemUI/res/layout/invocation_lights.xml new file mode 100644 index 000000000000..ff78670d0719 --- /dev/null +++ b/packages/SystemUI/res/layout/invocation_lights.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<com.android.systemui.assist.ui.InvocationLightsView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:visibility="gone"/> diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 8ffa2d83cfa2..87de9d4d3b51 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -219,7 +219,7 @@ asked for it --> android:gravity="center" android:orientation="vertical"> - <LinearLayout + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout android:id="@+id/alert" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -268,9 +268,9 @@ asked for it --> android:ellipsize="end" android:maxLines="2" android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> - </LinearLayout> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> - <LinearLayout + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout android:id="@+id/silence" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -321,7 +321,7 @@ asked for it --> android:ellipsize="end" android:maxLines="2" android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> - </LinearLayout> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml index 02651a212816..b409c8f2ba5a 100644 --- a/packages/SystemUI/res/layout/rounded_corners.xml +++ b/packages/SystemUI/res/layout/rounded_corners.xml @@ -18,36 +18,30 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> + <com.android.systemui.CornerHandleView + android:id="@+id/assist_hint_left" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_gravity="left|top" + android:visibility="gone"/> + <com.android.systemui.CornerHandleView + android:id="@+id/assist_hint_right" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_gravity="right|bottom" + android:visibility="gone"/> <ImageView android:id="@+id/left" android:layout_width="12dp" android:layout_height="12dp" android:layout_gravity="left|top" android:tint="#ff000000" - android:src="@drawable/rounded" /> + android:src="@drawable/rounded"/> <ImageView android:id="@+id/right" android:layout_width="12dp" android:layout_height="12dp" android:tint="#ff000000" android:layout_gravity="right|bottom" - android:src="@drawable/rounded" /> - <ImageView - android:id="@+id/assist_hint_left" - android:layout_width="32dp" - android:layout_height="32dp" - android:padding="6dp" - android:layout_gravity="left|top" - android:src="@drawable/corner_gesture_hint" - android:tint="#ffffffff" - android:visibility="gone" /> - <ImageView - android:id="@+id/assist_hint_right" - android:layout_width="32dp" - android:layout_height="32dp" - android:padding="6dp" - android:layout_gravity="right|bottom" - android:src="@drawable/corner_gesture_hint" - android:tint="#ffffffff" - android:visibility="gone" /> + android:src="@drawable/rounded"/> </com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_notification_section_header.xml b/packages/SystemUI/res/layout/status_bar_notification_section_header.xml index 0c3c597a7b3a..d3eb9aeb1710 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_section_header.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_section_header.xml @@ -44,6 +44,8 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginStart="@dimen/notification_section_header_padding_left" + android:gravity="start" + android:textAlignment="gravity" android:text="@string/notification_section_header_gentle" android:textSize="12sp" android:textColor="@color/notification_section_header_label_color" diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index dd3073fb5e65..c5f4052b0e89 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -31,7 +31,10 @@ <color name="notification_divider_color">#212121</color> <!-- The background color of the notification shade --> - <color name="notification_shade_background_color">#181818</color> + <color name="notification_shade_background_color">@color/GM2_grey_900</color> + + <!-- The color of the gear shown behind a notification --> + <color name="notification_gear_color">@color/GM2_grey_500</color> <!-- The color of the ripples on the untinted notifications --> <color name="notification_ripple_untinted_color">#30ffffff</color> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 3f84b32ee0c2..e7a1a660abc2 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -74,7 +74,7 @@ <color name="notification_divider_color">#FF616161</color> <!-- The background color of the notification shade --> - <color name="notification_shade_background_color">#ffeeeeee</color> + <color name="notification_shade_background_color">@color/GM2_grey_200</color> <!-- The color of the ripples on the untinted notifications --> <color name="notification_ripple_untinted_color">#28000000</color> @@ -83,7 +83,7 @@ <color name="notification_ripple_tinted_color">#30ffffff</color> <!-- The color of the gear shown behind a notification --> - <color name="notification_gear_color">#ff757575</color> + <color name="notification_gear_color">@color/GM2_grey_700</color> <!-- The color of the text inside a notification --> <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_light</color> @@ -166,14 +166,17 @@ <color name="smart_reply_button_stroke">#ffdadce0</color> <!-- Biometric dialog colors --> - <color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black --> + <color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black --> <color name="biometric_dialog_gray">#ff757575</color> - <color name="biometric_dialog_accent">#ff008577</color> <!-- dark teal --> - <color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 --> + <color name="biometric_dialog_accent">#ff008577</color> <!-- dark teal --> + <color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 --> <!-- Logout button --> <color name="logout_button_bg_color">#ccffffff</color> + <!-- Color for the Assistant invocation lights --> + <color name="default_invocation_lights_color">#ffffffff</color> <!-- white --> + <!-- GM2 colors --> <color name="GM2_grey_50">#F8F9FA</color> <color name="GM2_grey_100">#F1F3F4</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c04d28a6cda8..c5e4662f6d45 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -57,7 +57,7 @@ <item name="navigation_luminance_change_threshold" type="dimen" format="float">0.05</item> <dimen name="floating_rotation_button_diameter">40dp</dimen> - <dimen name="floating_rotation_button_margin">4dp</dimen> + <dimen name="floating_rotation_button_min_margin">4dp</dimen> <!-- Height of notification icons in the status bar --> <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8bc84c6d4a44..8174434cb51d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2459,4 +2459,11 @@ <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string> <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=20] --> <string name="bubble_dismiss_text">Dismiss</string> + + <!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] --> + <string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string> + + <!-- Notification content text when switching to a default launcher that supports gesture navigation [CHAR LIMIT=NONE] --> + <string name="notification_content_gesture_nav_available">Go to Settings to update system navigation</string> + </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java index 6567b6ad1171..2b1fce8a4cf5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java @@ -18,7 +18,6 @@ package com.android.systemui.shared.system; import android.os.Bundle; import android.os.Looper; -import android.util.Pair; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; import android.view.InputChannel; @@ -42,19 +41,6 @@ public class InputChannelCompat { } /** - * Creates a dispatcher and receiver pair to better handle events across threads. - */ - public static Pair<InputEventDispatcher, InputEventReceiver> createPair(String name, - Looper looper, Choreographer choreographer, InputEventListener listener) { - InputChannel[] channels = InputChannel.openInputChannelPair(name); - - InputEventDispatcher dispatcher = new InputEventDispatcher(channels[0], looper); - InputEventReceiver receiver = new InputEventReceiver(channels[1], looper, choreographer, - listener); - return Pair.create(dispatcher, receiver); - } - - /** * Creates a dispatcher from the extras received as part on onInitialize */ public static InputEventReceiver fromBundle(Bundle params, String key, diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SystemGestureExclusionListenerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SystemGestureExclusionListenerCompat.java new file mode 100644 index 000000000000..9fdecfbd44a0 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SystemGestureExclusionListenerCompat.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.system; + +import android.graphics.Region; +import android.os.RemoteException; +import android.util.Log; +import android.view.ISystemGestureExclusionListener; +import android.view.WindowManagerGlobal; + +/** + * Utility class to listen for exclusion rect changes. + */ +public abstract class SystemGestureExclusionListenerCompat { + + private static final String TAG = "SGEListenerCompat"; + + private final int mDisplayId; + + private ISystemGestureExclusionListener mGestureExclusionListener = + new ISystemGestureExclusionListener.Stub() { + @Override + public void onSystemGestureExclusionChanged(int displayId, + Region systemGestureExclusion) { + if (displayId == mDisplayId) { + onExclusionChanged(systemGestureExclusion); + } + } + }; + private boolean mRegistered; + + public SystemGestureExclusionListenerCompat(int displayId) { + mDisplayId = displayId; + } + + /** + * Called when the exclusion region has changed + */ + public abstract void onExclusionChanged(Region systemGestureExclusion); + + /** + * Registers the listener for getting exclusion rect changes. + */ + public void register() { + if (!mRegistered) { + try { + WindowManagerGlobal.getWindowManagerService() + .registerSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + mRegistered = true; + } catch (RemoteException e) { + Log.e(TAG, "Failed to register window manager callbacks", e); + } + } + } + + /** + * Unregisters the receiver if previously registered + */ + public void unregister() { + if (mRegistered) { + try { + WindowManagerGlobal.getWindowManagerService() + .unregisterSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + mRegistered = false; + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister window manager callbacks", e); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index de4c79839f25..bce5c23bc9cc 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -322,9 +322,6 @@ public class BatteryMeterView extends LinearLayout implements mCharging = pluggedIn; mLevel = level; updatePercentText(); - setContentDescription( - getContext().getString(charging ? R.string.accessibility_battery_level_charging - : R.string.accessibility_battery_level, level)); } @Override @@ -358,6 +355,9 @@ public class BatteryMeterView extends LinearLayout implements mBatteryController.getEstimatedTimeRemainingString((String estimate) -> { if (estimate != null) { mBatteryPercentView.setText(estimate); + setContentDescription(getContext().getString( + R.string.battery_low_percent_format_hybrid, mLevel, estimate)); + } else { setPercentTextAtCurrentLevel(); } @@ -371,6 +371,9 @@ public class BatteryMeterView extends LinearLayout implements private void setPercentTextAtCurrentLevel() { mBatteryPercentView.setText( NumberFormat.getPercentInstance().format(mLevel / 100f)); + setContentDescription( + getContext().getString(mCharging ? R.string.accessibility_battery_level_charging + : R.string.accessibility_battery_level, mLevel)); } private void updateShowPercent() { diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java new file mode 100644 index 000000000000..528db5acc86e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui; + +import android.animation.ArgbEvaluator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.SystemProperties; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.ContextThemeWrapper; +import android.view.View; + +import com.android.settingslib.Utils; + +/** + * CornerHandleView draws an inset arc intended to be displayed within the screen decoration + * corners. + */ +public class CornerHandleView extends View { + private static final boolean ALLOW_TUNING = false; + private static final int ANGLE_DEGREES = 50; + public static final int MARGIN_DP = 11; + public static final int RADIUS_DP = 37; + public static final float STROKE_DP = 2.5f; + + private Paint mPaint; + private int mLightColor; + private int mDarkColor; + private RectF mOval; + + + public CornerHandleView(Context context, AttributeSet attrs) { + super(context, attrs); + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setStrokeWidth(getStrokePx()); + + final int dualToneDarkTheme = Utils.getThemeAttr(mContext, + R.attr.darkIconTheme); + final int dualToneLightTheme = Utils.getThemeAttr(mContext, + R.attr.lightIconTheme); + Context lightContext = new ContextThemeWrapper(mContext, dualToneLightTheme); + Context darkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme); + mLightColor = Utils.getColorAttrDefaultColor(lightContext, + R.attr.singleToneColor); + mDarkColor = Utils.getColorAttrDefaultColor(darkContext, + R.attr.singleToneColor); + + updateOval(); + } + + /** + * Receives an intensity from 0 (lightest) to 1 (darkest) and sets the handle color + * approriately. Intention is to match the home handle color. + */ + public void updateDarkness(float darkIntensity) { + mPaint.setColor((int) ArgbEvaluator.getInstance().evaluate(darkIntensity, + mLightColor, + mDarkColor)); + invalidate(); + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (ALLOW_TUNING) { + mPaint.setStrokeWidth(getStrokePx()); + updateOval(); + } + + canvas.drawArc(mOval, 180 + ((90 - getAngle()) / 2), getAngle(), false, + mPaint); + } + + // TODO(b/133834204): Remove tweaking of corner handles + private static float convertDpToPixel(float dp, Context context) { + return dp * ((float) context.getResources().getDisplayMetrics().densityDpi + / DisplayMetrics.DENSITY_DEFAULT); + } + + private void updateOval() { + mOval = new RectF(getMarginPx() - (getStrokePx() / 2.f), + getMarginPx() - (getStrokePx() / 2.f), + getMarginPx() + (2 * (getRadiusPx()) + (getStrokePx() / 2.f)), + getMarginPx() + 2 * getRadiusPx() + (getStrokePx() / 2.f)); + } + + private int getAngle() { + if (ALLOW_TUNING) { + return SystemProperties.getInt("CORNER_HANDLE_ANGLE_DEGREES", ANGLE_DEGREES); + } else { + return ANGLE_DEGREES; + } + } + + private int getMarginPx() { + if (ALLOW_TUNING) { + return SystemProperties.getInt("CORNER_HANDLE_MARGIN_PX", + (int) convertDpToPixel(MARGIN_DP, getContext())); + } else { + return (int) convertDpToPixel(MARGIN_DP, getContext()); + } + } + + private int getRadiusPx() { + if (ALLOW_TUNING) { + return SystemProperties.getInt("CORNER_HANDLE_RADIUS_PX", + (int) convertDpToPixel(RADIUS_DP, getContext())); + } else { + return (int) convertDpToPixel(RADIUS_DP, getContext()); + } + } + + private int getStrokePx() { + if (ALLOW_TUNING) { + return SystemProperties.getInt("CORNER_HANDLE_STROKE_PX", + (int) convertDpToPixel(STROKE_DP, getContext())); + } else { + return (int) convertDpToPixel(STROKE_DP, getContext()); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 390a9e65c1b4..4aaf85adfce6 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -60,6 +60,12 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.OvershootInterpolator; +import android.view.animation.PathInterpolator; +import android.view.animation.ScaleAnimation; +import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import android.widget.ImageView; @@ -72,6 +78,7 @@ import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.SecureSetting; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; +import com.android.systemui.statusbar.phone.NavigationBarTransitions; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.tuner.TunablePadding; import com.android.systemui.tuner.TunerService; @@ -85,7 +92,8 @@ import java.util.List; * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout) * for antialiasing and emulation purposes. */ -public class ScreenDecorations extends SystemUI implements Tunable { +public class ScreenDecorations extends SystemUI implements Tunable, + NavigationBarTransitions.DarkIntensityListener { private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; @@ -93,13 +101,17 @@ public class ScreenDecorations extends SystemUI implements Tunable { public static final String PADDING = "sysui_rounded_content_padding"; private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS = SystemProperties.getBoolean("debug.screenshot_rounded_corners", false); + private static final boolean VERBOSE = false; private DisplayManager mDisplayManager; private DisplayManager.DisplayListener mDisplayListener; - @VisibleForTesting protected int mRoundedDefault; - @VisibleForTesting protected int mRoundedDefaultTop; - @VisibleForTesting protected int mRoundedDefaultBottom; + @VisibleForTesting + protected int mRoundedDefault; + @VisibleForTesting + protected int mRoundedDefaultTop; + @VisibleForTesting + protected int mRoundedDefaultBottom; private View mOverlay; private View mBottomOverlay; private float mDensity; @@ -111,6 +123,8 @@ public class ScreenDecorations extends SystemUI implements Tunable { private SecureSetting mColorInversionSetting; private boolean mPendingRotationChange; private Handler mHandler; + private boolean mAssistHintBlocked = false; + private boolean mIsReceivingNavBarColor = false; /** * Converts a set of {@link Rect}s into a {@link Region} @@ -137,15 +151,32 @@ public class ScreenDecorations extends SystemUI implements Tunable { putComponent(ScreenDecorations.class, this); } - private void fade(View view, boolean fadeIn) { + private void fade(View view, boolean fadeIn, boolean isLeft) { if (fadeIn) { view.animate().cancel(); - view.setAlpha(0f); + view.setAlpha(1f); view.setVisibility(View.VISIBLE); - view.animate().alpha(1f); + + AnimationSet anim = new AnimationSet(true); + anim.setDuration(900); + + Animation scaleAnimation = new ScaleAnimation(2f, 1f, 2f, 1f, + ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f); + anim.addAnimation(scaleAnimation); + anim.setInterpolator(new PathInterpolator(0.02f, 0.44f, 0.67f, 1.00f)); + + Animation translateAnimation = new TranslateAnimation( + TranslateAnimation.RELATIVE_TO_SELF, isLeft ? -0.2f : 0.2f, + TranslateAnimation.RELATIVE_TO_SELF, + 0f, + TranslateAnimation.RELATIVE_TO_SELF, 0.2f, TranslateAnimation.RELATIVE_TO_SELF, + 0f); + anim.addAnimation(translateAnimation); + anim.setInterpolator(new OvershootInterpolator()); + view.startAnimation(anim); } else { view.animate().cancel(); - view.animate().alpha(0f).withEndAction(() -> view.setVisibility(View.INVISIBLE)); + view.animate().setDuration(400).alpha(0f); } } @@ -161,35 +192,59 @@ public class ScreenDecorations extends SystemUI implements Tunable { return; } + if (mAssistHintBlocked && visible) { + if (VERBOSE) { + Log.v(TAG, "Assist hint blocked, cannot make it visible"); + } + return; + } + if (mAssistHintVisible != visible) { mAssistHintVisible = visible; - View assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left); - View assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right); - View assistHintBottomLeft = mBottomOverlay.findViewById(R.id.assist_hint_left); - View assistHintBottomRight = mBottomOverlay.findViewById(R.id.assist_hint_right); + CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left); + CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right); + CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById( + R.id.assist_hint_left); + CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById( + R.id.assist_hint_right); switch (mRotation) { case RotationUtils.ROTATION_NONE: - fade(assistHintBottomLeft, mAssistHintVisible); - fade(assistHintBottomRight, mAssistHintVisible); + fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true); + fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false); break; case RotationUtils.ROTATION_LANDSCAPE: - fade(assistHintTopRight, mAssistHintVisible); - fade(assistHintBottomRight, mAssistHintVisible); + fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true); + fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false); break; case RotationUtils.ROTATION_SEASCAPE: - fade(assistHintTopLeft, mAssistHintVisible); - fade(assistHintBottomLeft, mAssistHintVisible); + fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false); + fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true); break; case RotationUtils.ROTATION_UPSIDE_DOWN: - fade(assistHintTopLeft, mAssistHintVisible); - fade(assistHintTopRight, mAssistHintVisible); + fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false); + fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true); break; } } } + /** + * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true. + */ + public void setAssistHintBlocked(boolean blocked) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.post(() -> setAssistHintBlocked(blocked)); + return; + } + + mAssistHintBlocked = blocked; + if (mAssistHintVisible && mAssistHintBlocked) { + setAssistHintVisible(false); + } + } + @VisibleForTesting Handler startHandlerThread() { HandlerThread thread = new HandlerThread("ScreenDecorations"); @@ -253,12 +308,12 @@ public class ScreenDecorations extends SystemUI implements Tunable { .inflate(R.layout.rounded_corners, null); mCutoutTop = new DisplayCutoutView(mContext, true, this::updateWindowVisibilities, this); - ((ViewGroup)mOverlay).addView(mCutoutTop); + ((ViewGroup) mOverlay).addView(mCutoutTop); mBottomOverlay = LayoutInflater.from(mContext) .inflate(R.layout.rounded_corners, null); mCutoutBottom = new DisplayCutoutView(mContext, false, this::updateWindowVisibilities, this); - ((ViewGroup)mBottomOverlay).addView(mCutoutBottom); + ((ViewGroup) mBottomOverlay).addView(mCutoutBottom); mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); mOverlay.setAlpha(0); @@ -378,10 +433,26 @@ public class ScreenDecorations extends SystemUI implements Tunable { if (mOverlay != null) { updateLayoutParams(); updateViews(); + if (mAssistHintVisible) { + // If assist handles are visible, hide them without animation and then make them + // show once again (with corrected rotation). + hideAssistHandles(); + setAssistHintVisible(true); + } } } } + private void hideAssistHandles() { + if (mOverlay != null && mBottomOverlay != null) { + mOverlay.findViewById(R.id.assist_hint_left).setVisibility(View.GONE); + mOverlay.findViewById(R.id.assist_hint_right).setVisibility(View.GONE); + mBottomOverlay.findViewById(R.id.assist_hint_left).setVisibility(View.GONE); + mBottomOverlay.findViewById(R.id.assist_hint_right).setVisibility(View.GONE); + mAssistHintVisible = false; + } + } + private void updateRoundedCornerRadii() { final int newRoundedDefault = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.rounded_corner_radius); @@ -416,7 +487,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) { updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0); updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270); - updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);; + updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90); updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180); } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) { updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270); @@ -477,7 +548,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { } private void updateView(View v, int gravity, int rotation) { - ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity; + ((FrameLayout.LayoutParams) v.getLayoutParams()).gravity = gravity; v.setRotation(rotation); } @@ -613,6 +684,10 @@ public class ScreenDecorations extends SystemUI implements Tunable { setSize(mOverlay.findViewById(R.id.right), sizeTop); setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom); setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom); + setSize(mOverlay.findViewById(R.id.assist_hint_left), sizeTop * 2); + setSize(mOverlay.findViewById(R.id.assist_hint_right), sizeTop * 2); + setSize(mBottomOverlay.findViewById(R.id.assist_hint_left), sizeBottom * 2); + setSize(mBottomOverlay.findViewById(R.id.assist_hint_right), sizeBottom * 2); } }); } @@ -624,6 +699,27 @@ public class ScreenDecorations extends SystemUI implements Tunable { view.setLayoutParams(params); } + @Override + public void onDarkIntensity(float darkIntensity) { + if (mOverlay != null) { + CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left); + CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right); + + assistHintTopLeft.updateDarkness(darkIntensity); + assistHintTopRight.updateDarkness(darkIntensity); + } + + if (mBottomOverlay != null) { + CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById( + R.id.assist_hint_left); + CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById( + R.id.assist_hint_right); + + assistHintBottomLeft.updateDarkness(darkIntensity); + assistHintBottomRight.updateDarkness(darkIntensity); + } + } + @VisibleForTesting static class TunablePaddingTagListener implements FragmentListener { diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 3fc6689b2f19..49bd5bd09220 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -100,6 +100,13 @@ public class AppOpsControllerImpl implements AppOpsController, } else { mAppOps.stopWatchingActive(this); mAppOps.stopWatchingNoted(this); + mBGHandler.removeCallbacksAndMessages(null); // null removes all + synchronized (mActiveItems) { + mActiveItems.clear(); + } + synchronized (mNotedItems) { + mNotedItems.clear(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java index e2c731314246..2eda3d784382 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java @@ -16,31 +16,10 @@ package com.android.systemui.assist; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; - public enum AssistHandleBehavior { - TEST(new AssistHandleOffBehavior()), - OFF(new AssistHandleOffBehavior()), - LIKE_HOME(new AssistHandleLikeHomeBehavior()), - REMINDER_EXP(new AssistHandleReminderExpBehavior()); - - private BehaviorController mController; - - AssistHandleBehavior(BehaviorController controller) { - mController = controller; - } - - BehaviorController getController() { - return mController; - } - - @VisibleForTesting - void setTestController(BehaviorController controller) { - if (this.equals(TEST)) { - mController = controller; - } - } + TEST, + OFF, + LIKE_HOME, + REMINDER_EXP; } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java index 0203d9ed2376..01deb03c466b 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java @@ -16,24 +16,27 @@ package com.android.systemui.assist; -import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; import android.os.Handler; import android.os.SystemClock; -import android.os.SystemProperties; +import android.provider.DeviceConfig; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.AssistUtils; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; import com.android.systemui.ScreenDecorations; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.NavigationModeController; -import java.util.Locale; +import java.util.EnumMap; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -46,60 +49,76 @@ import java.util.function.Supplier; public final class AssistHandleBehaviorController implements AssistHandleCallbacks { private static final String TAG = "AssistHandleBehavior"; - private static final boolean IS_DEBUG_DEVICE = - Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") - || Build.TYPE.toLowerCase(Locale.ROOT).equals("eng"); - private static final String SHOWN_FREQUENCY_THRESHOLD_KEY = - "ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS"; private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10); - private static final String SHOW_AND_GO_DURATION_KEY = "ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS"; private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3); - private static final String BEHAVIOR_KEY = "behavior"; - private static final String SET_BEHAVIOR_ACTION = - "com.android.systemui.SET_ASSIST_HANDLE_BEHAVIOR"; + + /** + * This is the default behavior that will be used once the system is up. It will be set once the + * behavior dependencies are available. This ensures proper behavior lifecycle. + */ + private static final AssistHandleBehavior DEFAULT_BEHAVIOR = AssistHandleBehavior.REMINDER_EXP; private final Context mContext; + private final AssistUtils mAssistUtils; private final Handler mHandler; private final Runnable mHideHandles = this::hideHandles; private final Supplier<ScreenDecorations> mScreenDecorationsSupplier; + private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap = + new EnumMap<>(AssistHandleBehavior.class); private boolean mHandlesShowing = false; private long mHandlesLastHiddenAt; + /** + * This should always be initialized as {@link AssistHandleBehavior#OFF} to ensure proper + * behavior lifecycle. + */ private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF; private boolean mInGesturalMode; - AssistHandleBehaviorController(Context context, Handler handler) { - this(context, handler, () -> - SysUiServiceProvider.getComponent(context, ScreenDecorations.class)); + AssistHandleBehaviorController(Context context, AssistUtils assistUtils, Handler handler) { + this( + context, + assistUtils, + handler, () -> SysUiServiceProvider.getComponent(context, ScreenDecorations.class), + /* testBehavior = */ null); } @VisibleForTesting AssistHandleBehaviorController( Context context, + AssistUtils assistUtils, Handler handler, - Supplier<ScreenDecorations> screenDecorationsSupplier) { + Supplier<ScreenDecorations> screenDecorationsSupplier, + @Nullable BehaviorController testBehavior) { mContext = context; + mAssistUtils = assistUtils; mHandler = handler; mScreenDecorationsSupplier = screenDecorationsSupplier; + mBehaviorMap.put(AssistHandleBehavior.OFF, new AssistHandleOffBehavior()); + mBehaviorMap.put(AssistHandleBehavior.LIKE_HOME, new AssistHandleLikeHomeBehavior()); + mBehaviorMap.put(AssistHandleBehavior.REMINDER_EXP, new AssistHandleReminderExpBehavior()); + if (testBehavior != null) { + mBehaviorMap.put(AssistHandleBehavior.TEST, testBehavior); + } + mInGesturalMode = QuickStepContract.isGesturalMode( Dependency.get(NavigationModeController.class) .addListener(this::handleNavigationModeChange)); - if (IS_DEBUG_DEVICE) { - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String behaviorString = intent.getExtras().getString(BEHAVIOR_KEY); - try { - setBehavior(AssistHandleBehavior.valueOf(behaviorString)); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Invalid behavior identifier: " + behaviorString); + setBehavior(DeviceConfig.getString( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, + DEFAULT_BEHAVIOR.toString())); + DeviceConfig.addOnPropertyChangedListener( + DeviceConfig.NAMESPACE_SYSTEMUI, + mHandler::post, + (namespace, name, value) -> { + if (SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE.equals(name)) { + setBehavior(value); } - } - }, new IntentFilter(SET_BEHAVIOR_ACTION)); - } + }); } @Override @@ -123,26 +142,56 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true)); } + void onAssistantGesturePerformed() { + mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed(); + } + void setBehavior(AssistHandleBehavior behavior) { if (mCurrentBehavior == behavior) { return; } + if (!mBehaviorMap.containsKey(behavior)) { + Log.e(TAG, "Unsupported behavior requested: " + behavior.toString()); + return; + } + if (mInGesturalMode) { - mCurrentBehavior.getController().onModeDeactivated(); - behavior.getController().onModeActivated(mContext, this); + mBehaviorMap.get(mCurrentBehavior).onModeDeactivated(); + mBehaviorMap.get(behavior).onModeActivated(mContext, /* callbacks = */ this); } mCurrentBehavior = behavior; } - private static long getShownFrequencyThreshold() { - return SystemProperties.getLong( - SHOWN_FREQUENCY_THRESHOLD_KEY, DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS); + private void setBehavior(@Nullable String behavior) { + try { + setBehavior(AssistHandleBehavior.valueOf(behavior)); + } catch (IllegalArgumentException | NullPointerException e) { + Log.e(TAG, "Invalid behavior: " + behavior, e); + } + } + + private boolean handlesUnblocked(boolean ignoreThreshold) { + long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt; + boolean notThrottled = ignoreThreshold || timeSinceHidden > getShownFrequencyThreshold(); + ComponentName assistantComponent = + mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser()); + return notThrottled && assistantComponent != null; + } + + private long getShownFrequencyThreshold() { + return DeviceConfig.getLong( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS, + DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS); } - private static long getShowAndGoDuration() { - return SystemProperties.getLong(SHOW_AND_GO_DURATION_KEY, DEFAULT_SHOW_AND_GO_DURATION_MS); + private long getShowAndGoDuration() { + return DeviceConfig.getLong( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS, + DEFAULT_SHOW_AND_GO_DURATION_MS); } private void maybeShowHandles(boolean ignoreThreshold) { @@ -150,8 +199,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac return; } - long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt; - if (ignoreThreshold || timeSinceHidden > getShownFrequencyThreshold()) { + if (handlesUnblocked(ignoreThreshold)) { mHandlesShowing = true; ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get(); if (screenDecorations == null) { @@ -185,9 +233,9 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac mInGesturalMode = inGesturalMode; if (mInGesturalMode) { - mCurrentBehavior.getController().onModeActivated(mContext, this); + mBehaviorMap.get(mCurrentBehavior).onModeActivated(mContext, /* callbacks = */ this); } else { - mCurrentBehavior.getController().onModeDeactivated(); + mBehaviorMap.get(mCurrentBehavior).onModeDeactivated(); hide(); } } @@ -199,6 +247,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac interface BehaviorController { void onModeActivated(Context context, AssistHandleCallbacks callbacks); - void onModeDeactivated(); + default void onModeDeactivated() {} + default void onAssistantGesturePerformed() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java index e89e93c0adf0..05e504c93b82 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java @@ -16,17 +16,15 @@ package com.android.systemui.assist; -import android.app.StatusBarManager; import android.content.Context; import androidx.annotation.Nullable; import com.android.systemui.Dependency; -import com.android.systemui.SysUiServiceProvider; import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.phone.NavigationBarFragment; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.shared.system.QuickStepContract; /** * Assistant Handle behavior that makes Assistant handles show/hide when the home handle is @@ -34,47 +32,68 @@ import com.android.systemui.statusbar.phone.NavigationBarFragment; */ final class AssistHandleLikeHomeBehavior implements BehaviorController { - private final CommandQueue.Callbacks mCallbacks = new CommandQueue.Callbacks() { + private final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onDozingChanged(boolean isDozing) { + handleDozingChanged(isDozing); + } + }; + private final OverviewProxyService.OverviewProxyListener mOverviewProxyListener = + new OverviewProxyService.OverviewProxyListener() { @Override - public void setWindowState(int displayId, int window, int state) { - if (mNavBarDisplayId == displayId - && window == StatusBarManager.WINDOW_NAVIGATION_BAR) { - handleWindowStateChanged(state); - } + public void onSystemUiStateChanged(int sysuiStateFlags) { + handleSystemUiStateChange(sysuiStateFlags); } }; + private final StatusBarStateController mStatusBarStateController; + private final OverviewProxyService mOverviewProxyService; - private CommandQueue mCommandQueue; - private int mNavBarDisplayId; - private boolean mIsNavBarWindowVisible; + private boolean mIsDozing; + private boolean mIsHomeHandleHiding; @Nullable private AssistHandleCallbacks mAssistHandleCallbacks; + AssistHandleLikeHomeBehavior() { + mStatusBarStateController = Dependency.get(StatusBarStateController.class); + mOverviewProxyService = Dependency.get(OverviewProxyService.class); + } + @Override public void onModeActivated(Context context, AssistHandleCallbacks callbacks) { mAssistHandleCallbacks = callbacks; - NavigationBarFragment navigationBarFragment = - Dependency.get(NavigationBarController.class).getDefaultNavigationBarFragment(); - mNavBarDisplayId = navigationBarFragment.mDisplayId; - mIsNavBarWindowVisible = navigationBarFragment.isNavBarWindowVisible(); - mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class); - mCommandQueue.addCallback(mCallbacks); + mIsDozing = mStatusBarStateController.isDozing(); + mStatusBarStateController.addCallback(mStatusBarStateListener); + mOverviewProxyService.addCallback(mOverviewProxyListener); callbackForCurrentState(); } @Override public void onModeDeactivated() { mAssistHandleCallbacks = null; - mCommandQueue.removeCallback(mCallbacks); + mOverviewProxyService.removeCallback(mOverviewProxyListener); + } + + private static boolean isHomeHandleHiding(int sysuiStateFlags) { + return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0; + } + + private void handleDozingChanged(boolean isDozing) { + if (mIsDozing == isDozing) { + return; + } + + mIsDozing = isDozing; + callbackForCurrentState(); } - private void handleWindowStateChanged(int state) { - boolean newVisibility = state == StatusBarManager.WINDOW_STATE_SHOWING; - if (mIsNavBarWindowVisible == newVisibility) { + private void handleSystemUiStateChange(int sysuiStateFlags) { + boolean isHomeHandleHiding = isHomeHandleHiding(sysuiStateFlags); + if (mIsHomeHandleHiding == isHomeHandleHiding) { return; } - mIsNavBarWindowVisible = newVisibility; + mIsHomeHandleHiding = isHomeHandleHiding; callbackForCurrentState(); } @@ -83,10 +102,10 @@ final class AssistHandleLikeHomeBehavior implements BehaviorController { return; } - if (mIsNavBarWindowVisible) { - mAssistHandleCallbacks.showAndStay(); - } else { + if (mIsHomeHandleHiding || mIsDozing) { mAssistHandleCallbacks.hide(); + } else { + mAssistHandleCallbacks.showAndStay(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java index ebf15ae8755c..f4130aeb1d08 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java @@ -27,9 +27,4 @@ final class AssistHandleOffBehavior implements BehaviorController { public void onModeActivated(Context context, AssistHandleCallbacks callbacks) { callbacks.hide(); } - - @Override - public void onModeDeactivated() { - // Do nothing - } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java index 4a5e0e85107e..4b6a6dcee91d 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java @@ -16,24 +16,27 @@ package com.android.systemui.assist; +import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; -import android.graphics.Rect; -import android.view.View; -import android.view.WindowManager; +import android.os.SystemClock; +import android.provider.DeviceConfig; +import android.provider.Settings; import androidx.annotation.Nullable; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.Dependency; -import com.android.systemui.SysUiServiceProvider; import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.TaskStackChangeListener; -import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; +import java.util.concurrent.TimeUnit; + /** * Assistant handle behavior that hides the handles when the phone is dozing or in immersive mode, * shows the handles when on lockscreen, and shows the handles temporarily when changing tasks or @@ -41,6 +44,11 @@ import com.android.systemui.statusbar.StatusBarState; */ final class AssistHandleReminderExpBehavior implements BehaviorController { + private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed"; + private static final String LEARNING_EVENT_COUNT_KEY = "reminder_exp_learning_event_count"; + private static final long DEFAULT_LEARNING_TIME_MS = TimeUnit.DAYS.toMillis(3); + private static final int DEFAULT_LEARNING_COUNT = 3; + private final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override @@ -65,66 +73,95 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { handleTaskStackTopChanged(taskId); } }; - private final CommandQueue.Callbacks mCallbacks = new CommandQueue.Callbacks() { - @Override - public void setSystemUiVisibility(int displayId, int vis, - int fullscreenStackVis, int dockedStackVis, int mask, - Rect fullscreenStackBounds, Rect dockedStackBounds, - boolean navbarColorManagedByIme) { - if (mStatusBarDisplayId == displayId) { - handleSystemUiVisibilityChange(vis, mask); - } - } - }; private final OverviewProxyService.OverviewProxyListener mOverviewProxyListener = new OverviewProxyService.OverviewProxyListener() { @Override public void onOverviewShown(boolean fromHome) { handleOverviewShown(); } + + @Override + public void onSystemUiStateChanged(int sysuiStateFlags) { + handleSystemUiStateChanged(sysuiStateFlags); + } }; - private StatusBarStateController mStatusBarStateController; - private ActivityManagerWrapper mActivityManagerWrapper; - private OverviewProxyService mOverviewProxyService; - private int mStatusBarDisplayId; - private CommandQueue mCommandQueue; + private final StatusBarStateController mStatusBarStateController; + private final ActivityManagerWrapper mActivityManagerWrapper; + private final OverviewProxyService mOverviewProxyService; + private boolean mOnLockscreen; private boolean mIsDozing; private int mRunningTaskId; - private boolean mIsImmersive; + private boolean mIsNavBarHidden; + /** Whether user has learned the gesture. */ + private boolean mIsLearned; + private long mLastLearningTimestamp; + /** Uptime while in this behavior. */ + private long mLearningTimeElapsed; + /** Number of successful Assistant invocations while in this behavior. */ + private int mLearningCount; + + @Nullable private Context mContext; @Nullable private AssistHandleCallbacks mAssistHandleCallbacks; + AssistHandleReminderExpBehavior() { + mStatusBarStateController = Dependency.get(StatusBarStateController.class); + mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); + mOverviewProxyService = Dependency.get(OverviewProxyService.class); + } + @Override public void onModeActivated(Context context, AssistHandleCallbacks callbacks) { + mContext = context; mAssistHandleCallbacks = callbacks; - mStatusBarStateController = Dependency.get(StatusBarStateController.class); mOnLockscreen = onLockscreen(mStatusBarStateController.getState()); mIsDozing = mStatusBarStateController.isDozing(); mStatusBarStateController.addCallback(mStatusBarStateListener); - mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); - mRunningTaskId = mActivityManagerWrapper.getRunningTask().taskId; + ActivityManager.RunningTaskInfo runningTaskInfo = mActivityManagerWrapper.getRunningTask(); + mRunningTaskId = runningTaskInfo == null ? 0 : runningTaskInfo.taskId; mActivityManagerWrapper.registerTaskStackListener(mTaskStackChangeListener); - mStatusBarDisplayId = - ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) - .getDefaultDisplay().getDisplayId(); - mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class); - mCommandQueue.addCallback(mCallbacks); - mOverviewProxyService = Dependency.get(OverviewProxyService.class); mOverviewProxyService.addCallback(mOverviewProxyListener); - callbackForCurrentState(); + + mLearningTimeElapsed = Settings.Secure.getLong( + context.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, /* default = */ 0); + mLearningCount = Settings.Secure.getInt( + context.getContentResolver(), LEARNING_EVENT_COUNT_KEY, /* default = */ 0); + mLastLearningTimestamp = SystemClock.uptimeMillis(); + + callbackForCurrentState(/* justUnlocked = */ false); } @Override public void onModeDeactivated() { mAssistHandleCallbacks = null; + if (mContext != null) { + Settings.Secure.putLong( + mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed); + Settings.Secure.putInt( + mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, mLearningCount); + mContext = null; + } mStatusBarStateController.removeCallback(mStatusBarStateListener); mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackChangeListener); - mCommandQueue.removeCallback(mCallbacks); mOverviewProxyService.removeCallback(mOverviewProxyListener); } + @Override + public void onAssistantGesturePerformed() { + if (mContext == null) { + return; + } + + Settings.Secure.putLong( + mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, ++mLearningCount); + } + + private static boolean isNavBarHidden(int sysuiStateFlags) { + return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0; + } + private void handleStatusBarStateChanged(int newState) { boolean onLockscreen = onLockscreen(newState); if (mOnLockscreen == onLockscreen) { @@ -132,7 +169,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { } mOnLockscreen = onLockscreen; - callbackForCurrentState(); + callbackForCurrentState(!onLockscreen); } private void handleDozingChanged(boolean isDozing) { @@ -141,7 +178,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { } mIsDozing = isDozing; - callbackForCurrentState(); + callbackForCurrentState(/* justUnlocked = */ false); } private void handleTaskStackTopChanged(int taskId) { @@ -150,21 +187,21 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { } mRunningTaskId = taskId; - callbackForCurrentState(); + callbackForCurrentState(/* justUnlocked = */ false); } - private void handleSystemUiVisibilityChange(int vis, int mask) { - boolean isImmersive = isImmersive(vis, mask); - if (mIsImmersive == isImmersive) { + private void handleSystemUiStateChanged(int sysuiStateFlags) { + boolean isNavBarHidden = isNavBarHidden(sysuiStateFlags); + if (mIsNavBarHidden == isNavBarHidden) { return; } - mIsImmersive = isImmersive; - callbackForCurrentState(); + mIsNavBarHidden = isNavBarHidden; + callbackForCurrentState(/* justUnlocked = */ false); } private void handleOverviewShown() { - callbackForCurrentState(); + callbackForCurrentState(/* justUnlocked = */ false); } private boolean onLockscreen(int statusBarState) { @@ -172,17 +209,34 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { || statusBarState == StatusBarState.SHADE_LOCKED; } - private boolean isImmersive(int vis, int mask) { - return ((vis & mask) - & (View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)) != 0; + private void callbackForCurrentState(boolean justUnlocked) { + updateLearningStatus(); + + if (mIsLearned) { + callbackForLearnedState(justUnlocked); + } else { + callbackForUnlearnedState(); + } + } + + private void callbackForLearnedState(boolean justUnlocked) { + if (mAssistHandleCallbacks == null) { + return; + } + + if (mIsDozing || mIsNavBarHidden || mOnLockscreen) { + mAssistHandleCallbacks.hide(); + } else if (justUnlocked) { + mAssistHandleCallbacks.showAndGo(); + } } - private void callbackForCurrentState() { + private void callbackForUnlearnedState() { if (mAssistHandleCallbacks == null) { return; } - if (mIsDozing || mIsImmersive) { + if (mIsDozing || mIsNavBarHidden) { mAssistHandleCallbacks.hide(); } else if (mOnLockscreen) { mAssistHandleCallbacks.showAndStay(); @@ -190,4 +244,33 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { mAssistHandleCallbacks.showAndGo(); } } + + private void updateLearningStatus() { + if (mContext == null) { + return; + } + + long currentTimestamp = SystemClock.uptimeMillis(); + mLearningTimeElapsed += currentTimestamp - mLastLearningTimestamp; + mLastLearningTimestamp = currentTimestamp; + Settings.Secure.putLong( + mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed); + + mIsLearned = + mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs(); + } + + private long getLearningTimeMs() { + return DeviceConfig.getLong( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS, + DEFAULT_LEARNING_TIME_MS); + } + + private int getLearningCount() { + return DeviceConfig.getInt( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_COUNT, + DEFAULT_LEARNING_COUNT); + } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 2c38e513d7de..2fc79d618546 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -40,8 +40,11 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.ConfigurationChangedReceiver; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.assist.ui.DefaultUiController; +import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -50,6 +53,40 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; */ public class AssistManager implements ConfigurationChangedReceiver { + /** + * Controls the UI for showing Assistant invocation progress. + */ + public interface UiController { + /** + * Updates the invocation progress. + * + * @param type one of INVOCATION_TYPE_GESTURE, INVOCATION_TYPE_ACTIVE_EDGE, + * INVOCATION_TYPE_VOICE, INVOCATION_TYPE_QUICK_SEARCH_BAR, + * INVOCATION_HOME_BUTTON_LONG_PRESS + * @param progress a float between 0 and 1 inclusive. 0 represents the beginning of the + * gesture; 1 represents the end. + */ + void onInvocationProgress(int type, float progress); + + /** + * Called when an invocation gesture completes. + * + * @param velocity the speed of the invocation gesture, in pixels per millisecond. For + * drags, this is 0. + */ + void onGestureCompletion(float velocity); + + /** + * Called with the Bundle from VoiceInteractionSessionListener.onSetUiHints. + */ + void processBundle(Bundle hints); + + /** + * Hides the UI. + */ + void hide(); + } + private static final String TAG = "AssistManager"; // Note that VERBOSE logging may leak PII (e.g. transcription contents). @@ -76,6 +113,7 @@ public class AssistManager implements ConfigurationChangedReceiver { private final InterestingConfigChanges mInterestingConfigChanges; private final PhoneStateMonitor mPhoneStateMonitor; private final AssistHandleBehaviorController mHandleController; + private final UiController mUiController; private AssistOrbContainer mView; private final DeviceProvisionedController mDeviceProvisionedController; @@ -85,16 +123,16 @@ public class AssistManager implements ConfigurationChangedReceiver { private IVoiceInteractionSessionShowCallback mShowCallback = new IVoiceInteractionSessionShowCallback.Stub() { - @Override - public void onFailed() throws RemoteException { - mView.post(mHideRunnable); - } + @Override + public void onFailed() throws RemoteException { + mView.post(mHideRunnable); + } - @Override - public void onShown() throws RemoteException { - mView.post(mHideRunnable); - } - }; + @Override + public void onShown() throws RemoteException { + mView.post(mHideRunnable); + } + }; private Runnable mHideRunnable = new Runnable() { @Override @@ -111,7 +149,8 @@ public class AssistManager implements ConfigurationChangedReceiver { mAssistUtils = new AssistUtils(context); mAssistDisclosure = new AssistDisclosure(context, new Handler()); mPhoneStateMonitor = new PhoneStateMonitor(context); - mHandleController = new AssistHandleBehaviorController(context, new Handler()); + mHandleController = + new AssistHandleBehaviorController(context, mAssistUtils, new Handler()); registerVoiceInteractionSessionListener(); mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION @@ -119,6 +158,23 @@ public class AssistManager implements ConfigurationChangedReceiver { | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); onConfigurationChanged(context.getResources().getConfiguration()); mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic(); + + mUiController = new DefaultUiController(mContext); + + OverviewProxyService overviewProxy = Dependency.get(OverviewProxyService.class); + overviewProxy.addCallback(new OverviewProxyService.OverviewProxyListener() { + @Override + public void onAssistantProgress(float progress) { + // Progress goes from 0 to 1 to indicate how close the assist gesture is to + // completion. + onInvocationProgress(INVOCATION_TYPE_GESTURE, progress); + } + + @Override + public void onAssistantGestureCompletion(float velocity) { + onGestureCompletion(velocity); + } + }); } protected void registerVoiceInteractionSessionListener() { @@ -191,26 +247,32 @@ public class AssistManager implements ConfigurationChangedReceiver { if (args == null) { args = new Bundle(); } + int invocationType = args.getInt(INVOCATION_TYPE_KEY, 0); + if (invocationType == INVOCATION_TYPE_GESTURE) { + mHandleController.onAssistantGesturePerformed(); + } args.putInt(INVOCATION_PHONE_STATE_KEY, mPhoneStateMonitor.getPhoneState()); args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.uptimeMillis()); // Logs assistant start with invocation type. MetricsLogger.action( new LogMaker(MetricsEvent.ASSISTANT) - .setType(MetricsEvent.TYPE_OPEN).setSubtype(args.getInt(INVOCATION_TYPE_KEY))); + .setType(MetricsEvent.TYPE_OPEN).setSubtype( + invocationType)); startAssistInternal(args, assistComponent, isService); } /** Called when the user is performing an assistant invocation action (e.g. Active Edge) */ public void onInvocationProgress(int type, float progress) { - // intentional no-op, vendor's AssistManager implementation should override if needed. + mUiController.onInvocationProgress(type, progress); } - /** Called when the user has invoked the assistant with the incoming velocity, in pixels per + /** + * Called when the user has invoked the assistant with the incoming velocity, in pixels per * millisecond. For invocations without a velocity (e.g. slow drag), the velocity is set to * zero. */ - public void onAssistantGestureCompletion(float velocity) { - // intentional no-op, vendor's AssistManager implementation should override if needed. + public void onGestureCompletion(float velocity) { + mUiController.onGestureCompletion(velocity); } public void hideAssist() { @@ -264,7 +326,7 @@ public class AssistManager implements ConfigurationChangedReceiver { Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0; final SearchManager searchManager = - (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); if (searchManager == null) { return; } @@ -329,7 +391,7 @@ public class AssistManager implements ConfigurationChangedReceiver { // Look for the search icon specified in the activity meta-data Bundle metaData = isService ? packageManager.getServiceInfo( - component, PackageManager.GET_META_DATA).metaData + component, PackageManager.GET_META_DATA).metaData : packageManager.getActivityInfo( component, PackageManager.GET_META_DATA).metaData; if (metaData != null) { diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java b/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java new file mode 100644 index 000000000000..162e09e4d23d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist.ui; + +import android.graphics.Path; + +/** + * Describes paths for circular rounded device corners. + */ +public final class CircularCornerPathRenderer extends CornerPathRenderer { + + private final int mCornerRadiusBottom; + private final int mCornerRadiusTop; + private final int mHeight; + private final int mWidth; + private final Path mPath = new Path(); + + public CircularCornerPathRenderer(int cornerRadiusBottom, int cornerRadiusTop, + int width, int height) { + mCornerRadiusBottom = cornerRadiusBottom; + mCornerRadiusTop = cornerRadiusTop; + mHeight = height; + mWidth = width; + } + + @Override // CornerPathRenderer + public Path getCornerPath(Corner corner) { + mPath.reset(); + switch (corner) { + case BOTTOM_LEFT: + mPath.moveTo(0, mHeight - mCornerRadiusBottom); + mPath.arcTo(0, mHeight - mCornerRadiusBottom * 2, mCornerRadiusBottom * 2, mHeight, + 180, -90, true); + break; + case BOTTOM_RIGHT: + mPath.moveTo(mWidth - mCornerRadiusBottom, mHeight); + mPath.arcTo(mWidth - mCornerRadiusBottom * 2, mHeight - mCornerRadiusBottom * 2, + mWidth, mHeight, 90, -90, true); + break; + case TOP_RIGHT: + mPath.moveTo(mWidth, mCornerRadiusTop); + mPath.arcTo(mWidth - mCornerRadiusTop, 0, mWidth, mCornerRadiusTop, 0, -90, true); + break; + case TOP_LEFT: + mPath.moveTo(mCornerRadiusTop, 0); + mPath.arcTo(0, 0, mCornerRadiusTop, mCornerRadiusTop, 270, -90, true); + break; + } + return mPath; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/CornerPathRenderer.java b/packages/SystemUI/src/com/android/systemui/assist/ui/CornerPathRenderer.java new file mode 100644 index 000000000000..2b40e6501fdd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/CornerPathRenderer.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist.ui; + +import android.graphics.Path; +import android.graphics.PointF; + +import java.util.ArrayList; +import java.util.List; + +/** + * Handles paths along device corners. + */ +public abstract class CornerPathRenderer { + + // The maximum delta between the corner curve and points approximating the corner curve. + private static final float ACCEPTABLE_ERROR = 0.1f; + + /** + * For convenience, labels the four device corners. + * + * Corners must be listed in CCW order, otherwise we'll break rotation. + */ + public enum Corner { + BOTTOM_LEFT, + BOTTOM_RIGHT, + TOP_RIGHT, + TOP_LEFT + } + + /** + * Returns the path along the inside of a corner (centered insetAmountPx from the corner's + * edge). + */ + public Path getInsetPath(Corner corner, float insetAmountPx) { + return approximateInnerPath(getCornerPath(corner), -insetAmountPx); + } + + /** + * Returns the path of a corner (centered on the exact corner). Must be implemented by extending + * classes, based on the device-specific rounded corners. A default implementation for circular + * corners is provided by CircularCornerPathRenderer. + */ + public abstract Path getCornerPath(Corner corner); + + private Path approximateInnerPath(Path input, float delta) { + List<PointF> points = shiftBy(getApproximatePoints(input), delta); + return toPath(points); + } + + private ArrayList<PointF> getApproximatePoints(Path path) { + float[] rawInput = path.approximate(ACCEPTABLE_ERROR); + + ArrayList<PointF> output = new ArrayList<>(); + + for (int i = 0; i < rawInput.length; i = i + 3) { + output.add(new PointF(rawInput[i + 1], rawInput[i + 2])); + } + + return output; + } + + private ArrayList<PointF> shiftBy(ArrayList<PointF> input, float delta) { + ArrayList<PointF> output = new ArrayList<>(); + + for (int i = 0; i < input.size(); i++) { + PointF point = input.get(i); + PointF normal = normalAt(input, i); + PointF shifted = + new PointF(point.x + (normal.x * delta), point.y + (normal.y * delta)); + output.add(shifted); + } + return output; + } + + private Path toPath(List<PointF> points) { + Path path = new Path(); + if (points.size() > 0) { + path.moveTo(points.get(0).x, points.get(0).y); + for (PointF point : points.subList(1, points.size())) { + path.lineTo(point.x, point.y); + } + } + return path; + } + + private PointF normalAt(List<PointF> points, int index) { + PointF d1; + if (index == 0) { + d1 = new PointF(0, 0); + } else { + PointF point = points.get(index); + PointF previousPoint = points.get(index - 1); + d1 = new PointF((point.x - previousPoint.x), (point.y - previousPoint.y)); + } + + PointF d2; + if (index == (points.size() - 1)) { + d2 = new PointF(0, 0); + } else { + PointF point = points.get(index); + PointF nextPoint = points.get(index + 1); + d2 = new PointF((nextPoint.x - point.x), (nextPoint.y - point.y)); + } + + return rotate90Ccw(normalize(new PointF(d1.x + d2.x, d1.y + d2.y))); + } + + private PointF rotate90Ccw(PointF input) { + return new PointF(-input.y, input.x); + } + + private float magnitude(PointF point) { + return (float) Math.sqrt((point.x * point.x) + (point.y * point.y)); + } + + private PointF normalize(PointF point) { + float magnitude = magnitude(point); + if (magnitude == 0.f) { + return point; + } + + float normal = 1 / magnitude; + return new PointF((point.x * normal), (point.y * normal)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java new file mode 100644 index 000000000000..b1be811be9cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.ColorInt; +import android.content.Context; +import android.graphics.PixelFormat; +import android.metrics.LogMaker; +import android.os.Bundle; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.ScreenDecorations; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.assist.AssistManager; + +/** + * Default UiController implementation. Shows white edge lights along the bottom of the phone, + * expanding from the corners to meet in the center. + */ +public class DefaultUiController implements AssistManager.UiController { + + private static final String TAG = "DefaultUiController"; + + private static final long ANIM_DURATION_MS = 200; + + protected final FrameLayout mRoot; + + private final WindowManager mWindowManager; + private final WindowManager.LayoutParams mLayoutParams; + private final PathInterpolator mProgressInterpolator = new PathInterpolator(.83f, 0, .84f, 1); + + private boolean mAttached = false; + private boolean mInvocationInProgress = false; + private float mLastInvocationProgress = 0; + + private ValueAnimator mInvocationAnimator = new ValueAnimator(); + private InvocationLightsView mInvocationLightsView; + + public DefaultUiController(Context context) { + mRoot = new FrameLayout(context); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + + mLayoutParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + mLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; + mLayoutParams.gravity = Gravity.BOTTOM; + mLayoutParams.setTitle("Assist"); + + mInvocationLightsView = (InvocationLightsView) + LayoutInflater.from(context).inflate(R.layout.invocation_lights, mRoot, false); + mRoot.addView(mInvocationLightsView); + } + + @Override // AssistManager.UiController + public void processBundle(Bundle bundle) { + Log.e(TAG, "Bundle received but handling is not implemented; ignoring"); + } + + @Override // AssistManager.UiController + public void onInvocationProgress(int type, float progress) { + if (progress == 1) { + animateInvocationCompletion(type, 0); + } else if (progress == 0) { + hide(); + } else { + if (!mInvocationInProgress) { + attach(); + mInvocationInProgress = true; + updateAssistHandleVisibility(); + } + setProgressInternal(type, progress); + } + mLastInvocationProgress = progress; + + // Logs assistant invocation start. + if (!mInvocationInProgress && progress > 0.f) { + MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT) + .setType(MetricsEvent.TYPE_ACTION)); + } + // Logs assistant invocation cancelled. + if (mInvocationInProgress && progress == 0f) { + MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT) + .setType(MetricsEvent.TYPE_DISMISS).setSubtype(0)); + } + } + + @Override // AssistManager.UiController + public void onGestureCompletion(float velocity) { + animateInvocationCompletion(AssistManager.INVOCATION_TYPE_GESTURE, velocity); + } + + @Override // AssistManager.UiController + public void hide() { + Dependency.get(AssistManager.class).hideAssist(); + detach(); + if (mInvocationAnimator.isRunning()) { + mInvocationAnimator.cancel(); + } + mInvocationLightsView.hide(); + mInvocationInProgress = false; + updateAssistHandleVisibility(); + } + + /** + * Sets the colors of the four invocation lights, from left to right. + */ + public void setInvocationColors(@ColorInt int color1, @ColorInt int color2, + @ColorInt int color3, @ColorInt int color4) { + mInvocationLightsView.setColors(color1, color2, color3, color4); + } + + private void updateAssistHandleVisibility() { + ScreenDecorations decorations = SysUiServiceProvider.getComponent(mRoot.getContext(), + ScreenDecorations.class); + decorations.setAssistHintBlocked(mInvocationInProgress); + } + + private void attach() { + if (!mAttached) { + mWindowManager.addView(mRoot, mLayoutParams); + mAttached = true; + } + } + + private void detach() { + if (mAttached) { + mWindowManager.removeViewImmediate(mRoot); + mAttached = false; + } + } + + private void setProgressInternal(int type, float progress) { + mInvocationLightsView.onInvocationProgress( + mProgressInterpolator.getInterpolation(progress)); + } + + private void animateInvocationCompletion(int type, float velocity) { + mInvocationAnimator = ValueAnimator.ofFloat(mLastInvocationProgress, 1); + mInvocationAnimator.setStartDelay(1); + mInvocationAnimator.setDuration(ANIM_DURATION_MS); + mInvocationAnimator.addUpdateListener( + animation -> setProgressInternal(type, (float) animation.getAnimatedValue())); + mInvocationAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mInvocationInProgress = false; + mLastInvocationProgress = 0; + hide(); + } + }); + mInvocationAnimator.start(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java new file mode 100644 index 000000000000..251229f42da3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist.ui; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.Surface; + +/** + * Utility class for determining screen and corner dimensions. + */ +public class DisplayUtils { + /** + * Converts given distance from dp to pixels. + */ + public static int convertDpToPx(float dp, Context context) { + Display d = context.getDisplay(); + + DisplayMetrics dm = new DisplayMetrics(); + d.getRealMetrics(dm); + + return (int) Math.ceil(dp * dm.density); + } + + /** + * The width of the display. + * + * - Not affected by rotation. + * - Includes system decor. + */ + public static int getWidth(Context context) { + Display d = context.getDisplay(); + + DisplayMetrics dm = new DisplayMetrics(); + d.getRealMetrics(dm); + + int rotation = d.getRotation(); + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { + return dm.widthPixels; + } else { + return dm.heightPixels; + } + } + + /** + * The height of the display. + * + * - Not affected by rotation. + * - Includes system decor. + */ + public static int getHeight(Context context) { + Display d = context.getDisplay(); + + DisplayMetrics dm = new DisplayMetrics(); + d.getRealMetrics(dm); + + int rotation = d.getRotation(); + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { + return dm.heightPixels; + } else { + return dm.widthPixels; + } + } + + /** + * Returns the radius of the bottom corners (the distance from the true corner to the point + * where the curve ends), in pixels. + */ + public static int getCornerRadiusBottom(Context context) { + int radius = 0; + + int resourceId = context.getResources().getIdentifier("rounded_corner_radius_bottom", + "dimen", "android"); + if (resourceId > 0) { + radius = context.getResources().getDimensionPixelSize(resourceId); + } + + if (radius == 0) { + radius = getCornerRadiusDefault(context); + } + return radius; + } + + /** + * Returns the radius of the top corners (the distance from the true corner to the point where + * the curve ends), in pixels. + */ + public static int getCornerRadiusTop(Context context) { + int radius = 0; + + int resourceId = context.getResources().getIdentifier("rounded_corner_radius_top", + "dimen", "android"); + if (resourceId > 0) { + radius = context.getResources().getDimensionPixelSize(resourceId); + } + + if (radius == 0) { + radius = getCornerRadiusDefault(context); + } + return radius; + } + + private static int getCornerRadiusDefault(Context context) { + int radius = 0; + + int resourceId = context.getResources().getIdentifier("rounded_corner_radius", "dimen", + "android"); + if (resourceId > 0) { + radius = context.getResources().getDimensionPixelSize(resourceId); + } + return radius; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/EdgeLight.java b/packages/SystemUI/src/com/android/systemui/assist/ui/EdgeLight.java new file mode 100644 index 000000000000..9ae02c5e3104 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/EdgeLight.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist.ui; + +import androidx.annotation.ColorInt; + +/** + * Represents a line drawn on the perimeter of the display. + * + * Offsets and lengths are both normalized to the perimeter of the display – ex. a length of 1 + * is equal to the perimeter of the display. Positions move counter-clockwise as values increase. + * + * If there is no bottom corner radius, the origin is the bottom-left corner. + * If there is a bottom corner radius, the origin is immediately after the bottom corner radius, + * counter-clockwise. + */ +public final class EdgeLight { + @ColorInt + private int mColor; + private float mOffset; + private float mLength; + + /** Copies a list of EdgeLights. */ + public static EdgeLight[] copy(EdgeLight[] array) { + EdgeLight[] copy = new EdgeLight[array.length]; + for (int i = 0; i < array.length; i++) { + copy[i] = new EdgeLight(array[i]); + } + return copy; + } + + public EdgeLight(@ColorInt int color, float offset, float length) { + mColor = color; + mOffset = offset; + mLength = length; + } + + public EdgeLight(EdgeLight sourceLight) { + mColor = sourceLight.getColor(); + mOffset = sourceLight.getOffset(); + mLength = sourceLight.getLength(); + } + + /** Returns the current edge light color. */ + @ColorInt + public int getColor() { + return mColor; + } + + /** Sets the edge light color. */ + public void setColor(@ColorInt int color) { + mColor = color; + } + + /** Returns the edge light length, in units of the total device perimeter. */ + public float getLength() { + return mLength; + } + + /** Sets the edge light length, in units of the total device perimeter. */ + public void setLength(float length) { + mLength = length; + } + + /** + * Returns the current offset, in units of the total device perimeter and measured from the + * bottom-left corner (see class description). + */ + public float getOffset() { + return mOffset; + } + + /** + * Sets the current offset, in units of the total device perimeter and measured from the + * bottom-left corner (see class description). + */ + public void setOffset(float offset) { + mOffset = offset; + } + + /** Returns the center, measured from the bottom-left corner (see class description). */ + public float getCenter() { + return mOffset + (mLength / 2.f); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java new file mode 100644 index 000000000000..de1d7c8c0a04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist.ui; + +import android.annotation.ColorInt; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.util.AttributeSet; +import android.util.Log; +import android.util.MathUtils; +import android.view.View; + +import com.android.systemui.R; + +import java.util.ArrayList; + +/** + * Shows lights at the bottom of the phone, marking the invocation progress. + */ +public class InvocationLightsView extends View { + + private static final String TAG = "InvocationLightsView"; + + private static final int LIGHT_HEIGHT_DP = 3; + // minimum light length as a fraction of the corner length + private static final float MINIMUM_CORNER_RATIO = .6f; + + protected final ArrayList<EdgeLight> mAssistInvocationLights = new ArrayList<>(); + protected final PerimeterPathGuide mGuide; + + private final Paint mPaint = new Paint(); + // Path used to render lights. One instance is used to draw all lights and is cached to avoid + // allocation on each frame. + private final Path mPath = new Path(); + private final int mViewHeight; + + // Allocate variable for screen location lookup to avoid memory alloc onDraw() + private int[] mScreenLocation = new int[2]; + + public InvocationLightsView(Context context) { + this(context, null); + } + + public InvocationLightsView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public InvocationLightsView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public InvocationLightsView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + int strokeWidth = DisplayUtils.convertDpToPx(LIGHT_HEIGHT_DP, context); + mPaint.setStrokeWidth(strokeWidth); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeJoin(Paint.Join.MITER); + mPaint.setAntiAlias(true); + + int cornerRadiusBottom = DisplayUtils.getCornerRadiusBottom(context); + int cornerRadiusTop = DisplayUtils.getCornerRadiusTop(context); + int displayWidth = DisplayUtils.getWidth(context); + int displayHeight = DisplayUtils.getHeight(context); + CircularCornerPathRenderer cornerPathRenderer = new CircularCornerPathRenderer( + cornerRadiusBottom, cornerRadiusTop, displayWidth, displayHeight); + mGuide = new PerimeterPathGuide(context, cornerPathRenderer, + strokeWidth / 2, displayWidth, displayHeight); + + mViewHeight = Math.max(cornerRadiusBottom, cornerRadiusTop); + + @ColorInt int lightColor = getResources().getColor(R.color.default_invocation_lights_color); + for (int i = 0; i < 4; i++) { + mAssistInvocationLights.add(new EdgeLight(lightColor, 0, 0)); + } + } + + /** + * Updates positions of the invocation lights based on the progress (a float between 0 and 1). + * The lights begin at the device corners and expand inward until they meet at the center. + */ + public void onInvocationProgress(float progress) { + if (progress == 0) { + setVisibility(View.GONE); + } else { + float cornerLengthNormalized = + mGuide.getRegionWidth(PerimeterPathGuide.Region.BOTTOM_LEFT); + float arcLengthNormalized = cornerLengthNormalized * MINIMUM_CORNER_RATIO; + float arcOffsetNormalized = (cornerLengthNormalized - arcLengthNormalized) / 2f; + + float minLightLength = arcLengthNormalized / 2; + float maxLightLength = mGuide.getRegionWidth(PerimeterPathGuide.Region.BOTTOM) / 4f; + + float lightLength = MathUtils.lerp(minLightLength, maxLightLength, progress); + + float leftStart = (-cornerLengthNormalized + arcOffsetNormalized) * (1 - progress); + float rightStart = mGuide.getRegionWidth(PerimeterPathGuide.Region.BOTTOM) + + (cornerLengthNormalized - arcOffsetNormalized) * (1 - progress); + + setLight(0, leftStart, lightLength); + setLight(1, leftStart + lightLength, lightLength); + setLight(2, rightStart - (lightLength * 2), lightLength); + setLight(3, rightStart - lightLength, lightLength); + setVisibility(View.VISIBLE); + } + invalidate(); + } + + /** + * Hides and resets the invocation lights. + */ + public void hide() { + setVisibility(GONE); + for (EdgeLight light : mAssistInvocationLights) { + light.setLength(0); + } + } + + /** + * Sets the invocation light colors, from left to right. + */ + public void setColors(@ColorInt int color1, @ColorInt int color2, + @ColorInt int color3, @ColorInt int color4) { + mAssistInvocationLights.get(0).setColor(color1); + mAssistInvocationLights.get(1).setColor(color2); + mAssistInvocationLights.get(2).setColor(color3); + mAssistInvocationLights.get(3).setColor(color4); + } + + @Override + protected void onFinishInflate() { + getLayoutParams().height = mViewHeight; + requestLayout(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + int rotation = getContext().getDisplay().getRotation(); + mGuide.setRotation(rotation); + } + + @Override + protected void onDraw(Canvas canvas) { + // If the view doesn't take up the whole screen, offset the canvas by its translation + // distance such that PerimeterPathGuide's paths are drawn properly based upon the actual + // screen edges. + getLocationOnScreen(mScreenLocation); + canvas.translate(-mScreenLocation[0], -mScreenLocation[1]); + + // if the lights are different colors, the inner ones need to be drawn last and with a + // square cap so that the join between lights is straight + mPaint.setStrokeCap(Paint.Cap.ROUND); + renderLight(mAssistInvocationLights.get(0), canvas); + renderLight(mAssistInvocationLights.get(3), canvas); + + mPaint.setStrokeCap(Paint.Cap.SQUARE); + renderLight(mAssistInvocationLights.get(1), canvas); + renderLight(mAssistInvocationLights.get(2), canvas); + } + + protected void setLight(int index, float offset, float length) { + if (index < 0 || index >= 4) { + Log.w(TAG, "invalid invocation light index: " + index); + } + mAssistInvocationLights.get(index).setOffset(offset); + mAssistInvocationLights.get(index).setLength(length); + } + + private void renderLight(EdgeLight light, Canvas canvas) { + mGuide.strokeSegment(mPath, light.getOffset(), light.getOffset() + light.getLength()); + mPaint.setColor(light.getColor()); + canvas.drawPath(mPath, mPaint); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java b/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java new file mode 100644 index 000000000000..8eea36892aa7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist.ui; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.util.Log; +import android.util.Pair; +import android.view.Surface; + +import androidx.core.math.MathUtils; + +/** + * PerimeterPathGuide establishes a coordinate system for drawing paths along the perimeter of the + * screen. All positions around the perimeter have a coordinate [0, 1). The origin is the bottom + * left corner of the screen, to the right of the curved corner, if any. Coordinates increase + * counter-clockwise around the screen. + * + * Non-square screens require PerimeterPathGuide to be notified when the rotation changes, such that + * it can recompute the edge lengths for the coordinate system. + */ +public class PerimeterPathGuide { + + private static final String TAG = "PerimeterPathGuide"; + + /** + * For convenience, labels sections of the device perimeter. + * + * Must be listed in CCW order. + */ + public enum Region { + BOTTOM, + BOTTOM_RIGHT, + RIGHT, + TOP_RIGHT, + TOP, + TOP_LEFT, + LEFT, + BOTTOM_LEFT + } + + private final int mDeviceWidthPx; + private final int mDeviceHeightPx; + private final int mTopCornerRadiusPx; + private final int mBottomCornerRadiusPx; + + private class RegionAttributes { + public float absoluteLength; + public float normalizedLength; + public float endCoordinate; + public Path path; + } + + // Allocate a Path and PathMeasure for use by intermediate operations that would otherwise have + // to allocate. reset() must be called before using this path, this ensures state from previous + // operations is cleared. + private final Path mScratchPath = new Path(); + private final CornerPathRenderer mCornerPathRenderer; + private final PathMeasure mScratchPathMeasure = new PathMeasure(mScratchPath, false); + private RegionAttributes[] mRegions; + private final int mEdgeInset; + private int mRotation = ROTATION_0; + + public PerimeterPathGuide(Context context, CornerPathRenderer cornerPathRenderer, + int edgeInset, int screenWidth, int screenHeight) { + mCornerPathRenderer = cornerPathRenderer; + mDeviceWidthPx = screenWidth; + mDeviceHeightPx = screenHeight; + mTopCornerRadiusPx = DisplayUtils.getCornerRadiusTop(context); + mBottomCornerRadiusPx = DisplayUtils.getCornerRadiusBottom(context); + mEdgeInset = edgeInset; + + mRegions = new RegionAttributes[8]; + for (int i = 0; i < mRegions.length; i++) { + mRegions[i] = new RegionAttributes(); + } + computeRegions(); + } + + /** + * Sets the rotation. + * + * @param rotation one of Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, + * Surface.ROTATION_270 + */ + public void setRotation(int rotation) { + if (rotation != mRotation) { + switch (rotation) { + case ROTATION_0: + case ROTATION_90: + case ROTATION_180: + case ROTATION_270: + mRotation = rotation; + computeRegions(); + break; + default: + Log.e(TAG, "Invalid rotation provided: " + rotation); + } + } + } + + /** + * Sets path to the section of the perimeter between startCoord and endCoord (measured + * counter-clockwise from the bottom left). + */ + public void strokeSegment(Path path, float startCoord, float endCoord) { + path.reset(); + + startCoord = ((startCoord % 1) + 1) % 1; // Wrap to the range [0, 1). + endCoord = ((endCoord % 1) + 1) % 1; // Wrap to the range [0, 1). + boolean outOfOrder = startCoord > endCoord; + + if (outOfOrder) { + strokeSegmentInternal(path, startCoord, 1f); + startCoord = 0; + } + strokeSegmentInternal(path, startCoord, endCoord); + } + + /** + * Returns the device perimeter in pixels. + */ + public float getPerimeterPx() { + float total = 0; + for (RegionAttributes region : mRegions) { + total += region.absoluteLength; + } + return total; + } + + /** + * Returns the bottom corner radius in pixels. + */ + public float getBottomCornerRadiusPx() { + return mBottomCornerRadiusPx; + } + + /** + * Given a region and a progress value [0,1] indicating the counter-clockwise progress within + * that region, compute the global [0,1) coordinate. + */ + public float getCoord(Region region, float progress) { + RegionAttributes regionAttributes = mRegions[region.ordinal()]; + progress = MathUtils.clamp(progress, 0, 1); + return regionAttributes.endCoordinate - (1 - progress) * regionAttributes.normalizedLength; + } + + /** + * Returns the center of the provided region, relative to the entire perimeter. + */ + public float getRegionCenter(Region region) { + return getCoord(region, 0.5f); + } + + /** + * Returns the width of the provided region, in units relative to the entire perimeter. + */ + public float getRegionWidth(Region region) { + return mRegions[region.ordinal()].normalizedLength; + } + + /** + * Points are expressed in terms of their relative position on the perimeter of the display, + * moving counter-clockwise. This method converts a point to clockwise, assisting use cases + * such as animating to a point clockwise instead of counter-clockwise. + * + * @param point A point in the range from 0 to 1. + * @return A point in the range of -1 to 0 that represents the same location as {@code point}. + */ + public static float makeClockwise(float point) { + return point - 1; + } + + private int getPhysicalCornerRadius(CircularCornerPathRenderer.Corner corner) { + if (corner == CircularCornerPathRenderer.Corner.BOTTOM_LEFT + || corner == CircularCornerPathRenderer.Corner.BOTTOM_RIGHT) { + return mBottomCornerRadiusPx; + } + return mTopCornerRadiusPx; + } + + // Populate mRegions based upon the current rotation value. + private void computeRegions() { + int screenWidth = mDeviceWidthPx; + int screenHeight = mDeviceHeightPx; + + int rotateMatrix = 0; + + switch (mRotation) { + case ROTATION_90: + rotateMatrix = -90; + break; + case ROTATION_180: + rotateMatrix = -180; + break; + case Surface.ROTATION_270: + rotateMatrix = -270; + break; + } + + Matrix matrix = new Matrix(); + matrix.postRotate(rotateMatrix, mDeviceWidthPx / 2, mDeviceHeightPx / 2); + + if (mRotation == ROTATION_90 || mRotation == Surface.ROTATION_270) { + screenHeight = mDeviceWidthPx; + screenWidth = mDeviceHeightPx; + matrix.postTranslate((mDeviceHeightPx + - mDeviceWidthPx) / 2, (mDeviceWidthPx - mDeviceHeightPx) / 2); + } + + CircularCornerPathRenderer.Corner screenBottomLeft = getRotatedCorner( + CircularCornerPathRenderer.Corner.BOTTOM_LEFT); + CircularCornerPathRenderer.Corner screenBottomRight = getRotatedCorner( + CircularCornerPathRenderer.Corner.BOTTOM_RIGHT); + CircularCornerPathRenderer.Corner screenTopLeft = getRotatedCorner( + CircularCornerPathRenderer.Corner.TOP_LEFT); + CircularCornerPathRenderer.Corner screenTopRight = getRotatedCorner( + CircularCornerPathRenderer.Corner.TOP_RIGHT); + + mRegions[Region.BOTTOM_LEFT.ordinal()].path = + mCornerPathRenderer.getInsetPath(screenBottomLeft, mEdgeInset); + mRegions[Region.BOTTOM_RIGHT.ordinal()].path = + mCornerPathRenderer.getInsetPath(screenBottomRight, mEdgeInset); + mRegions[Region.TOP_RIGHT.ordinal()].path = + mCornerPathRenderer.getInsetPath(screenTopRight, mEdgeInset); + mRegions[Region.TOP_LEFT.ordinal()].path = + mCornerPathRenderer.getInsetPath(screenTopLeft, mEdgeInset); + + mRegions[Region.BOTTOM_LEFT.ordinal()].path.transform(matrix); + mRegions[Region.BOTTOM_RIGHT.ordinal()].path.transform(matrix); + mRegions[Region.TOP_RIGHT.ordinal()].path.transform(matrix); + mRegions[Region.TOP_LEFT.ordinal()].path.transform(matrix); + + + Path bottomPath = new Path(); + bottomPath.moveTo(getPhysicalCornerRadius(screenBottomLeft), screenHeight - mEdgeInset); + bottomPath.lineTo(screenWidth - getPhysicalCornerRadius(screenBottomRight), + screenHeight - mEdgeInset); + mRegions[Region.BOTTOM.ordinal()].path = bottomPath; + + Path topPath = new Path(); + topPath.moveTo(screenWidth - getPhysicalCornerRadius(screenTopRight), mEdgeInset); + topPath.lineTo(getPhysicalCornerRadius(screenTopLeft), mEdgeInset); + mRegions[Region.TOP.ordinal()].path = topPath; + + Path rightPath = new Path(); + rightPath.moveTo(screenWidth - mEdgeInset, + screenHeight - getPhysicalCornerRadius(screenBottomRight)); + rightPath.lineTo(screenWidth - mEdgeInset, getPhysicalCornerRadius(screenTopRight)); + mRegions[Region.RIGHT.ordinal()].path = rightPath; + + Path leftPath = new Path(); + leftPath.moveTo(mEdgeInset, + getPhysicalCornerRadius(screenTopLeft)); + leftPath.lineTo(mEdgeInset, screenHeight - getPhysicalCornerRadius(screenBottomLeft)); + mRegions[Region.LEFT.ordinal()].path = leftPath; + + float perimeterLength = 0; + PathMeasure pathMeasure = new PathMeasure(); + for (int i = 0; i < mRegions.length; i++) { + pathMeasure.setPath(mRegions[i].path, false); + mRegions[i].absoluteLength = pathMeasure.getLength(); + perimeterLength += mRegions[i].absoluteLength; + } + + float accum = 0; + for (int i = 0; i < mRegions.length; i++) { + mRegions[i].normalizedLength = mRegions[i].absoluteLength / perimeterLength; + accum += mRegions[i].normalizedLength; + mRegions[i].endCoordinate = accum; + } + } + + private CircularCornerPathRenderer.Corner getRotatedCorner( + CircularCornerPathRenderer.Corner screenCorner) { + int corner = screenCorner.ordinal(); + switch (mRotation) { + case ROTATION_90: + corner += 3; + break; + case ROTATION_180: + corner += 2; + break; + case Surface.ROTATION_270: + corner += 1; + break; + } + return CircularCornerPathRenderer.Corner.values()[corner % 4]; + } + + private void strokeSegmentInternal(Path path, float startCoord, float endCoord) { + Pair<Region, Float> startPoint = placePoint(startCoord); + Pair<Region, Float> endPoint = placePoint(endCoord); + + if (startPoint.first.equals(endPoint.first)) { + strokeRegion(path, startPoint.first, startPoint.second, endPoint.second); + } else { + strokeRegion(path, startPoint.first, startPoint.second, 1f); + boolean hitStart = false; + for (Region r : Region.values()) { + if (r.equals(startPoint.first)) { + hitStart = true; + continue; + } + if (hitStart) { + if (!r.equals(endPoint.first)) { + strokeRegion(path, r, 0f, 1f); + } else { + strokeRegion(path, r, 0f, endPoint.second); + break; + } + } + } + } + } + + private void strokeRegion(Path path, Region r, float relativeStart, float relativeEnd) { + if (relativeStart == relativeEnd) { + return; + } + + mScratchPathMeasure.setPath(mRegions[r.ordinal()].path, false); + mScratchPathMeasure.getSegment(relativeStart * mScratchPathMeasure.getLength(), + relativeEnd * mScratchPathMeasure.getLength(), path, true); + } + + /** + * Return the Region where the point is located, and its relative position within that region + * (from 0 to 1). + * Note that we move counterclockwise around the perimeter; for example, a relative position of + * 0 in + * the BOTTOM region is on the left side of the screen, but in the TOP region it’s on the + * right. + */ + private Pair<Region, Float> placePoint(float coord) { + if (0 > coord || coord > 1) { + coord = ((coord % 1) + 1) + % 1; // Wrap to the range [0, 1). Inputs of exactly 1 are preserved. + } + + Region r = getRegionForPoint(coord); + if (r.equals(Region.BOTTOM)) { + return Pair.create(r, coord / mRegions[r.ordinal()].normalizedLength); + } else { + float coordOffsetInRegion = coord - mRegions[r.ordinal() - 1].endCoordinate; + float coordRelativeToRegion = + coordOffsetInRegion / mRegions[r.ordinal()].normalizedLength; + return Pair.create(r, coordRelativeToRegion); + } + } + + private Region getRegionForPoint(float coord) { + // If coord is outside of [0,1], wrap to [0,1). + if (coord < 0 || coord > 1) { + coord = ((coord % 1) + 1) % 1; + } + + for (Region region : Region.values()) { + if (coord <= mRegions[region.ordinal()].endCoordinate) { + return region; + } + } + + // Should never happen. + Log.e(TAG, "Fell out of getRegionForPoint"); + return Region.BOTTOM; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index c63389a47d2d..771df2d86110 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -330,9 +330,7 @@ public class BubbleStackView extends FrameLayout { mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER; mBubbleContainer = new PhysicsAnimationLayout(context); - mBubbleContainer.setMaxRenderedChildren( - getResources().getInteger(R.integer.bubbles_max_rendered)); - mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setActiveController(mStackAnimationController); mBubbleContainer.setElevation(elevation); mBubbleContainer.setClipChildren(false); addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); @@ -728,7 +726,7 @@ public class BubbleStackView extends FrameLayout { public void updateBubbleOrder(List<Bubble> bubbles) { for (int i = 0; i < bubbles.size(); i++) { Bubble bubble = bubbles.get(i); - mBubbleContainer.moveViewTo(bubble.iconView, i); + mBubbleContainer.reorderView(bubble.iconView, i); } } @@ -908,19 +906,17 @@ public class BubbleStackView extends FrameLayout { }; if (shouldExpand) { - mBubbleContainer.setController(mExpandedAnimationController); - mExpandedAnimationController.expandFromStack( - mStackAnimationController.getStackPositionAlongNearestHorizontalEdge() - /* collapseTo */, - () -> { - updatePointerPosition(); - updateAfter.run(); - } /* after */); + mBubbleContainer.setActiveController(mExpandedAnimationController); + mExpandedAnimationController.expandFromStack(() -> { + updatePointerPosition(); + updateAfter.run(); + } /* after */); } else { mBubbleContainer.cancelAllAnimations(); mExpandedAnimationController.collapseBackToStack( + mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(), () -> { - mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setActiveController(mStackAnimationController); updateAfter.run(); }); } @@ -1013,7 +1009,7 @@ public class BubbleStackView extends FrameLayout { } mStackAnimationController.cancelStackPositionAnimations(); - mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setActiveController(mStackAnimationController); hideFlyoutImmediate(); mDraggingInDismissTarget = false; @@ -1111,6 +1107,10 @@ public class BubbleStackView extends FrameLayout { /** Called when a gesture is completed or cancelled. */ void onGestureFinished() { mIsGestureInProgress = false; + + if (mIsExpanded) { + mExpandedAnimationController.onGestureFinished(); + } } /** Prepares and starts the desaturate/darken animation on the bubble stack. */ @@ -1201,6 +1201,7 @@ public class BubbleStackView extends FrameLayout { */ void magnetToStackIfNeededThenAnimateDismissal( View touchedView, float velX, float velY, Runnable after) { + final View draggedOutBubble = mExpandedAnimationController.getDraggedOutBubble(); final Runnable animateDismissal = () -> { mAfterMagnet = null; @@ -1218,7 +1219,7 @@ public class BubbleStackView extends FrameLayout { resetDesaturationAndDarken(); }); } else { - mExpandedAnimationController.dismissDraggedOutBubble(() -> { + mExpandedAnimationController.dismissDraggedOutBubble(draggedOutBubble, () -> { mAnimatingMagnet = false; mShowingDismiss = false; mDraggingInDismissTarget = false; @@ -1385,10 +1386,18 @@ public class BubbleStackView extends FrameLayout { }; // Post in case layout isn't complete and getWidth returns 0. - post(() -> mFlyout.showFlyout( - updateMessage, mStackAnimationController.getStackPosition(), getWidth(), - mStackAnimationController.isStackOnLeftSide(), - bubble.iconView.getBadgeColor(), mAfterFlyoutHides)); + post(() -> { + // An auto-expanding bubble could have been posted during the time it takes to + // layout. + if (isExpanded()) { + return; + } + + mFlyout.showFlyout( + updateMessage, mStackAnimationController.getStackPosition(), getWidth(), + mStackAnimationController.isStackOnLeftSide(), + bubble.iconView.getBadgeColor(), mAfterFlyoutHides); + }); } mFlyout.removeCallbacks(mHideFlyout); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 24337a3c3312..1fa0e12452e1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -22,6 +22,7 @@ import android.graphics.PointF; import android.view.View; import android.view.WindowInsets; +import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringForce; @@ -63,12 +64,16 @@ public class ExpandedAnimationController private Point mDisplaySize; /** Size of dismiss target at bottom of screen. */ private float mPipDismissHeight; - /** Max number of bubbles shown in row above expanded view.*/ - private int mBubblesMaxRendered; /** Whether the dragged-out bubble is in the dismiss target. */ private boolean mIndividualBubbleWithinDismissTarget = false; + private boolean mAnimatingExpand = false; + private boolean mAnimatingCollapse = false; + private Runnable mAfterExpand; + private Runnable mAfterCollapse; + private PointF mCollapsePoint; + /** * Whether the dragged out bubble is springing towards the touch point, rather than using the * default behavior of moving directly to the touch point. @@ -97,56 +102,60 @@ public class ExpandedAnimationController private View mBubbleDraggingOut; /** - * Drag velocities for the dragging-out bubble when the drag finished. These are used by - * {@link #onChildRemoved} to animate out the bubble while respecting touch velocity. + * Animates expanding the bubbles into a row along the top of the screen. */ - private float mBubbleDraggingOutVelX; - private float mBubbleDraggingOutVelY; + public void expandFromStack(Runnable after) { + mAnimatingCollapse = false; + mAnimatingExpand = true; + mAfterExpand = after; - @Override - protected void setLayout(PhysicsAnimationLayout layout) { - super.setLayout(layout); + startOrUpdateExpandAnimation(); + } - final Resources res = layout.getResources(); - mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); - mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding); - mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); - mStatusBarHeight = - res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height); - mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); + /** Animate collapsing the bubbles back to their stacked position. */ + public void collapseBackToStack(PointF collapsePoint, Runnable after) { + mAnimatingExpand = false; + mAnimatingCollapse = true; + mAfterCollapse = after; + mCollapsePoint = collapsePoint; + + startOrUpdateCollapseAnimation(); } - /** - * Animates expanding the bubbles into a row along the top of the screen. - */ - public void expandFromStack(PointF collapseTo, Runnable after) { + private void startOrUpdateExpandAnimation() { animationsForChildrenFromIndex( 0, /* startIndex */ - new ChildAnimationConfigurator() { - @Override - public void configureAnimationForChildAtIndex( - int index, PhysicsAnimationLayout.PhysicsPropertyAnimator animation) { - animation.position(getBubbleLeft(index), getExpandedY()); + (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY())) + .startAll(() -> { + mAnimatingExpand = false; + + if (mAfterExpand != null) { + mAfterExpand.run(); } - }) - .startAll(after); - mCollapseToPoint = collapseTo; + mAfterExpand = null; + }); } - /** Animate collapsing the bubbles back to their stacked position. */ - public void collapseBackToStack(Runnable after) { + private void startOrUpdateCollapseAnimation() { // Stack to the left if we're going to the left, or right if not. - final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapseToPoint.x) ? -1 : 1; - + final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1; animationsForChildrenFromIndex( 0, /* startIndex */ - (index, animation) -> + (index, animation) -> { animation.position( - mCollapseToPoint.x + (sideMultiplier * index * mStackOffsetPx), - mCollapseToPoint.y)) - .startAll(after /* endAction */); + mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx), + mCollapsePoint.y); + }) + .startAll(() -> { + mAnimatingCollapse = false; + + if (mAfterCollapse != null) { + mAfterCollapse.run(); + } + + mAfterCollapse = null; + }); } /** Prepares the given bubble to be dragged out. */ @@ -190,10 +199,10 @@ public class ExpandedAnimationController } /** Plays a dismiss animation on the dragged out bubble. */ - public void dismissDraggedOutBubble(Runnable after) { + public void dismissDraggedOutBubble(View bubble, Runnable after) { mIndividualBubbleWithinDismissTarget = false; - animationForChild(mBubbleDraggingOut) + animationForChild(bubble) .withStiffness(SpringForce.STIFFNESS_HIGH) .scaleX(1.1f) .scaleY(1.1f) @@ -203,6 +212,10 @@ public class ExpandedAnimationController updateBubblePositions(); } + @Nullable public View getDraggedOutBubble() { + return mBubbleDraggingOut; + } + /** Magnets the given bubble to the dismiss target. */ public void magnetBubbleToDismiss( View bubbleView, float velX, float velY, float destY, Runnable after) { @@ -241,24 +254,17 @@ public class ExpandedAnimationController final int index = mLayout.indexOfChild(bubbleView); animationForChildAtIndex(index) - .position(getBubbleLeft(index), getExpandedY()) - .withPositionStartVelocities(velX, velY) - .start(() -> bubbleView.setTranslationZ(0f) /* after */); + .position(getBubbleLeft(index), getExpandedY()) + .withPositionStartVelocities(velX, velY) + .start(() -> bubbleView.setTranslationZ(0f) /* after */); - mBubbleDraggingOut = null; - mBubbleDraggedOutEnough = false; updateBubblePositions(); } - /** - * Sets configuration variables so that when the given bubble is removed, the animations are - * started with the given velocities. - */ - public void prepareForDismissalWithVelocity(View bubbleView, float velX, float velY) { - mBubbleDraggingOut = bubbleView; - mBubbleDraggingOutVelX = velX; - mBubbleDraggingOutVelY = velY; + /** Resets bubble drag out gesture flags. */ + public void onGestureFinished() { mBubbleDraggedOutEnough = false; + mBubbleDraggingOut = null; } /** @@ -297,6 +303,23 @@ public class ExpandedAnimationController } @Override + void onActiveControllerForLayout(PhysicsAnimationLayout layout) { + final Resources res = layout.getResources(); + mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mStatusBarHeight = + res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height); + + // Ensure that all child views are at 1x scale, and visible, in case they were animating + // in. + mLayout.setVisibility(View.VISIBLE); + animationsForChildrenFromIndex(0 /* startIndex */, (index, animation) -> + animation.scaleX(1f).scaleY(1f).alpha(1f)).startAll(); + } + + @Override Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { return Sets.newHashSet( DynamicAnimation.TRANSLATION_X, @@ -325,14 +348,21 @@ public class ExpandedAnimationController @Override void onChildAdded(View child, int index) { - child.setTranslationX(getXForChildAtIndex(index)); - - animationForChild(child) - .translationY( - getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ - getExpandedY() /* to */) - .start(); - updateBubblePositions(); + // If a bubble is added while the expand/collapse animations are playing, update the + // animation to include the new bubble. + if (mAnimatingExpand) { + startOrUpdateExpandAnimation(); + } else if (mAnimatingCollapse) { + startOrUpdateCollapseAnimation(); + } else { + child.setTranslationX(getXForChildAtIndex(index)); + animationForChild(child) + .translationY( + getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ + getExpandedY() /* to */) + .start(); + updateBubblePositions(); + } } @Override @@ -357,19 +387,15 @@ public class ExpandedAnimationController } @Override - protected void setChildVisibility(View child, int index, int visibility) { - if (visibility == View.VISIBLE) { - // Set alpha to 0 but then become visible immediately so the animation is visible. - child.setAlpha(0f); - child.setVisibility(View.VISIBLE); - } - - animationForChild(child) - .alpha(visibility == View.GONE ? 0f : 1f) - .start(() -> super.setChildVisibility(child, index, visibility) /* after */); + void onChildReordered(View child, int oldIndex, int newIndex) { + updateBubblePositions(); } private void updateBubblePositions() { + if (mAnimatingExpand || mAnimatingCollapse) { + return; + } + for (int i = 0; i < mLayout.getChildCount(); i++) { final View bubble = mLayout.getChildAt(i); @@ -378,6 +404,7 @@ public class ExpandedAnimationController if (bubble.equals(mBubbleDraggingOut)) { return; } + animationForChild(bubble) .translationX(getBubbleLeft(i)) .start(); @@ -403,10 +430,7 @@ public class ExpandedAnimationController return 0; } int bubbleCount = mLayout.getChildCount(); - if (bubbleCount > mBubblesMaxRendered) { - // Only shown bubbles are relevant for calculating position. - bubbleCount = mBubblesMaxRendered; - } + // Width calculations. double bubble = bubbleCount * mBubbleSizePx; float gap = (bubbleCount - 1) * mBubblePaddingPx; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java index 997d2c4627d8..3a3339249d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -17,10 +17,12 @@ package com.android.systemui.bubbles.animation; import android.content.Context; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; @@ -137,12 +139,33 @@ public class PhysicsAnimationLayout extends FrameLayout { */ abstract void onChildRemoved(View child, int index, Runnable finishRemoval); + /** Called when a child view has been reordered in the view hierachy. */ + abstract void onChildReordered(View child, int oldIndex, int newIndex); + + /** + * Called when the controller is set as the active animation controller for the given + * layout. Once active, the controller can start animations using the animator instances + * returned by {@link #animationForChild}. + * + * While all animations started by the previous controller will be cancelled, the new + * controller should not make any assumptions about the state of the layout or its children. + * Their translation, alpha, scale, etc. values may have been changed by the previous + * controller and should be reset here if relevant. + */ + abstract void onActiveControllerForLayout(PhysicsAnimationLayout layout); + protected PhysicsAnimationLayout mLayout; PhysicsAnimationController() { } + /** Whether this controller is the currently active controller for its associated layout. */ + protected boolean isActiveController() { + return this == mLayout.mController; + } + protected void setLayout(PhysicsAnimationLayout layout) { this.mLayout = layout; + onActiveControllerForLayout(layout); } protected PhysicsAnimationLayout getLayout() { @@ -150,15 +173,6 @@ public class PhysicsAnimationLayout extends FrameLayout { } /** - * Sets the child's visibility when it moves beyond or within the limits set by a call to - * {@link PhysicsAnimationLayout#setMaxRenderedChildren}. This can be overridden to animate - * this transition. - */ - protected void setChildVisibility(View child, int index, int visibility) { - child.setVisibility(visibility); - } - - /** * Returns a {@link PhysicsPropertyAnimator} instance for the given child view. */ protected PhysicsPropertyAnimator animationForChild(View child) { @@ -170,6 +184,9 @@ public class PhysicsAnimationLayout extends FrameLayout { child.setTag(R.id.physics_animator_tag, animator); } + animator.clearAnimator(); + animator.setAssociatedController(this); + return animator; } @@ -235,32 +252,17 @@ public class PhysicsAnimationLayout extends FrameLayout { new HashMap<>(); /** The currently active animation controller. */ - private PhysicsAnimationController mController; - - /** - * The maximum number of children to render and animate at a time. See - * {@link #setMaxRenderedChildren}. - */ - private int mMaxRenderedChildren = 5; + @Nullable protected PhysicsAnimationController mController; public PhysicsAnimationLayout(Context context) { super(context); } /** - * The maximum number of children to render and animate at a time. Any child views added beyond - * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view, - * the corresponding property will be set with no animation. - */ - public void setMaxRenderedChildren(int max) { - this.mMaxRenderedChildren = max; - } - - /** * Sets the animation controller and constructs or reconfigures the layout's physics animations * to meet the controller's specifications. */ - public void setController(PhysicsAnimationController controller) { + public void setActiveController(PhysicsAnimationController controller) { cancelAllAnimations(); mEndActionForProperty.clear(); @@ -312,42 +314,11 @@ public class PhysicsAnimationLayout extends FrameLayout { @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { - super.addView(child, index, params); - - // Set up animations for the new view, if the controller is set. If it isn't set, we'll be - // setting up animations for all children when setController is called. - if (mController != null) { - for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { - setUpAnimationForChild(property, child, index); - } - - mController.onChildAdded(child, index); - } - - setChildrenVisibility(); + addViewInternal(child, index, params, false /* isReorder */); } @Override public void removeView(View view) { - removeViewAndThen(view, /* callback */ null); - } - - @Override - public void removeViewAt(int index) { - removeView(getChildAt(index)); - } - - /** Immediately moves the view from wherever it currently is, to the given index. */ - public void moveViewTo(View view, int index) { - super.removeView(view); - addView(view, index); - } - - /** - * Let the controller know that this view should be removed, and then call the callback once the - * controller has finished any removal animations and the view has actually been removed. - */ - public void removeViewAndThen(View view, Runnable callback) { if (mController != null) { final int index = indexOfChild(view); @@ -355,8 +326,6 @@ public class PhysicsAnimationLayout extends FrameLayout { super.removeView(view); addTransientView(view, index); - setChildrenVisibility(); - // Tell the controller to animate this view out, and call the callback when it's // finished. mController.onChildRemoved(view, index, () -> { @@ -364,19 +333,28 @@ public class PhysicsAnimationLayout extends FrameLayout { // any are still running and then remove it. cancelAnimationsOnView(view); removeTransientView(view); - - if (callback != null) { - callback.run(); - } }); } else { // Without a controller, nobody will animate this view out, so it gets an unceremonious // departure. super.removeView(view); + } + } - if (callback != null) { - callback.run(); - } + @Override + public void removeViewAt(int index) { + removeView(getChildAt(index)); + } + + /** Immediately re-orders the view to the given index. */ + public void reorderView(View view, int index) { + final int oldIndex = indexOfChild(view); + + super.removeView(view); + addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */); + + if (mController != null) { + mController.onChildReordered(view, oldIndex, index); } } @@ -427,6 +405,10 @@ public class PhysicsAnimationLayout extends FrameLayout { } } + protected boolean isActiveController(PhysicsAnimationController controller) { + return mController == controller; + } + /** Whether the first child would be left of center if translated to the given x value. */ protected boolean isFirstChildXLeftOfCenter(float x) { if (getChildCount() > 0) { @@ -454,6 +436,26 @@ public class PhysicsAnimationLayout extends FrameLayout { } /** + * Adds a view to the layout. If this addition is not the result of a call to + * {@link #reorderView}, this will also notify the controller via + * {@link PhysicsAnimationController#onChildAdded} and set up animations for the view. + */ + private void addViewInternal( + View child, int index, ViewGroup.LayoutParams params, boolean isReorder) { + super.addView(child, index, params); + + // Set up animations for the new view, if the controller is set. If it isn't set, we'll be + // setting up animations for all children when setActiveController is called. + if (mController != null && !isReorder) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationForChild(property, child, index); + } + + mController.onChildAdded(child, index); + } + } + + /** * Retrieves the animation of the given property from the view at the given index via the view * tag system. */ @@ -481,33 +483,16 @@ public class PhysicsAnimationLayout extends FrameLayout { SpringAnimation newAnim = new SpringAnimation(child, property); newAnim.addUpdateListener((animation, value, velocity) -> { final int indexOfChild = indexOfChild(child); - final int nextAnimInChain = - mController.getNextAnimationInChain(property, indexOfChild); + final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild); if (nextAnimInChain == PhysicsAnimationController.NONE || indexOfChild < 0) { return; } - final int animIndex = indexOfChild(child); - final float offset = - mController.getOffsetForChainedPropertyAnimation(property); - - // If this property's animations should be chained, then check to see if there is a - // subsequent animation within the rendering limit, and if so, tell it to animate to - // this animation's new value (plus the offset). - if (nextAnimInChain < Math.min(getChildCount(), mMaxRenderedChildren)) { - getAnimationAtIndex(property, animIndex + 1) + final float offset = mController.getOffsetForChainedPropertyAnimation(property); + if (nextAnimInChain < getChildCount()) { + getAnimationAtIndex(property, nextAnimInChain) .animateToFinalPosition(value + offset); - } else if (nextAnimInChain < getChildCount()) { - // If the next child view is not rendered, update the property directly without - // animating it, so that the view is still in the correct state if it later - // becomes visible. - for (int i = nextAnimInChain; i < getChildCount(); i++) { - // 'value' here is the value of the last child within the rendering limit, - // not the first child's value - so we want to subtract the last child's - // index when calculating the offset. - property.setValue(getChildAt(i), value + offset * (i - animIndex)); - } } }); @@ -516,22 +501,6 @@ public class PhysicsAnimationLayout extends FrameLayout { child.setTag(getTagIdForProperty(property), newAnim); } - /** Hides children beyond the max rendering count. */ - private void setChildrenVisibility() { - for (int i = 0; i < getChildCount(); i++) { - final int targetVisibility = i < mMaxRenderedChildren ? View.VISIBLE : View.GONE; - final View targetView = getChildAt(i); - - if (targetView.getVisibility() != targetVisibility) { - if (mController != null) { - mController.setChildVisibility(targetView, i, targetVisibility); - } else { - targetView.setVisibility(targetVisibility); - } - } - } - } - /** Return a stable ID to use as a tag key for the given property's animations. */ private int getTagIdForProperty(DynamicAnimation.ViewProperty property) { if (property.equals(DynamicAnimation.TRANSLATION_X)) { @@ -592,7 +561,7 @@ public class PhysicsAnimationLayout extends FrameLayout { private View mView; /** Start velocity to use for all property animations. */ - private float mDefaultStartVelocity = 0f; + private float mDefaultStartVelocity = -Float.MAX_VALUE; /** Start delay to use when start is called. */ private long mStartDelay = 0; @@ -625,6 +594,15 @@ public class PhysicsAnimationLayout extends FrameLayout { */ private Map<DynamicAnimation.ViewProperty, Float> mAnimatedProperties = new HashMap<>(); + /** + * All of the initial property values that have been set. These values will be instantly set + * when {@link #start} is called, just before the animation begins. + */ + private Map<DynamicAnimation.ViewProperty, Float> mInitialPropertyValues = new HashMap<>(); + + /** The animation controller that last retrieved this animator instance. */ + private PhysicsAnimationController mAssociatedController; + protected PhysicsPropertyAnimator(View view) { this.mView = view; } @@ -644,7 +622,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's alpha value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator alpha(float from, float to, Runnable... endActions) { - mView.setAlpha(from); + mInitialPropertyValues.put(DynamicAnimation.ALPHA, from); return alpha(to, endActions); } @@ -656,7 +634,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's translationX value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator translationX( float from, float to, Runnable... endActions) { - mView.setTranslationX(from); + mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_X, from); return translationX(to, endActions); } @@ -668,7 +646,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's translationY value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator translationY( float from, float to, Runnable... endActions) { - mView.setTranslationY(from); + mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_Y, from); return translationY(to, endActions); } @@ -690,7 +668,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's scaleX value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator scaleX(float from, float to, Runnable... endActions) { - mView.setScaleX(from); + mInitialPropertyValues.put(DynamicAnimation.SCALE_X, from); return scaleX(to, endActions); } @@ -701,7 +679,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's scaleY value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator scaleY(float from, float to, Runnable... endActions) { - mView.setScaleY(from); + mInitialPropertyValues.put(DynamicAnimation.SCALE_Y, from); return scaleY(to, endActions); } @@ -750,6 +728,13 @@ public class PhysicsAnimationLayout extends FrameLayout { * animated property on every child (including chained animations) have ended. */ public void start(Runnable... after) { + if (!isActiveController(mAssociatedController)) { + Log.w(TAG, "Only the active animation controller is allowed to start animations. " + + "Use PhysicsAnimationLayout#setActiveController to set the active " + + "animation controller."); + return; + } + final Set<DynamicAnimation.ViewProperty> properties = getAnimatedProperties(); // If there are end actions, set an end listener on the layout for all the properties @@ -791,6 +776,10 @@ public class PhysicsAnimationLayout extends FrameLayout { // Actually start the animations. for (DynamicAnimation.ViewProperty property : properties) { + if (mInitialPropertyValues.containsKey(property)) { + property.setValue(mView, mInitialPropertyValues.get(property)); + } + final SpringForce defaultSpringForce = mController.getSpringForce(property, mView); animateValueForChild( property, @@ -803,14 +792,7 @@ public class PhysicsAnimationLayout extends FrameLayout { mEndActionsForProperty.get(property)); } - // Clear out the animator. - mAnimatedProperties.clear(); - mPositionStartVelocities.clear(); - mDefaultStartVelocity = 0; - mStartDelay = 0; - mStiffness = -1; - mDampingRatio = -1; - mEndActionsForProperty.clear(); + clearAnimator(); } /** Returns the set of properties that will animate once {@link #start} is called. */ @@ -847,20 +829,50 @@ public class PhysicsAnimationLayout extends FrameLayout { }); } - animation.getSpring().setStiffness(stiffness); - animation.getSpring().setDampingRatio(dampingRatio); + final SpringForce animationSpring = animation.getSpring(); - if (startVel > 0) { - animation.setStartVelocity(startVel); + if (animationSpring == null) { + return; } + final Runnable configureAndStartAnimation = () -> { + animationSpring.setStiffness(stiffness); + animationSpring.setDampingRatio(dampingRatio); + + if (startVel > -Float.MAX_VALUE) { + animation.setStartVelocity(startVel); + } + + animationSpring.setFinalPosition(value); + animation.start(); + }; + if (startDelay > 0) { - postDelayed(() -> animation.animateToFinalPosition(value), startDelay); + postDelayed(configureAndStartAnimation, startDelay); } else { - animation.animateToFinalPosition(value); + configureAndStartAnimation.run(); } } } + + private void clearAnimator() { + mInitialPropertyValues.clear(); + mAnimatedProperties.clear(); + mPositionStartVelocities.clear(); + mDefaultStartVelocity = -Float.MAX_VALUE; + mStartDelay = 0; + mStiffness = -1; + mDampingRatio = -1; + mEndActionsForProperty.clear(); + } + + /** + * Sets the controller that last retrieved this animator instance, so that we can prevent + * {@link #start} from actually starting animations if called by a non-active controller. + */ + private void setAssociatedController(PhysicsAnimationController controller) { + mAssociatedController = controller; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index b9cdc844eef9..ab8752e4195f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -154,21 +154,6 @@ public class StackAnimationController extends /** Height of the status bar. */ private float mStatusBarHeight; - @Override - protected void setLayout(PhysicsAnimationLayout layout) { - super.setLayout(layout); - - Resources res = layout.getResources(); - mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); - mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); - mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); - mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); - mStackStartingVerticalOffset = - res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y); - mStatusBarHeight = - res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - } - /** * Instantly move the first bubble to the given point, and animate the rest of the stack behind * it with the 'following' effect. @@ -286,6 +271,8 @@ public class StackAnimationController extends }, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + // If we're flinging now, there's no more touch event to catch up to. + mFirstBubbleSpringingToTouch = false; mIsMovingFromFlinging = true; return destinationRelativeX; } @@ -656,19 +643,38 @@ public class StackAnimationController extends if (mLayout.getChildCount() > 0) { animationForChildAtIndex(0).translationX(mStackPosition.x).start(); + } else { + // Set the start position back to the default since we're out of bubbles. New bubbles + // will then animate in from the start position. + mStackPosition = getDefaultStartPosition(); } } + @Override + void onChildReordered(View child, int oldIndex, int newIndex) {} + + @Override + void onActiveControllerForLayout(PhysicsAnimationLayout layout) { + Resources res = layout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); + mStackStartingVerticalOffset = + res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y); + mStatusBarHeight = + res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + } + /** Moves the stack, without any animation, to the starting position. */ private void moveStackToStartPosition() { // Post to ensure that the layout's width and height have been calculated. mLayout.setVisibility(View.INVISIBLE); mLayout.post(() -> { + setStackPosition(mRestingStackPosition == null + ? getDefaultStartPosition() + : mRestingStackPosition); mStackMovedToStartPosition = true; - setStackPosition( - mRestingStackPosition == null - ? getDefaultStartPosition() - : mRestingStackPosition); mLayout.setVisibility(View.VISIBLE); // Animate in the top bubble now that we're visible. @@ -707,15 +713,20 @@ public class StackAnimationController extends Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y)); mStackPosition.set(pos.x, pos.y); - mLayout.cancelAllAnimations(); - cancelStackPositionAnimations(); - - // Since we're not using the chained animations, apply the offsets manually. - final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); - final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y); - for (int i = 0; i < mLayout.getChildCount(); i++) { - mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset)); - mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset)); + // If we're not the active controller, we don't want to physically move the bubble views. + if (isActiveController()) { + mLayout.cancelAllAnimations(); + cancelStackPositionAnimations(); + + // Since we're not using the chained animations, apply the offsets manually. + final float xOffset = getOffsetForChainedPropertyAnimation( + DynamicAnimation.TRANSLATION_X); + final float yOffset = getOffsetForChainedPropertyAnimation( + DynamicAnimation.TRANSLATION_Y); + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset)); + mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset)); + } } } @@ -732,6 +743,10 @@ public class StackAnimationController extends /** Animates in the given bubble. */ private void animateInBubble(View child) { + if (!isActiveController()) { + return; + } + child.setTranslationY(mStackPosition.y); float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index ff16ed0f1477..dc977547b024 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -551,7 +551,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private class EmergencyDialerAction extends EmergencyAction { private EmergencyDialerAction() { - super(R.drawable.ic_faster_emergency, + super(com.android.systemui.R.drawable.ic_emergency_star, R.string.global_action_emergency); } @@ -1588,17 +1588,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // Disable rotation suggestions, if enabled setRotationSuggestionsEnabled(false); - FrameLayout panelContainer = new FrameLayout(mContext); + FrameLayout panelContainer = + findViewById(com.android.systemui.R.id.global_actions_panel_container); FrameLayout.LayoutParams panelParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT); + FrameLayout.LayoutParams.MATCH_PARENT); panelContainer.addView(mPanelController.getPanelContent(), panelParams); - addContentView( - panelContainer, - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); mBackgroundDrawable = mPanelController.getBackgroundDrawable(); mScrimAlpha = 1f; } @@ -1606,8 +1602,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private void initializeLayout() { setContentView(getGlobalActionsLayoutId(mContext)); + fixNavBarClipping(); mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); + ((View) mGlobalActionsLayout.getParent()).setOnClickListener(view -> dismiss()); mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() { @Override public boolean dispatchPopulateAccessibilityEvent( @@ -1630,6 +1628,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, getWindow().setBackgroundDrawable(mBackgroundDrawable); } + private void fixNavBarClipping() { + ViewGroup content = findViewById(android.R.id.content); + content.setClipChildren(false); + content.setClipToPadding(false); + ViewGroup contentParent = (ViewGroup) content.getParent(); + contentParent.setClipChildren(false); + contentParent.setClipToPadding(false); + } + private int getGlobalActionsLayoutId(Context context) { int rotation = RotationUtils.getRotation(context); boolean useGridLayout = isForceGridEnabled(context) diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java index 03165f47c472..e1462d15c887 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -42,8 +42,6 @@ public class GlobalActionsGridLayout extends GlobalActionsLayout { listView.setReverseSublists(shouldReverseSublists()); listView.setReverseItems(shouldReverseListItems()); listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns()); - - fixNavBarClipping(); } @Override @@ -75,19 +73,6 @@ public class GlobalActionsGridLayout extends GlobalActionsLayout { } } - /** - * Allows the dialog to clip over the navbar, which prevents shadows and animations from being - * cut off. - */ - private void fixNavBarClipping() { - ViewGroup parent = (ViewGroup) this.getParent(); - ViewGroup parentParent = (ViewGroup) parent.getParent(); - parent.setClipChildren(false); - parent.setClipToPadding(false); - parentParent.setClipChildren(false); - parentParent.setClipToPadding(false); - } - @Override protected ListGridLayout getListView() { return (ListGridLayout) super.getListView(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java index 04ff58b36c94..64b2f048ce2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java @@ -25,6 +25,7 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; @@ -69,7 +70,8 @@ public class NotificationData { mHeadsUpManager = headsUpManager; } - private final Comparator<NotificationEntry> mRankingComparator = + @VisibleForTesting + protected final Comparator<NotificationEntry> mRankingComparator = new Comparator<NotificationEntry>() { private final Ranking mRankingA = new Ranking(); private final Ranking mRankingB = new Ranking(); @@ -120,6 +122,8 @@ public class NotificationData { } else if (aSystemMax != bSystemMax) { // Upsort PRIORITY_MAX system notifications return aSystemMax ? -1 : 1; + } else if (a.isHighPriority() != b.isHighPriority()) { + return -1 * Boolean.compare(a.isHighPriority(), b.isHighPriority()); } else if (aRank != bRank) { return aRank - bRank; } else { @@ -231,17 +235,14 @@ public class NotificationData { /** * Returns true if this notification should be displayed in the high-priority notifications - * section (and on the lockscreen and status bar). + * section */ public boolean isHighPriority(StatusBarNotification statusBarNotification) { if (mRankingMap != null) { getRanking(statusBarNotification.getKey(), mTmpRanking); if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT - || isImportantOngoing(statusBarNotification.getNotification()) - || statusBarNotification.getNotification().hasMediaSession() - || hasPerson(statusBarNotification.getNotification()) - || hasStyle(statusBarNotification.getNotification(), - Notification.MessagingStyle.class)) { + || hasHighPriorityCharacteristics( + mTmpRanking.getChannel(), statusBarNotification)) { return true; } if (mGroupManager.isSummaryOfGroup(statusBarNotification)) { @@ -257,6 +258,25 @@ public class NotificationData { return false; } + private boolean hasHighPriorityCharacteristics(NotificationChannel channel, + StatusBarNotification statusBarNotification) { + + if (isImportantOngoing(statusBarNotification.getNotification()) + || statusBarNotification.getNotification().hasMediaSession() + || hasPerson(statusBarNotification.getNotification()) + || hasStyle(statusBarNotification.getNotification(), + Notification.MessagingStyle.class)) { + // Users who have long pressed and demoted to silent should not see the notification + // in the top section + if (channel != null && channel.hasUserSetImportance()) { + return false; + } + return true; + } + + return false; + } + private boolean isImportantOngoing(Notification notification) { return notification.isForegroundService() && mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_LOW; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ButtonLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ButtonLinearLayout.java new file mode 100644 index 000000000000..94bdd81401bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ButtonLinearLayout.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Button; +import android.widget.LinearLayout; + +public class ButtonLinearLayout extends LinearLayout { + + public ButtonLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public CharSequence getAccessibilityClassName() { + return Button.class.getName(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index ee2dacd67f46..4526eafe8ab5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -295,6 +295,7 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi int tintColor = getNotificationHeader().getOriginalIconColor(); mSeekBarElapsedTime.setTextColor(tintColor); mSeekBarTotalTime.setTextColor(tintColor); + mSeekBarTotalTime.setShadowLayer(1.5f, 1.5f, 1.5f, mBackgroundColor); ColorStateList tintList = ColorStateList.valueOf(tintColor); mSeekBar.setThumbTintList(tintList); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index a517e760c8b4..e6f47315bf4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -939,18 +939,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd return; } - float alpha = - BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); - alpha *= 1f - mInterpolatedDarkAmount; - // We need to manually blend in the background color. - int scrimColor = mScrimController.getBackgroundColor(); - int awakeColor = ColorUtils.blendARGB(scrimColor, mBgColor, alpha); - // Interpolate between semi-transparent notification panel background color // and white AOD separator. float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */, mLinearDarkAmount); - int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation); + int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation); if (mCachedBackgroundColor != color) { mCachedBackgroundColor = color; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index f2218651d7c7..05a86fa9d7ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -33,13 +33,14 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.MathUtils; -import android.view.Choreographer; import android.view.Gravity; import android.view.IPinnedStackController; import android.view.IPinnedStackListener; import android.view.ISystemGestureExclusionListener; +import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputEventReceiver; import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -53,7 +54,6 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -165,12 +165,14 @@ public class EdgeBackGestureHandler implements DisplayListener { mEdgeWidth = res.getDimensionPixelSize( com.android.internal.R.dimen.config_backGestureInset); - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + // Reduce the default touch slop to ensure that we can intercept the gesture + // before the app starts to react to it. + // TODO(b/130352502) Tune this value and extract into a constant + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 0.75f; mLongPressTimeout = ViewConfiguration.getLongPressTimeout(); mNavBarHeight = res.getDimensionPixelSize(R.dimen.navigation_bar_frame_height); - mMinArrowPosition = res.getDimensionPixelSize( - R.dimen.navigation_edge_arrow_min_y); + mMinArrowPosition = res.getDimensionPixelSize(R.dimen.navigation_edge_arrow_min_y); mFingerOffset = res.getDimensionPixelSize(R.dimen.navigation_edge_finger_offset); } @@ -250,9 +252,8 @@ public class EdgeBackGestureHandler implements DisplayListener { // Register input event receiver mInputMonitor = InputManager.getInstance().monitorGestureInput( "edge-swipe", mDisplayId); - mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(), - Looper.getMainLooper(), Choreographer.getMainThreadInstance(), - this::onInputEvent); + mInputEventReceiver = new SysUiInputEventReceiver( + mInputMonitor.getInputChannel(), Looper.getMainLooper()); // Add a nav bar panel window mEdgePanel = new NavigationBarEdgePanel(mContext); @@ -440,4 +441,15 @@ public class EdgeBackGestureHandler implements DisplayListener { } InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + + class SysUiInputEventReceiver extends InputEventReceiver { + SysUiInputEventReceiver(InputChannel channel, Looper looper) { + super(channel, looper); + } + + public void onInputEvent(InputEvent event) { + EdgeBackGestureHandler.this.onInputEvent(event); + finishInputEvent(event, true); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java index a79b6251a3d0..a4965ba59c6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java @@ -51,9 +51,10 @@ public class FloatingRotationButton implements RotationButton { R.layout.rotate_suggestion, null); mKeyButtonView.setVisibility(View.VISIBLE); - Resources resources = mContext.getResources(); - mDiameter = resources.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter); - mMargin = resources.getDimensionPixelSize(R.dimen.floating_rotation_button_margin); + Resources res = mContext.getResources(); + mDiameter = res.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter); + mMargin = Math.max(res.getDimensionPixelSize(R.dimen.floating_rotation_button_min_margin), + res.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index b7a154d67c10..a0cda693822f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -22,8 +22,6 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.Region.Op; import android.util.Log; import android.util.Pools; import android.view.DisplayCutout; @@ -78,6 +76,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, private int[] mTmpTwoArray = new int[2]; private boolean mHeadsUpGoingAway; private int mStatusBarState; + private Rect mTouchableRegion = new Rect(); private AnimationStateHandler mAnimationStateHandler; @@ -297,10 +296,13 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, @Nullable public void updateTouchableRegion(ViewTreeObserver.InternalInsetsInfo info) { info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(calculateTouchableRegion()); + } + public Rect calculateTouchableRegion() { if (!hasPinnedHeadsUp()) { - info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); - updateRegionForNotch(info.touchableRegion); + mTouchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); + updateRegionForNotch(mTouchableRegion); } else { NotificationEntry topEntry = getTopEntry(); if (topEntry.isChildInGroup()) { @@ -315,11 +317,12 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, int minX = mTmpTwoArray[0]; int maxX = mTmpTwoArray[0] + topRow.getWidth(); int height = topRow.getIntrinsicHeight(); - info.touchableRegion.set(minX, 0, maxX, mHeadsUpInset + height); + mTouchableRegion.set(minX, 0, maxX, mHeadsUpInset + height); } + return mTouchableRegion; } - private void updateRegionForNotch(Region region) { + private void updateRegionForNotch(Rect region) { DisplayCutout cutout = mStatusBarWindowView.getRootWindowInsets().getDisplayCutout(); if (cutout == null) { return; @@ -330,7 +333,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, Rect bounds = new Rect(); ScreenDecorations.DisplayCutoutView.boundsFromDirection(cutout, Gravity.TOP, bounds); bounds.offset(0, mDisplayCutoutTouchableRegionSize); - region.op(bounds, Op.UNION); + region.union(bounds); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 337c6b167cdf..d94a33556a43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -86,6 +86,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.ScreenDecorations; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.assist.AssistManager; import com.android.systemui.fragments.FragmentHostManager; @@ -170,6 +171,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback public int mDisplayId; private boolean mIsOnDefaultDisplay; public boolean mHomeBlockedThisTouch; + private ScreenDecorations mScreenDecorations; private Handler mHandler = Dependency.get(Dependency.MAIN_HANDLER); @@ -348,12 +350,17 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS; } setDisabled2Flags(mDisabledFlags2); + + mScreenDecorations = SysUiServiceProvider.getComponent(getContext(), + ScreenDecorations.class); + getBarTransitions().addDarkIntensityListener(mScreenDecorations); } @Override public void onDestroyView() { super.onDestroyView(); if (mNavigationBarView != null) { + mNavigationBarView.getBarTransitions().removeDarkIntensityListener(mScreenDecorations); mNavigationBarView.getBarTransitions().destroy(); mNavigationBarView.getLightTransitionsController().destroy(getContext()); } @@ -1020,7 +1027,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback getBarTransitions().transitionTo(barMode, animate); } - private BarTransitions getBarTransitions() { + public NavigationBarTransitions getBarTransitions() { return mNavigationBarView.getBarTransitions(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index 8a28c6fbff29..2b5a28e60082 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -36,9 +36,23 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.R; +import java.util.ArrayList; +import java.util.List; + public final class NavigationBarTransitions extends BarTransitions implements LightBarTransitionsController.DarkIntensityApplier { + /** + * Notified when the color of nav bar elements changes. + */ + public interface DarkIntensityListener { + /** + * Called when the color of nav bar elements changes. + * @param darkIntensity 0 is the lightest color, 1 is the darkest. + */ + void onDarkIntensity(float darkIntensity); + } + private final NavigationBarView mView; private final IStatusBarService mBarService; private final LightBarTransitionsController mLightTransitionsController; @@ -49,6 +63,7 @@ public final class NavigationBarTransitions extends BarTransitions implements private boolean mAutoDim; private View mNavButtons; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; + private List<DarkIntensityListener> mDarkIntensityListeners; private final Handler mHandler = Handler.getMain(); private final IWallpaperVisibilityListener mWallpaperVisibilityListener = @@ -69,6 +84,7 @@ public final class NavigationBarTransitions extends BarTransitions implements mLightTransitionsController = new LightBarTransitionsController(view.getContext(), this); mAllowAutoDimWallpaperNotVisible = view.getContext().getResources() .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper); + mDarkIntensityListeners = new ArrayList(); IWindowManager windowManagerService = Dependency.get(IWindowManager.class); try { @@ -168,12 +184,16 @@ public final class NavigationBarTransitions extends BarTransitions implements applyDarkIntensity(mLightTransitionsController.getCurrentDarkIntensity()); } + @Override public void applyDarkIntensity(float darkIntensity) { SparseArray<ButtonDispatcher> buttonDispatchers = mView.getButtonDispatchers(); for (int i = buttonDispatchers.size() - 1; i >= 0; i--) { buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity); } mView.getRotationButtonController().setDarkIntensity(darkIntensity); + for (DarkIntensityListener listener : mDarkIntensityListeners) { + listener.onDarkIntensity(darkIntensity); + } if (mAutoDim) { applyLightsOut(false, true); } @@ -190,4 +210,18 @@ public final class NavigationBarTransitions extends BarTransitions implements public void onNavigationModeChanged(int mode) { mNavBarMode = mode; } + + /** + * Register {@code listener} to be notified when the color of nav bar elements changes. + */ + public void addDarkIntensityListener(DarkIntensityListener listener) { + mDarkIntensityListeners.add(listener); + } + + /** + * Remove {@code listener} from being notified when the color of nav bar elements changes. + */ + public void removeDarkIntensityListener(DarkIntensityListener listener) { + mDarkIntensityListeners.remove(listener); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 7682e8a594ce..8a895e187b31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -299,7 +299,7 @@ public class NavigationBarView extends FrameLayout implements return mTintController; } - public BarTransitions getBarTransitions() { + public NavigationBarTransitions getBarTransitions() { return mBarTransitions; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index bb490f52b045..e8ca3eef24de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -1,7 +1,5 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; - import android.content.Context; import android.content.res.Resources; import android.graphics.Color; @@ -17,7 +15,6 @@ import androidx.collection.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.ContrastColorUtil; -import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; @@ -86,17 +83,6 @@ public class NotificationIconAreaController implements DarkReceiver, * Ratio representing being awake or in ambient mode, where 1 is dark and 0 awake. */ private float mDarkAmount; - /** - * Maximum translation to avoid burn in. - */ - private int mBurnInOffset; - /** - * Height of the keyguard status bar (not the one after unlocking.) - */ - private int mKeyguardStatusBarHeight; - - private final ViewClippingUtil.ClippingParameters mClippingParameters = - view -> view instanceof StatusBarWindowView; public NotificationIconAreaController(Context context, StatusBar statusBar, StatusBarStateController statusBarStateController, @@ -166,9 +152,6 @@ public class NotificationIconAreaController implements DarkReceiver, Resources res = context.getResources(); mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding); - mBurnInOffset = res.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset); - mKeyguardStatusBarHeight = res - .getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard); } /** @@ -479,49 +462,9 @@ public class NotificationIconAreaController implements DarkReceiver, mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate); } - /** - * Moves icons whenever the device wakes up in AOD, to avoid burn in. - */ - public void dozeTimeTick() { - if (mNotificationIcons.getVisibility() != View.VISIBLE) { - return; - } - - if (mDarkAmount == 0 && !mStatusBarStateController.isDozing()) { - mNotificationIcons.setTranslationX(0); - mNotificationIcons.setTranslationY(0); - mCenteredIcon.setTranslationX(0); - mCenteredIcon.setTranslationY(0); - return; - } - - int yOffset = (mKeyguardStatusBarHeight - getHeight()) / 2; - int translationX = getBurnInOffset(mBurnInOffset, true /* xAxis */); - int translationY = getBurnInOffset(mBurnInOffset, false /* xAxis */) + yOffset; - mNotificationIcons.setTranslationX(translationX); - mNotificationIcons.setTranslationY(translationY); - mCenteredIcon.setTranslationX(translationX); - mCenteredIcon.setTranslationY(translationY); - } - - @Override - public void onDozingChanged(boolean isDozing) { - dozeTimeTick(); - } - @Override public void onDozeAmountChanged(float linear, float eased) { - boolean wasOrIsAwake = mDarkAmount == 0 || linear == 0; - boolean wasOrIsDozing = mDarkAmount == 1 || linear == 1; mDarkAmount = linear; - if (wasOrIsAwake) { - ViewClippingUtil.setClippingDeactivated(mNotificationIcons, mDarkAmount != 0, - mClippingParameters); - } - if (wasOrIsAwake || wasOrIsDozing) { - dozeTimeTick(); - } - boolean fullyDark = mDarkAmount == 1f; if (mFullyDark != fullyDark) { mFullyDark = fullyDark; 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 7623dee10c39..92aa884b14d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -100,6 +100,7 @@ import com.android.systemui.util.InjectionInflationController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -141,6 +142,7 @@ public class NotificationPanelView extends PanelView implements private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek"; private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1); + private static final Rect mEmptyRect = new Rect(); private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties() .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); @@ -596,6 +598,25 @@ public class NotificationPanelView extends PanelView implements mQs.setHeightOverride(mQs.getDesiredHeight()); } updateMaxHeadsUpTranslation(); + updateGestureExclusionRect(); + } + + private void updateGestureExclusionRect() { + Rect exclusionRect = calculateGestureExclusionRect(); + setSystemGestureExclusionRects(exclusionRect.isEmpty() + ? Collections.EMPTY_LIST + : Collections.singletonList(exclusionRect)); + } + + private Rect calculateGestureExclusionRect() { + Rect exclusionRect = null; + if (isFullyCollapsed()) { + // Note: The heads up manager also calculates the non-pinned touchable region + exclusionRect = mHeadsUpManager.calculateTouchableRegion(); + } + return exclusionRect != null + ? exclusionRect + : mEmptyRect; } private void setIsFullWidth(boolean isFullWidth) { @@ -1798,6 +1819,7 @@ public class NotificationPanelView extends PanelView implements updateHeader(); updateNotificationTranslucency(); updatePanelExpanded(); + updateGestureExclusionRect(); if (DEBUG) { invalidate(); } @@ -2568,6 +2590,7 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.runAfterAnimationFinished( mHeadsUpExistenceChangedRunnable); } + updateGestureExclusionRect(); } public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { @@ -2992,6 +3015,7 @@ public class NotificationPanelView extends PanelView implements @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { super.dump(fd, pw, args); + pw.println(" gestureExclusionRect: " + calculateGestureExclusionRect()); if (mKeyguardStatusBar != null) { mKeyguardStatusBar.dump(fd, pw, args); } 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 1da819f1a764..05a23fa49922 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1311,7 +1311,6 @@ public class StatusBar extends SystemUI implements DemoMode, && !mDozing && !ONLY_CORE_APPS; mNotificationPanel.setQsExpansionEnabled(expandEnabled); - // STOPSHIP(kozynski, b/129405675) Remove log Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled); } @@ -4013,7 +4012,6 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void dozeTimeTick() { mNotificationPanel.dozeTimeTick(); - mNotificationIconAreaController.dozeTimeTick(); if (mAmbientIndicationContainer instanceof DozeReceiver) { ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index b9afea155ccf..cc31531c90a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -16,7 +16,10 @@ package com.android.systemui.appops; +import static junit.framework.TestCase.assertFalse; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -189,4 +192,21 @@ public class AppOpsControllerTest extends SysuiTestCase { AppOpsManager.MODE_ALLOWED); verify(mMockHandler).scheduleRemoval(any(AppOpItem.class), anyLong()); } + + @Test + public void noItemsAfterStopListening() { + mController.setBGHandler(mMockHandler); + + mController.setListening(true); + mController.onOpActiveChanged(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + true); + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + assertFalse(mController.getActiveAppOps().isEmpty()); + + mController.setListening(false); + + verify(mMockHandler).removeCallbacksAndMessages(null); + assertTrue(mController.getActiveAppOps().isEmpty()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java index 13c92b605c03..18f114a71a8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java @@ -18,21 +18,27 @@ package com.android.systemui.assist; import static org.mockito.AdditionalAnswers.answerVoid; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; +import com.android.internal.app.AssistUtils; import com.android.systemui.ScreenDecorations; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; import org.junit.Before; import org.junit.Test; @@ -46,29 +52,35 @@ import org.mockito.MockitoAnnotations; @RunWithLooper public class AssistHandleBehaviorControllerTest extends SysuiTestCase { - private final AssistHandleBehavior mTestBehavior = AssistHandleBehavior.TEST; + private static final ComponentName COMPONENT_NAME = new ComponentName("", ""); private AssistHandleBehaviorController mAssistHandleBehaviorController; @Mock private ScreenDecorations mMockScreenDecorations; + @Mock private AssistUtils mMockAssistUtils; @Mock private Handler mMockHandler; @Mock private AssistHandleBehaviorController.BehaviorController mMockBehaviorController; @Before public void setup() { MockitoAnnotations.initMocks(this); + mDependency.injectMockDependency(StatusBarStateController.class); + mDependency.injectMockDependency(OverviewProxyService.class); doAnswer(answerVoid(Runnable::run)).when(mMockHandler).post(any(Runnable.class)); doAnswer(answerVoid(Runnable::run)).when(mMockHandler) .postDelayed(any(Runnable.class), anyLong()); - mTestBehavior.setTestController(mMockBehaviorController); mAssistHandleBehaviorController = new AssistHandleBehaviorController( - mContext, mMockHandler, () -> mMockScreenDecorations); + mContext, + mMockAssistUtils, + mMockHandler, () -> mMockScreenDecorations, + mMockBehaviorController); } @Test public void hide_hidesHandlesWhenShowing() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.showAndStay(); reset(mMockScreenDecorations); @@ -83,6 +95,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void hide_doesNothingWhenHiding() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.hide(); reset(mMockScreenDecorations); @@ -96,6 +109,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void showAndStay_showsHandlesWhenHiding() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.hide(); reset(mMockScreenDecorations); @@ -110,6 +124,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void showAndStay_doesNothingWhenShowing() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.showAndStay(); reset(mMockScreenDecorations); @@ -121,8 +136,23 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { } @Test + public void showAndStay_doesNothingWhenThereIsNoAssistant() { + // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(null); + mAssistHandleBehaviorController.hide(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndStay(); + + // Assert + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test public void showAndGo_showsThenHidesHandlesWhenHiding() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.hide(); reset(mMockScreenDecorations); @@ -139,6 +169,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void showAndGo_hidesHandlesAfterTimeoutWhenShowing() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.showAndStay(); reset(mMockScreenDecorations); @@ -153,6 +184,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void showAndGo_doesNothingIfRecentlyHidden() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.showAndGo(); reset(mMockScreenDecorations); @@ -164,12 +196,27 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { } @Test + public void showAndGo_doesNothingWhenThereIsNoAssistant() { + // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(null); + mAssistHandleBehaviorController.hide(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndGo(); + + // Assert + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test public void setBehavior_activatesTheBehaviorWhenInGesturalMode() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.setInGesturalModeForTest(true); // Act - mAssistHandleBehaviorController.setBehavior(mTestBehavior); + mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST); // Assert verify(mMockBehaviorController).onModeActivated(mContext, mAssistHandleBehaviorController); @@ -179,8 +226,10 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void setBehavior_deactivatesThePreviousBehaviorWhenInGesturalMode() { // Arrange - mAssistHandleBehaviorController.setBehavior(mTestBehavior); + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); + mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST); mAssistHandleBehaviorController.setInGesturalModeForTest(true); + reset(mMockBehaviorController); // Act mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.OFF); @@ -193,10 +242,11 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void setBehavior_doesNothingWhenNotInGesturalMode() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.setInGesturalModeForTest(false); // Act - mAssistHandleBehaviorController.setBehavior(mTestBehavior); + mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST); // Assert verifyNoMoreInteractions(mMockBehaviorController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java index 756cf3e138f6..b324235106c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java @@ -60,8 +60,8 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC @Before public void setUp() throws Exception { super.setUp(); - addOneMoreThanRenderLimitBubbles(); - mLayout.setController(mExpandedController); + addOneMoreThanBubbleLimitBubbles(); + mLayout.setActiveController(mExpandedController); Resources res = mLayout.getResources(); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); @@ -73,14 +73,14 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC @Test public void testExpansionAndCollapse() throws InterruptedException { Runnable afterExpand = Mockito.mock(Runnable.class); - mExpandedController.expandFromStack(mExpansionPoint, afterExpand); + mExpandedController.expandFromStack(afterExpand); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); testBubblesInCorrectExpandedPositions(); verify(afterExpand).run(); Runnable afterCollapse = Mockito.mock(Runnable.class); - mExpandedController.collapseBackToStack(afterCollapse); + mExpandedController.collapseBackToStack(mExpansionPoint, afterCollapse); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); @@ -139,7 +139,6 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC assertEquals(500f, draggedBubble.getTranslationY(), 1f); // Snap it back and make sure it made it back correctly. - mExpandedController.prepareForDismissalWithVelocity(draggedBubble, 0f, 0f); mLayout.removeView(draggedBubble); waitForLayoutMessageQueue(); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); @@ -169,7 +168,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC // Dismiss the now-magneted bubble, verify that the callback was called. final Runnable afterDismiss = Mockito.mock(Runnable.class); - mExpandedController.dismissDraggedOutBubble(afterDismiss); + mExpandedController.dismissDraggedOutBubble(draggedOutView, afterDismiss); waitForPropertyAnimations(DynamicAnimation.ALPHA); verify(after).run(); @@ -224,7 +223,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC /** Expand the stack and wait for animations to finish. */ private void expand() throws InterruptedException { - mExpandedController.expandFromStack(mExpansionPoint, Mockito.mock(Runnable.class)); + mExpandedController.expandFromStack(Mockito.mock(Runnable.class)); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); } @@ -236,26 +235,19 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC assertEquals(x + i * offsetMultiplier * mStackOffset, mLayout.getChildAt(i).getTranslationX(), 2f); assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f); - - if (i < mMaxRenderedBubbles) { - assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f); - } + assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f); } } /** Check that children are in the correct positions for being expanded. */ private void testBubblesInCorrectExpandedPositions() { // Check all the visible bubbles to see if they're in the right place. - for (int i = 0; i < Math.min(mLayout.getChildCount(), mMaxRenderedBubbles); i++) { + for (int i = 0; i < mLayout.getChildCount(); i++) { assertEquals(getBubbleLeft(i), mLayout.getChildAt(i).getTranslationX(), 2f); assertEquals(mExpandedController.getExpandedY(), mLayout.getChildAt(i).getTranslationY(), 2f); - - if (i < mMaxRenderedBubbles) { - assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f); - } } } @@ -273,9 +265,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC return 0; } int bubbleCount = mLayout.getChildCount(); - if (bubbleCount > mMaxRenderedBubbles) { - bubbleCount = mMaxRenderedBubbles; - } + // Width calculations. double bubble = bubbleCount * mBubbleSize; float gap = (bubbleCount - 1) * mBubblePadding; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java index eef6ddcf6d6b..f8b32c213109 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java @@ -23,7 +23,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -77,21 +76,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Test - public void testRenderVisibility() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); - - // The last child should be GONE, the rest VISIBLE. - for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { - assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE, - mLayout.getChildAt(i).getVisibility()); - } - } - - @Test public void testHierarchyChanges() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); // Make sure the controller was notified of all the views we added. for (View mView : mViews) { @@ -115,8 +102,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testUpdateValueNotChained() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); // Don't chain any values. mTestableController.setChainedProperties(Sets.newHashSet()); @@ -146,8 +133,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testSetEndActions() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); mTestableController.setChainedProperties(Sets.newHashSet()); final CountDownLatch xLatch = new CountDownLatch(1); @@ -189,8 +176,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testRemoveEndListeners() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); mTestableController.setChainedProperties(Sets.newHashSet()); final CountDownLatch xLatch = new CountDownLatch(1); @@ -229,8 +216,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { public void testSetController() throws InterruptedException { // Add the bubbles, then set the controller, to make sure that a controller added to an // already-initialized view works correctly. - addOneMoreThanRenderLimitBubbles(); - mLayout.setController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); + mLayout.setActiveController(mTestableController); testChainedTranslationAnimations(); TestableAnimationController secondController = @@ -243,7 +230,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { DynamicAnimation.SCALE_X, 10f); secondController.setRemoveImmediately(true); - mLayout.setController(secondController); + mLayout.setActiveController(secondController); mTestableController.animationForChildAtIndex(0) .scaleX(1.5f) .start(); @@ -266,7 +253,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { Mockito.verify(secondController, Mockito.atLeastOnce()) .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); - mLayout.setController(mTestableController); + mLayout.setActiveController(mTestableController); mTestableController.animationForChildAtIndex(0) .translationX(100f) .start(); @@ -283,8 +270,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testArePropertiesAnimating() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); assertFalse(mLayout.arePropertiesAnimating( DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); @@ -307,8 +294,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testCancelAllAnimations() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); mTestableController.animationForChildAtIndex(0) .position(1000, 1000) @@ -321,29 +308,10 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { assertTrue(mViews.get(0).getTranslationY() < 1000); } - @Test - public void testSetChildVisibility() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); - - // The last view should have been set to GONE by the controller, since we added one more - // than the limit and it got pushed off. None of the first children should have been set - // VISIBLE, since they would have been animated in by onChildAdded. - Mockito.verify(mTestableController).setChildVisibility( - mViews.get(mViews.size() - 1), 5, View.GONE); - Mockito.verify(mTestableController, never()).setChildVisibility( - any(View.class), anyInt(), eq(View.VISIBLE)); - - // Remove the first view, which should cause the last view to become visible again. - mLayout.removeView(mViews.get(0)); - Mockito.verify(mTestableController).setChildVisibility( - mViews.get(mViews.size() - 1), 4, View.VISIBLE); - } - /** Standard test of chained translation animations. */ private void testChainedTranslationAnimations() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); @@ -354,11 +322,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); - // Since we enabled chaining, animating the first view to 100 should animate the second to - // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble - // not being visible, or animated, make sure that it has the appropriate chained - // translation. - for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + for (int i = 0; i < mLayout.getChildCount(); i++) { assertEquals( 100 + i * TEST_TRANSLATION_X_OFFSET, mLayout.getChildAt(i).getTranslationX(), .1f); @@ -383,8 +347,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testPhysicsAnimator() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); Runnable afterAll = Mockito.mock(Runnable.class); Runnable after = Mockito.spy(new Runnable() { @@ -430,9 +394,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { // Don't chain since we're going to invoke each animation independently. mTestableController.setChainedProperties(new HashSet<>()); - mLayout.setController(mTestableController); + mLayout.setActiveController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + addOneMoreThanBubbleLimitBubbles(); Runnable allEnd = Mockito.mock(Runnable.class); @@ -452,7 +416,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testAnimationsForChildrenFromIndex_noChildren() { - mLayout.setController(mTestableController); + mLayout.setActiveController(mTestableController); final Runnable after = Mockito.mock(Runnable.class); mTestableController @@ -523,8 +487,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Override - protected void setChildVisibility(View child, int index, int visibility) { - super.setChildVisibility(child, index, visibility); - } + void onChildReordered(View child, int oldIndex, int newIndex) {} + + @Override + void onActiveControllerForLayout(PhysicsAnimationLayout layout) {} } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java index c6acef5d4907..f633f3996d13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java @@ -56,7 +56,6 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { Handler mMainThreadHandler; - int mMaxRenderedBubbles; int mSystemWindowInsetSize = 50; int mCutoutInsetSize = 100; @@ -69,6 +68,8 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { @Mock private DisplayCutout mCutout; + private int mMaxBubbles; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -79,7 +80,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { mLayout.setTop(0); mLayout.setBottom(mHeight); - mMaxRenderedBubbles = + mMaxBubbles = getContext().getResources().getInteger(R.integer.bubbles_max_rendered); mMainThreadHandler = new Handler(Looper.getMainLooper()); @@ -96,8 +97,8 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ - void addOneMoreThanRenderLimitBubbles() throws InterruptedException { - for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + void addOneMoreThanBubbleLimitBubbles() throws InterruptedException { + for (int i = 0; i < mMaxBubbles + 1; i++) { final View newView = new FrameLayout(mContext); mLayout.addView(newView, 0); mViews.add(0, newView); @@ -138,6 +139,13 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } @Override + protected boolean isActiveController(PhysicsAnimationController controller) { + // Return true since otherwise all test controllers will be seen as inactive since they + // are wrapped by MainThreadAnimationControllerWrapper. + return true; + } + + @Override public boolean post(Runnable action) { return mMainThreadHandler.post(action); } @@ -148,9 +156,9 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } @Override - public void setController(PhysicsAnimationController controller) { + public void setActiveController(PhysicsAnimationController controller) { runOnMainThreadAndBlock( - () -> super.setController( + () -> super.setActiveController( new MainThreadAnimationControllerWrapper(controller))); } @@ -267,8 +275,15 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } @Override - protected void setChildVisibility(View child, int index, int visibility) { - mWrappedController.setChildVisibility(child, index, visibility); + void onChildReordered(View child, int oldIndex, int newIndex) { + runOnMainThreadAndBlock( + () -> mWrappedController.onChildReordered(child, oldIndex, newIndex)); + } + + @Override + void onActiveControllerForLayout(PhysicsAnimationLayout layout) { + runOnMainThreadAndBlock( + () -> mWrappedController.onActiveControllerForLayout(layout)); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java index 9218a8b93f66..31a7d5a45b68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -54,8 +54,8 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase @Before public void setUp() throws Exception { super.setUp(); - mLayout.setController(mStackController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mStackController); + addOneMoreThanBubbleLimitBubbles(); mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java index a396f3e8bf46..ea478597ef77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java @@ -48,8 +48,9 @@ public class GlobalActionsGridLayoutTest extends SysuiTestCase { @Before public void setUp() throws Exception { - mGridLayout = spy((GlobalActionsGridLayout) - LayoutInflater.from(mContext).inflate(R.layout.global_actions_grid, null)); + mGridLayout = spy(LayoutInflater.from(mContext) + .inflate(R.layout.global_actions_grid, null) + .requireViewById(R.id.global_actions_view)); mListGrid = spy(mGridLayout.getListView()); doReturn(mListGrid).when(mGridLayout).getListView(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java index 746140fc725d..74e8cc280979 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java @@ -43,8 +43,9 @@ public class ListGridLayoutTest extends SysuiTestCase { @Before public void setUp() throws Exception { - GlobalActionsGridLayout globalActions = (GlobalActionsGridLayout) - LayoutInflater.from(mContext).inflate(R.layout.global_actions_grid, null); + GlobalActionsGridLayout globalActions = LayoutInflater.from(mContext) + .inflate(R.layout.global_actions_grid, null) + .requireViewById(R.id.global_actions_view); mListGridLayout = globalActions.getListView(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java index 79a6ac4cb0c7..6e0ddbf0cc46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java @@ -28,6 +28,7 @@ import static android.app.NotificationManager.IMPORTANCE_MIN; import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_CHANNEL; import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_IMPORTANCE; +import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_RANK; import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_VIS_EFFECTS; import static junit.framework.Assert.assertEquals; @@ -61,16 +62,12 @@ import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.ArraySet; -import androidx.test.filters.SmallTest; - import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceController; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationTestHelper; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; @@ -83,7 +80,11 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import androidx.test.filters.SmallTest; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -137,7 +138,9 @@ public class NotificationDataTest extends SysuiTestCase { @Test public void testChannelSetWhenAdded() { - mNotificationData.rankingOverrides.putParcelable(OVERRIDE_CHANNEL, NOTIFICATION_CHANNEL); + Bundle override = new Bundle(); + override.putParcelable(OVERRIDE_CHANNEL, NOTIFICATION_CHANNEL); + mNotificationData.rankingOverrides.put(mRow.getEntry().key, override); mNotificationData.add(mRow.getEntry()); assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().channel); } @@ -229,7 +232,9 @@ public class NotificationDataTest extends SysuiTestCase { n.flags = Notification.FLAG_FOREGROUND_SERVICE; NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification); mNotificationData.add(entry); - mNotificationData.rankingOverrides.putInt(OVERRIDE_VIS_EFFECTS, 255); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_VIS_EFFECTS, 255); + mNotificationData.rankingOverrides.put(entry.key, override); assertTrue(entry.isExemptFromDndVisualSuppression()); assertFalse(entry.shouldSuppressAmbient()); @@ -245,7 +250,9 @@ public class NotificationDataTest extends SysuiTestCase { when(mMockStatusBarNotification.getNotification()).thenReturn(n); NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification); mNotificationData.add(entry); - mNotificationData.rankingOverrides.putInt(OVERRIDE_VIS_EFFECTS, 255); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_VIS_EFFECTS, 255); + mNotificationData.rankingOverrides.put(entry.key, override); assertTrue(entry.isExemptFromDndVisualSuppression()); assertFalse(entry.shouldSuppressAmbient()); @@ -257,7 +264,9 @@ public class NotificationDataTest extends SysuiTestCase { NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification); entry.mIsSystemNotification = true; mNotificationData.add(entry); - mNotificationData.rankingOverrides.putInt(OVERRIDE_VIS_EFFECTS, 255); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_VIS_EFFECTS, 255); + mNotificationData.rankingOverrides.put(entry.key, override); assertTrue(entry.isExemptFromDndVisualSuppression()); assertFalse(entry.shouldSuppressAmbient()); @@ -268,8 +277,9 @@ public class NotificationDataTest extends SysuiTestCase { initStatusBarNotification(false); NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification); entry.mIsSystemNotification = true; - mNotificationData.rankingOverrides.putInt(OVERRIDE_VIS_EFFECTS, - NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_VIS_EFFECTS, NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT); + mNotificationData.rankingOverrides.put(entry.key, override); mNotificationData.add(entry); when(mMockStatusBarNotification.getNotification()).thenReturn( @@ -395,11 +405,13 @@ public class NotificationDataTest extends SysuiTestCase { Notification notification = mock(Notification.class); when(notification.isForegroundService()).thenReturn(true); - mNotificationData.rankingOverrides.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_MIN); - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, notification, mContext.getUser(), "", 0); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_MIN); + mNotificationData.rankingOverrides.put(sbn.getKey(), override); + assertFalse(mNotificationData.isHighPriority(sbn)); } @@ -408,14 +420,114 @@ public class NotificationDataTest extends SysuiTestCase { Notification notification = mock(Notification.class); when(notification.isForegroundService()).thenReturn(true); - mNotificationData.rankingOverrides.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, notification, mContext.getUser(), "", 0); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + mNotificationData.rankingOverrides.put(sbn.getKey(), override); + assertTrue(mNotificationData.isHighPriority(sbn)); } + @Test + public void userChangeTrumpsHighPriorityCharacteristics() { + Person person = new Person.Builder() + .setName("name") + .setKey("abc") + .setUri("uri") + .setBot(true) + .build(); + + Notification notification = new Notification.Builder(mContext, "test") + .addPerson(person) + .setStyle(new Notification.MessagingStyle("")) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + notification, mContext.getUser(), "", 0); + + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); + channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + + Bundle override = new Bundle(); + override.putParcelable(OVERRIDE_CHANNEL, channel); + mNotificationData.rankingOverrides.put(sbn.getKey(), override); + + assertFalse(mNotificationData.isHighPriority(sbn)); + } + + @Test + public void testSort_highPriorityTrumpsNMSRank() { + // NMS rank says A and then B. But A is not high priority and B is, so B should sort in + // front + Notification aN = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + StatusBarNotification aSbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + aN, mContext.getUser(), "", 0); + NotificationEntry a = new NotificationEntry(aSbn); + a.setRow(mock(ExpandableNotificationRow.class)); + a.setIsHighPriority(false); + + Bundle override = new Bundle(); + override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + override.putInt(OVERRIDE_RANK, 1); + mNotificationData.rankingOverrides.put(a.key, override); + + Notification bN = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + StatusBarNotification bSbn = new StatusBarNotification("pkg2", "pkg2", 0, "tag", 0, 0, + bN, mContext.getUser(), "", 0); + NotificationEntry b = new NotificationEntry(bSbn); + b.setIsHighPriority(true); + b.setRow(mock(ExpandableNotificationRow.class)); + + Bundle bOverride = new Bundle(); + bOverride.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + bOverride.putInt(OVERRIDE_RANK, 2); + mNotificationData.rankingOverrides.put(b.key, bOverride); + + assertEquals(1, mNotificationData.mRankingComparator.compare(a, b)); + } + + @Test + public void testSort_samePriorityUsesNMSRank() { + // NMS rank says A and then B. But A is not high priority and B is, so B should sort in + // front + Notification aN = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + StatusBarNotification aSbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + aN, mContext.getUser(), "", 0); + NotificationEntry a = new NotificationEntry(aSbn); + a.setRow(mock(ExpandableNotificationRow.class)); + a.setIsHighPriority(false); + + Bundle override = new Bundle(); + override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + override.putInt(OVERRIDE_RANK, 1); + mNotificationData.rankingOverrides.put(a.key, override); + + Notification bN = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + StatusBarNotification bSbn = new StatusBarNotification("pkg2", "pkg2", 0, "tag", 0, 0, + bN, mContext.getUser(), "", 0); + NotificationEntry b = new NotificationEntry(bSbn); + b.setRow(mock(ExpandableNotificationRow.class)); + b.setIsHighPriority(false); + + Bundle bOverride = new Bundle(); + bOverride.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + bOverride.putInt(OVERRIDE_RANK, 2); + mNotificationData.rankingOverrides.put(b.key, bOverride); + + assertEquals(-1, mNotificationData.mRankingComparator.compare(a, b)); + } + private void initStatusBarNotification(boolean allowDuringSetup) { Bundle bundle = new Bundle(); bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup); @@ -449,7 +561,7 @@ public class NotificationDataTest extends SysuiTestCase { public static final String OVERRIDE_SMART_REPLIES = "sr"; public static final String OVERRIDE_BUBBLE = "cb"; - public Bundle rankingOverrides = new Bundle(); + public Map<String, Bundle> rankingOverrides = new HashMap<>(); @Override protected boolean getRanking(String key, Ranking outRanking) { @@ -475,40 +587,41 @@ public class NotificationDataTest extends SysuiTestCase { currentReplies.addAll(outRanking.getSmartReplies()); } - outRanking.populate(key, - rankingOverrides.getInt(OVERRIDE_RANK, outRanking.getRank()), - rankingOverrides.getBoolean(OVERRIDE_DND, - outRanking.matchesInterruptionFilter()), - rankingOverrides.getInt(OVERRIDE_VIS_OVERRIDE, - outRanking.getVisibilityOverride()), - rankingOverrides.getInt(OVERRIDE_VIS_EFFECTS, - outRanking.getSuppressedVisualEffects()), - rankingOverrides.getInt(OVERRIDE_IMPORTANCE, outRanking.getImportance()), - rankingOverrides.getCharSequence(OVERRIDE_IMP_EXP, - outRanking.getImportanceExplanation()), - rankingOverrides.getString(OVERRIDE_GROUP, outRanking.getOverrideGroupKey()), - rankingOverrides.containsKey(OVERRIDE_CHANNEL) - ? (NotificationChannel) rankingOverrides.getParcelable(OVERRIDE_CHANNEL) - : outRanking.getChannel(), - rankingOverrides.containsKey(OVERRIDE_PEOPLE) - ? rankingOverrides.getStringArrayList(OVERRIDE_PEOPLE) - : currentAdditionalPeople, - rankingOverrides.containsKey(OVERRIDE_SNOOZE_CRITERIA) - ? rankingOverrides.getParcelableArrayList(OVERRIDE_SNOOZE_CRITERIA) - : currentSnooze, - rankingOverrides.getBoolean(OVERRIDE_BADGE, outRanking.canShowBadge()), - rankingOverrides.getInt(OVERRIDE_USER_SENTIMENT, outRanking.getUserSentiment()), - rankingOverrides.getBoolean(OVERRIDE_HIDDEN, outRanking.isSuspended()), - rankingOverrides.getLong(OVERRIDE_LAST_ALERTED, - outRanking.getLastAudiblyAlertedMillis()), - rankingOverrides.getBoolean(OVERRIDE_NOISY, outRanking.isNoisy()), - rankingOverrides.containsKey(OVERRIDE_SMART_ACTIONS) - ? rankingOverrides.getParcelableArrayList(OVERRIDE_SMART_ACTIONS) - : currentActions, - rankingOverrides.containsKey(OVERRIDE_SMART_REPLIES) - ? rankingOverrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES) - : currentReplies, - rankingOverrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble())); + if (rankingOverrides.get(key) != null) { + Bundle overrides = rankingOverrides.get(key); + outRanking.populate(key, + overrides.getInt(OVERRIDE_RANK, outRanking.getRank()), + overrides.getBoolean(OVERRIDE_DND, outRanking.matchesInterruptionFilter()), + overrides.getInt(OVERRIDE_VIS_OVERRIDE, outRanking.getVisibilityOverride()), + overrides.getInt(OVERRIDE_VIS_EFFECTS, + outRanking.getSuppressedVisualEffects()), + overrides.getInt(OVERRIDE_IMPORTANCE, outRanking.getImportance()), + overrides.getCharSequence(OVERRIDE_IMP_EXP, + outRanking.getImportanceExplanation()), + overrides.getString(OVERRIDE_GROUP, outRanking.getOverrideGroupKey()), + overrides.containsKey(OVERRIDE_CHANNEL) + ? (NotificationChannel) overrides.getParcelable(OVERRIDE_CHANNEL) + : outRanking.getChannel(), + overrides.containsKey(OVERRIDE_PEOPLE) + ? overrides.getStringArrayList(OVERRIDE_PEOPLE) + : currentAdditionalPeople, + overrides.containsKey(OVERRIDE_SNOOZE_CRITERIA) + ? overrides.getParcelableArrayList(OVERRIDE_SNOOZE_CRITERIA) + : currentSnooze, + overrides.getBoolean(OVERRIDE_BADGE, outRanking.canShowBadge()), + overrides.getInt(OVERRIDE_USER_SENTIMENT, outRanking.getUserSentiment()), + overrides.getBoolean(OVERRIDE_HIDDEN, outRanking.isSuspended()), + overrides.getLong(OVERRIDE_LAST_ALERTED, + outRanking.getLastAudiblyAlertedMillis()), + overrides.getBoolean(OVERRIDE_NOISY, outRanking.isNoisy()), + overrides.containsKey(OVERRIDE_SMART_ACTIONS) + ? overrides.getParcelableArrayList(OVERRIDE_SMART_ACTIONS) + : currentActions, + overrides.containsKey(OVERRIDE_SMART_REPLIES) + ? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES) + : currentReplies, + overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble())); + } return true; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index 4181d38ad2c5..faf5a9706735 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -268,7 +268,7 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { when(view.getRecentsButton()).thenReturn(mock(ButtonDispatcher.class)); when(view.getAccessibilityButton()).thenReturn(mock(ButtonDispatcher.class)); when(view.getRotateSuggestionButton()).thenReturn(mock(RotationContextButton.class)); - when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class)); + when(view.getBarTransitions()).thenReturn(mock(NavigationBarTransitions.class)); when(view.getLightTransitionsController()).thenReturn( mock(LightBarTransitionsController.class)); when(view.getRotationButtonController()).thenReturn( diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml index 987d20375e5e..1232201de862 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml @@ -25,4 +25,6 @@ <dimen name="navigation_bar_width">16dp</dimen> <!-- Height of the bottom navigation / system bar. --> <dimen name="navigation_bar_frame_height">48dp</dimen> + <!-- The height of the bottom navigation gesture area. --> + <dimen name="navigation_bar_gesture_height">32dp</dimen> </resources>
\ No newline at end of file diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 430abf5c479d..10ba9a5e98d9 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -28,13 +28,17 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; +import android.os.IBinder; import android.os.RemoteException; import android.service.appprediction.AppPredictionService; +import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.infra.AbstractPerUserSystemService; +import java.util.ArrayList; + /** * Per-user instance of {@link AppPredictionManagerService}. */ @@ -48,6 +52,17 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") private RemoteAppPredictionService mRemoteService; + /** + * When {@code true}, remote service died but service state is kept so it's restored after + * the system re-binds to it. + */ + @GuardedBy("mLock") + private boolean mZombie; + + @GuardedBy("mLock") + private final ArrayMap<AppPredictionSessionId, AppPredictionSessionInfo> mSessionInfos = + new ArrayMap<>(); + protected AppPredictionPerUserService(AppPredictionManagerService master, Object lock, int userId) { super(master, lock, userId); @@ -92,6 +107,16 @@ public class AppPredictionPerUserService extends final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { service.onCreatePredictionSession(context, sessionId); + + mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context, () -> { + synchronized (mLock) { + AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo != null) { + sessionInfo.removeAllCallbacksLocked(); + mSessionInfos.remove(sessionId); + } + } + })); } } @@ -140,6 +165,11 @@ public class AppPredictionPerUserService extends final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { service.registerPredictionUpdates(sessionId, callback); + + AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo != null) { + sessionInfo.addCallbackLocked(callback); + } } } @@ -152,6 +182,11 @@ public class AppPredictionPerUserService extends final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { service.unregisterPredictionUpdates(sessionId, callback); + + AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo != null) { + sessionInfo.removeCallbackLocked(callback); + } } } @@ -174,6 +209,12 @@ public class AppPredictionPerUserService extends final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { service.onDestroyPredictionSession(sessionId); + + AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo != null) { + sessionInfo.removeAllCallbacksLocked(); + mSessionInfos.remove(sessionId); + } } } @@ -182,17 +223,54 @@ public class AppPredictionPerUserService extends if (isDebug()) { Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut); } - // Do nothing, we are just proxying to the prediction service } @Override + public void onConnectedStateChanged(boolean connected) { + if (isDebug()) { + Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected); + } + if (connected) { + synchronized (mLock) { + if (mZombie) { + // Sanity check - shouldn't happen + if (mRemoteService == null) { + Slog.w(TAG, "Cannot resurrect sessions because remote service is null"); + return; + } + mZombie = false; + resurrectSessionsLocked(); + } + } + } + } + + @Override public void onServiceDied(RemoteAppPredictionService service) { if (isDebug()) { - Slog.d(TAG, "onServiceDied():"); + Slog.w(TAG, "onServiceDied(): service=" + service); + } + synchronized (mLock) { + mZombie = true; + } + // Do nothing, eventually the system will bind to the remote service again... + } + + /** + * Called after the remote service connected, it's used to restore state from a 'zombie' + * service (i.e., after it died). + */ + private void resurrectSessionsLocked() { + final int numSessions = mSessionInfos.size(); + if (isDebug()) { + Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on " + + numSessions + " sessions."); } - // Do nothing, we are just proxying to the prediction service + for (AppPredictionSessionInfo sessionInfo : mSessionInfos.values()) { + sessionInfo.resurrectSessionLocked(this); + } } @GuardedBy("mLock") @@ -215,4 +293,57 @@ public class AppPredictionPerUserService extends return mRemoteService; } + + private static final class AppPredictionSessionInfo { + private final AppPredictionSessionId mSessionId; + private final AppPredictionContext mContext; + private final ArrayList<IPredictionCallback> mCallbacks = new ArrayList<>(); + private final IBinder.DeathRecipient mBinderDeathHandler; + + AppPredictionSessionInfo(AppPredictionSessionId id, AppPredictionContext context, + IBinder.DeathRecipient binderDeathHandler) { + mSessionId = id; + mContext = context; + mBinderDeathHandler = binderDeathHandler; + } + + void addCallbackLocked(IPredictionCallback callback) { + if (mBinderDeathHandler != null) { + try { + callback.asBinder().linkToDeath(mBinderDeathHandler, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death: " + e); + } + } + mCallbacks.add(callback); + } + + void removeCallbackLocked(IPredictionCallback callback) { + if (mBinderDeathHandler != null) { + callback.asBinder().unlinkToDeath(mBinderDeathHandler, 0); + } + mCallbacks.remove(callback); + } + + void removeAllCallbacksLocked() { + if (mBinderDeathHandler != null) { + for (IPredictionCallback callback : mCallbacks) { + callback.asBinder().unlinkToDeath(mBinderDeathHandler, 0); + } + } + mCallbacks.clear(); + } + + void resurrectSessionLocked(AppPredictionPerUserService service) { + if (service.isDebug()) { + Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked() + + ") for session Id=" + mSessionId + " and " + + mCallbacks.size() + " callbacks."); + } + service.onCreatePredictionSessionLocked(mContext, mSessionId); + for (IPredictionCallback callback : mCallbacks) { + service.registerPredictionUpdatesLocked(mSessionId, callback); + } + } + } } diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java index 19226be2e1ca..c82e7a012fff 100644 --- a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java +++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java @@ -42,6 +42,8 @@ public class RemoteAppPredictionService extends private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; + private final RemoteAppPredictionServiceCallbacks mCallback; + public RemoteAppPredictionService(Context context, String serviceInterface, ComponentName componentName, int userId, RemoteAppPredictionServiceCallbacks callback, boolean bindInstantServiceAllowed, @@ -50,6 +52,7 @@ public class RemoteAppPredictionService extends context.getMainThreadHandler(), bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0, verbose, /* initialCapacity= */ 1); + mCallback = callback; } @Override @@ -141,5 +144,17 @@ public class RemoteAppPredictionService extends * Notifies a the failure or timeout of a remote call. */ void onFailureOrTimeout(boolean timedOut); + + /** + * Notifies change in connected state of the remote service. + */ + void onConnectedStateChanged(boolean connected); + } + + @Override // from AbstractRemoteService + protected void handleOnConnectedStateChanged(boolean connected) { + if (mCallback != null) { + mCallback.onConnectedStateChanged(connected); + } } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 45f7360a2570..d6b4043af4fe 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -91,6 +91,7 @@ import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; +import android.net.NetworkMonitorManager; import android.net.NetworkPolicyManager; import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; @@ -169,6 +170,7 @@ import com.android.internal.util.MessageUtils; import com.android.internal.util.WakeupMessage; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; +import com.android.server.connectivity.AutodestructReference; import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; @@ -1785,8 +1787,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // caller type. Need to re-factor NetdEventListenerService to allow multiple // NetworkMonitor registrants. if (nai != null && nai.satisfies(mDefaultRequest)) { - Binder.withCleanCallingIdentity(() -> - nai.networkMonitor().notifyDnsResponse(returnCode)); + nai.networkMonitor().notifyDnsResponse(returnCode); } } @@ -2763,29 +2764,31 @@ public class ConnectivityService extends IConnectivityManager.Stub } private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub { - private final NetworkAgentInfo mNai; + private final int mNetId; + private final AutodestructReference<NetworkAgentInfo> mNai; private NetworkMonitorCallbacks(NetworkAgentInfo nai) { - mNai = nai; + mNetId = nai.network.netId; + mNai = new AutodestructReference(nai); } @Override public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) { mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, - new Pair<>(mNai, networkMonitor))); + new Pair<>(mNai.getAndDestroy(), networkMonitor))); } @Override public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) { mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED, - testResult, mNai.network.netId, redirectUrl)); + testResult, mNetId, redirectUrl)); } @Override public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) { mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage( EVENT_PRIVATE_DNS_CONFIG_RESOLVED, - 0, mNai.network.netId, PrivateDnsConfig.fromParcel(config))); + 0, mNetId, PrivateDnsConfig.fromParcel(config))); } @Override @@ -2803,15 +2806,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage( EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW, - mNai.network.netId, - pendingIntent)); + mNetId, pendingIntent)); } @Override public void hideProvisioningNotification() { mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage( - EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE, - mNai.network.netId)); + EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE, mNetId)); } @Override @@ -2853,11 +2854,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or // schedule DNS resolutions. If a DNS resolution is required the // result will be sent back to us. - try { - nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel()); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } + nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel()); // With Private DNS bypass support, we can proceed to update the // Private DNS config immediately, even if we're in strict mode @@ -3023,11 +3020,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Disable wakeup packet monitoring for each interface. wakeupModifyInterface(iface, nai.networkCapabilities, false); } - try { - nai.networkMonitor().notifyNetworkDisconnected(); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } + nai.networkMonitor().notifyNetworkDisconnected(); mNetworkAgentInfos.remove(nai.messenger); nai.clatd.update(); synchronized (mNetworkForNetId) { @@ -3440,11 +3433,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // // TODO: NetworkMonitor does not refer to the "never ask again" bit. The bit is stored // per network. Therefore, NetworkMonitor may still do https probe. - try { - nai.networkMonitor().setAcceptPartialConnectivity(); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } + nai.networkMonitor().setAcceptPartialConnectivity(); } } @@ -3476,11 +3465,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return; if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return; - try { - nai.networkMonitor().launchCaptivePortalApp(); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } + nai.networkMonitor().launchCaptivePortalApp(); }); } @@ -3515,7 +3500,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public void appResponse(final int response) throws RemoteException { + public void appResponse(final int response) { if (response == CaptivePortal.APP_RETURN_WANTED_AS_IS) { enforceSettingsPermission(); } @@ -3525,16 +3510,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai == null) return; // nai.networkMonitor() is thread-safe - final INetworkMonitor nm = nai.networkMonitor(); + final NetworkMonitorManager nm = nai.networkMonitor(); if (nm == null) return; - - final long token = Binder.clearCallingIdentity(); - try { - nm.notifyCaptivePortalAppFinished(response); - } finally { - // Not using Binder.withCleanCallingIdentity() to keep the checked RemoteException - Binder.restoreCallingIdentity(token); - } + nm.notifyCaptivePortalAppFinished(response); } @Override @@ -4105,11 +4083,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) { return; } - try { - nai.networkMonitor().forceReevaluation(uid); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } + nai.networkMonitor().forceReevaluation(uid); } /** @@ -5542,11 +5516,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Start or stop DNS64 detection and 464xlat according to network state. networkAgent.clatd.update(); notifyIfacesChangedForNetworkStats(); - try { - networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } + networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp); if (networkAgent.everConnected) { notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED); } @@ -6530,15 +6500,11 @@ public class ConnectivityService extends IConnectivityManager.Stub // command must be sent after updating LinkProperties to maximize chances of // NetworkMonitor seeing the correct LinkProperties when starting. // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call. - try { - if (networkAgent.networkMisc.acceptPartialConnectivity) { - networkAgent.networkMonitor().setAcceptPartialConnectivity(); - } - networkAgent.networkMonitor().notifyNetworkConnected( - networkAgent.linkProperties, networkAgent.networkCapabilities); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + if (networkAgent.networkMisc.acceptPartialConnectivity) { + networkAgent.networkMonitor().setAcceptPartialConnectivity(); } + networkAgent.networkMonitor().notifyNetworkConnected( + networkAgent.linkProperties, networkAgent.networkCapabilities); scheduleUnvalidatedPrompt(networkAgent); // Whether a particular NetworkRequest listen should cause signal strength thresholds to diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index d52fe8170358..ae04f76b95b4 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -82,6 +82,7 @@ import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.provider.Settings; +import android.stats.location.LocationStatsEnums; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -269,10 +270,14 @@ public class LocationManagerService extends ILocationManager.Stub { @PowerManager.LocationPowerSaveMode private int mBatterySaverMode; + @GuardedBy("mLock") + private final LocationUsageLogger mLocationUsageLogger; + public LocationManagerService(Context context) { super(); mContext = context; mHandler = FgThread.getHandler(); + mLocationUsageLogger = new LocationUsageLogger(); // Let the package manager query which are the default location // providers as they get certain permissions granted by default. @@ -2346,7 +2351,18 @@ public class LocationManagerService extends ILocationManager.Stub { * Method to be called when a record will no longer be used. */ private void disposeLocked(boolean removeReceiver) { - mRequestStatistics.stopRequesting(mReceiver.mCallerIdentity.mPackageName, mProvider); + String packageName = mReceiver.mCallerIdentity.mPackageName; + mRequestStatistics.stopRequesting(packageName, mProvider); + + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + LocationStatsEnums.API_REQUEST_LOCATION_UPDATES, + packageName, + mRealRequest, + mReceiver.isListener(), + mReceiver.isPendingIntent(), + /* radius= */ 0, + mActivityManager.getPackageImportance(packageName)); // remove from mRecordsByProvider ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider); @@ -2521,6 +2537,13 @@ public class LocationManagerService extends ILocationManager.Stub { "cannot register both listener and intent"); } + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + LocationStatsEnums.API_REQUEST_LOCATION_UPDATES, + packageName, request, listener != null, intent != null, + /* radius= */ 0, + mActivityManager.getPackageImportance(packageName)); + Receiver receiver; if (intent != null) { receiver = getReceiverLocked(intent, pid, uid, packageName, workSource, @@ -2813,6 +2836,18 @@ public class LocationManagerService extends ILocationManager.Stub { } long identity = Binder.clearCallingIdentity(); try { + synchronized (mLock) { + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + LocationStatsEnums.API_REQUEST_GEOFENCE, + packageName, + request, + /* hasListener= */ false, + intent != null, + geofence.getRadius(), + mActivityManager.getPackageImportance(packageName)); + } + mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel, uid, packageName); @@ -2833,6 +2868,17 @@ public class LocationManagerService extends ILocationManager.Stub { // geo-fence manager uses the public location API, need to clear identity long identity = Binder.clearCallingIdentity(); try { + synchronized (mLock) { + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + LocationStatsEnums.API_REQUEST_GEOFENCE, + packageName, + /* LocationRequest= */ null, + /* hasListener= */ false, + intent != null, + geofence.getRadius(), + mActivityManager.getPackageImportance(packageName)); + } mGeofenceManager.removeFence(geofence, intent); } finally { Binder.restoreCallingIdentity(identity); @@ -2916,6 +2962,20 @@ public class LocationManagerService extends ILocationManager.Stub { gnssDataListeners.put(binder, linkedListener); long identity = Binder.clearCallingIdentity(); try { + if (gnssDataProvider == mGnssNavigationMessageProvider + || gnssDataProvider == mGnssStatusProvider) { + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + gnssDataProvider == mGnssNavigationMessageProvider + ? LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER + : LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK, + packageName, + /* LocationRequest= */ null, + /* hasListener= */ true, + /* hasIntent= */ false, + /* radius */ 0, + mActivityManager.getPackageImportance(packageName)); + } if (isThrottlingExemptLocked(callerIdentity) || isImportanceForeground( mActivityManager.getPackageImportance(packageName))) { @@ -2941,6 +3001,26 @@ public class LocationManagerService extends ILocationManager.Stub { if (linkedListener == null) { return; } + long identity = Binder.clearCallingIdentity(); + try { + if (gnssDataProvider == mGnssNavigationMessageProvider + || gnssDataProvider == mGnssStatusProvider) { + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + gnssDataProvider == mGnssNavigationMessageProvider + ? LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER + : LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK, + linkedListener.mCallerIdentity.mPackageName, + /* LocationRequest= */ null, + /* hasListener= */ true, + /* hasIntent= */ false, + /* radius= */ 0, + mActivityManager.getPackageImportance( + linkedListener.mCallerIdentity.mPackageName)); + } + } finally { + Binder.restoreCallingIdentity(identity); + } unlinkFromListenerDeathNotificationLocked(binder, linkedListener); gnssDataProvider.removeListener(listener); } @@ -3026,6 +3106,11 @@ public class LocationManagerService extends ILocationManager.Stub { checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(), providerName); + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + LocationStatsEnums.API_SEND_EXTRA_COMMAND, + providerName); + // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) != PERMISSION_GRANTED)) { @@ -3037,6 +3122,11 @@ public class LocationManagerService extends ILocationManager.Stub { provider.sendExtraCommandLocked(command, extras); } + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + LocationStatsEnums.API_SEND_EXTRA_COMMAND, + providerName); + return true; } } diff --git a/services/core/java/com/android/server/LocationUsageLogger.java b/services/core/java/com/android/server/LocationUsageLogger.java new file mode 100644 index 000000000000..c5030351f69d --- /dev/null +++ b/services/core/java/com/android/server/LocationUsageLogger.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.ActivityManager; +import android.location.LocationManager; +import android.location.LocationRequest; +import android.os.SystemClock; +import android.stats.location.LocationStatsEnums; +import android.util.Log; +import android.util.StatsLog; + +import java.time.Instant; + +/** + * Logger for Location API usage logging. + */ +class LocationUsageLogger { + private static final String TAG = "LocationUsageLogger"; + private static final boolean D = Log.isLoggable(TAG, Log.DEBUG); + + private static final int ONE_SEC_IN_MILLIS = 1000; + private static final int ONE_MINUTE_IN_MILLIS = 60000; + private static final int ONE_HOUR_IN_MILLIS = 3600000; + + private long mLastApiUsageLogHour = 0; + + private int mApiUsageLogHourlyCount = 0; + + private static final int API_USAGE_LOG_HOURLY_CAP = 60; + + private static int providerNameToStatsdEnum(String provider) { + if (LocationManager.NETWORK_PROVIDER.equals(provider)) { + return LocationStatsEnums.PROVIDER_NETWORK; + } else if (LocationManager.GPS_PROVIDER.equals(provider)) { + return LocationStatsEnums.PROVIDER_GPS; + } else if (LocationManager.PASSIVE_PROVIDER.equals(provider)) { + return LocationStatsEnums.PROVIDER_PASSIVE; + } else if (LocationManager.FUSED_PROVIDER.equals(provider)) { + return LocationStatsEnums.PROVIDER_FUSED; + } else { + return LocationStatsEnums.PROVIDER_UNKNOWN; + } + } + + private static int bucketizeIntervalToStatsdEnum(long interval) { + // LocationManager already converts negative values to 0. + if (interval < ONE_SEC_IN_MILLIS) { + return LocationStatsEnums.INTERVAL_BETWEEN_0_SEC_AND_1_SEC; + } else if (interval < ONE_SEC_IN_MILLIS * 5) { + return LocationStatsEnums.INTERVAL_BETWEEN_1_SEC_AND_5_SEC; + } else if (interval < ONE_MINUTE_IN_MILLIS) { + return LocationStatsEnums.INTERVAL_BETWEEN_5_SEC_AND_1_MIN; + } else if (interval < ONE_MINUTE_IN_MILLIS * 10) { + return LocationStatsEnums.INTERVAL_BETWEEN_1_MIN_AND_10_MIN; + } else if (interval < ONE_HOUR_IN_MILLIS) { + return LocationStatsEnums.INTERVAL_BETWEEN_10_MIN_AND_1_HOUR; + } else { + return LocationStatsEnums.INTERVAL_LARGER_THAN_1_HOUR; + } + } + + private static int bucketizeSmallestDisplacementToStatsdEnum(float smallestDisplacement) { + // LocationManager already converts negative values to 0. + if (smallestDisplacement == 0) { + return LocationStatsEnums.DISTANCE_ZERO; + } else if (smallestDisplacement > 0 && smallestDisplacement <= 100) { + return LocationStatsEnums.DISTANCE_BETWEEN_0_AND_100; + } else { + return LocationStatsEnums.DISTANCE_LARGER_THAN_100; + } + } + + private static int bucketizeRadiusToStatsdEnum(float radius) { + if (radius < 0) { + return LocationStatsEnums.RADIUS_NEGATIVE; + } else if (radius < 100) { + return LocationStatsEnums.RADIUS_BETWEEN_0_AND_100; + } else if (radius < 200) { + return LocationStatsEnums.RADIUS_BETWEEN_100_AND_200; + } else if (radius < 300) { + return LocationStatsEnums.RADIUS_BETWEEN_200_AND_300; + } else if (radius < 1000) { + return LocationStatsEnums.RADIUS_BETWEEN_300_AND_1000; + } else if (radius < 10000) { + return LocationStatsEnums.RADIUS_BETWEEN_1000_AND_10000; + } else { + return LocationStatsEnums.RADIUS_LARGER_THAN_100000; + } + } + + private static int getBucketizedExpireIn(long expireAt) { + if (expireAt == Long.MAX_VALUE) { + return LocationStatsEnums.EXPIRATION_NO_EXPIRY; + } + + long elapsedRealtime = SystemClock.elapsedRealtime(); + long expireIn = Math.max(0, expireAt - elapsedRealtime); + + if (expireIn < 20 * ONE_SEC_IN_MILLIS) { + return LocationStatsEnums.EXPIRATION_BETWEEN_0_AND_20_SEC; + } else if (expireIn < ONE_MINUTE_IN_MILLIS) { + return LocationStatsEnums.EXPIRATION_BETWEEN_20_SEC_AND_1_MIN; + } else if (expireIn < ONE_MINUTE_IN_MILLIS * 10) { + return LocationStatsEnums.EXPIRATION_BETWEEN_1_MIN_AND_10_MIN; + } else if (expireIn < ONE_HOUR_IN_MILLIS) { + return LocationStatsEnums.EXPIRATION_BETWEEN_10_MIN_AND_1_HOUR; + } else { + return LocationStatsEnums.EXPIRATION_LARGER_THAN_1_HOUR; + } + } + + private static int categorizeActivityImportance(int importance) { + if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return LocationStatsEnums.IMPORTANCE_TOP; + } else if (importance == ActivityManager + .RunningAppProcessInfo + .IMPORTANCE_FOREGROUND_SERVICE) { + return LocationStatsEnums.IMPORTANCE_FORGROUND_SERVICE; + } else { + return LocationStatsEnums.IMPORTANCE_BACKGROUND; + } + } + + private static int getCallbackType( + int apiType, boolean hasListener, boolean hasIntent) { + if (apiType == LocationStatsEnums.API_SEND_EXTRA_COMMAND) { + return LocationStatsEnums.CALLBACK_NOT_APPLICABLE; + } + + // Listener and PendingIntent will not be set at + // the same time. + if (hasIntent) { + return LocationStatsEnums.CALLBACK_PENDING_INTENT; + } else if (hasListener) { + return LocationStatsEnums.CALLBACK_LISTENER; + } else { + return LocationStatsEnums.CALLBACK_UNKNOWN; + } + } + + // Update the hourly count of APIUsage log event. + // Returns false if hit the hourly log cap. + private boolean checkApiUsageLogCap() { + if (D) { + Log.d(TAG, "checking APIUsage log cap."); + } + + long currentHour = Instant.now().toEpochMilli() / ONE_HOUR_IN_MILLIS; + if (currentHour > mLastApiUsageLogHour) { + mLastApiUsageLogHour = currentHour; + mApiUsageLogHourlyCount = 0; + return true; + } else { + mApiUsageLogHourlyCount = Math.min( + mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP); + return mApiUsageLogHourlyCount < API_USAGE_LOG_HOURLY_CAP; + } + } + + /** + * Log a Location API usage event to Statsd. + * Logging event is capped at 60 per hour. Usage events exceeding + * the cap will be dropped by LocationUsageLogger. + */ + public void logLocationApiUsage(int usageType, int apiInUse, + String packageName, LocationRequest locationRequest, + boolean hasListener, boolean hasIntent, + float radius, int activityImportance) { + try { + if (!checkApiUsageLogCap()) { + return; + } + + boolean isLocationRequestNull = locationRequest == null; + if (D) { + Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: " + + apiInUse + ", packageName: " + (packageName == null ? "" : packageName) + + ", locationRequest: " + + (isLocationRequestNull ? "" : locationRequest.toString()) + + ", hasListener: " + hasListener + + ", hasIntent: " + hasIntent + + ", radius: " + radius + + ", importance: " + activityImportance); + } + + StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, + apiInUse, packageName, + isLocationRequestNull + ? LocationStatsEnums.PROVIDER_UNKNOWN + : providerNameToStatsdEnum(locationRequest.getProvider()), + isLocationRequestNull + ? LocationStatsEnums.QUALITY_UNKNOWN + : locationRequest.getQuality(), + isLocationRequestNull + ? LocationStatsEnums.INTERVAL_UNKNOWN + : bucketizeIntervalToStatsdEnum(locationRequest.getInterval()), + isLocationRequestNull + ? LocationStatsEnums.DISTANCE_UNKNOWN + : bucketizeSmallestDisplacementToStatsdEnum( + locationRequest.getSmallestDisplacement()), + isLocationRequestNull ? 0 : locationRequest.getNumUpdates(), + // only log expireIn for USAGE_STARTED + isLocationRequestNull || usageType == LocationStatsEnums.USAGE_ENDED + ? LocationStatsEnums.EXPIRATION_UNKNOWN + : getBucketizedExpireIn(locationRequest.getExpireAt()), + getCallbackType(apiInUse, hasListener, hasIntent), + bucketizeRadiusToStatsdEnum(radius), + categorizeActivityImportance(activityImportance)); + } catch (Exception e) { + // Swallow exceptions to avoid crashing LMS. + Log.w(TAG, "Failed to log API usage to statsd.", e); + } + } + + /** + * Log a Location API usage event to Statsd. + * Logging event is capped at 60 per hour. Usage events exceeding + * the cap will be dropped by LocationUsageLogger. + */ + public void logLocationApiUsage(int usageType, int apiInUse, String providerName) { + try { + if (!checkApiUsageLogCap()) { + return; + } + + if (D) { + Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: " + + apiInUse + ", providerName: " + providerName); + } + + StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, apiInUse, + /* package_name= */ null, + providerNameToStatsdEnum(providerName), + LocationStatsEnums.QUALITY_UNKNOWN, + LocationStatsEnums.INTERVAL_UNKNOWN, + LocationStatsEnums.DISTANCE_UNKNOWN, + /* numUpdates= */ 0, + LocationStatsEnums.EXPIRATION_UNKNOWN, + getCallbackType( + apiInUse, + /* isListenerNull= */ true, + /* isIntentNull= */ true), + /* bucketizedRadius= */ 0, + LocationStatsEnums.IMPORTANCE_UNKNOWN); + } catch (Exception e) { + // Swallow exceptions to avoid crashing LMS. + Log.w(TAG, "Failed to log API usage to statsd.", e); + } + } +} diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 32671144aee3..dee89e5d5c4d 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; +import android.net.NetworkStackClient; import android.os.Environment; import android.os.Handler; import android.os.Looper; @@ -115,6 +116,7 @@ public class PackageWatchdog { // File containing the XML data of monitored packages /data/system/package-watchdog.xml private final AtomicFile mPolicyFile; private final ExplicitHealthCheckController mHealthCheckController; + private final NetworkStackClient mNetworkStackClient; @GuardedBy("mLock") private boolean mIsPackagesReady; // Flag to control whether explicit health checks are supported or not @@ -135,7 +137,8 @@ public class PackageWatchdog { new File(new File(Environment.getDataDirectory(), "system"), "package-watchdog.xml")), new Handler(Looper.myLooper()), BackgroundThread.getHandler(), - new ExplicitHealthCheckController(context)); + new ExplicitHealthCheckController(context), + NetworkStackClient.getInstance()); } /** @@ -143,12 +146,14 @@ public class PackageWatchdog { */ @VisibleForTesting PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, - Handler longTaskHandler, ExplicitHealthCheckController controller) { + Handler longTaskHandler, ExplicitHealthCheckController controller, + NetworkStackClient networkStackClient) { mContext = context; mPolicyFile = policyFile; mShortTaskHandler = shortTaskHandler; mLongTaskHandler = longTaskHandler; mHealthCheckController = controller; + mNetworkStackClient = networkStackClient; loadFromFile(); } @@ -174,6 +179,7 @@ public class PackageWatchdog { () -> syncRequestsAsync()); setPropertyChangedListenerLocked(); updateConfigs(); + registerNetworkStackHealthListener(); } } @@ -630,29 +636,40 @@ public class PackageWatchdog { synchronized (mLock) { PackageHealthObserver registeredObserver = observer.mRegisteredObserver; if (registeredObserver != null) { - PackageManager pm = mContext.getPackageManager(); Iterator<MonitoredPackage> it = failedPackages.iterator(); while (it.hasNext()) { String failedPackage = it.next().getName(); - long versionCode = 0; Slog.i(TAG, "Explicit health check failed for package " + failedPackage); - try { - versionCode = pm.getPackageInfo( - failedPackage, 0 /* flags */).getLongVersionCode(); - } catch (PackageManager.NameNotFoundException e) { + VersionedPackage versionedPkg = getVersionedPackage(failedPackage); + if (versionedPkg == null) { Slog.w(TAG, "Explicit health check failed but could not find package " + failedPackage); // TODO(b/120598832): Skip. We only continue to pass tests for now since // the tests don't install any packages + versionedPkg = new VersionedPackage(failedPackage, 0L); } - registeredObserver.execute( - new VersionedPackage(failedPackage, versionCode)); + registeredObserver.execute(versionedPkg); } } } }); } + @Nullable + private VersionedPackage getVersionedPackage(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + if (pm == null) { + return null; + } + try { + final long versionCode = pm.getPackageInfo( + packageName, 0 /* flags */).getLongVersionCode(); + return new VersionedPackage(packageName, versionCode); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + /** * Loads mAllObservers from file. * @@ -726,6 +743,27 @@ public class PackageWatchdog { } } + private void registerNetworkStackHealthListener() { + // TODO: have an internal method to trigger a rollback by reporting high severity errors, + // and rely on ActivityManager to inform the watchdog of severe network stack crashes + // instead of having this listener in parallel. + mNetworkStackClient.registerHealthListener( + packageName -> { + final VersionedPackage pkg = getVersionedPackage(packageName); + if (pkg == null) { + Slog.wtf(TAG, "NetworkStack failed but could not find its package"); + return; + } + // This is a severe failure and recovery should be attempted immediately. + // TODO: have a better way to handle such failures. + final List<VersionedPackage> pkgList = Collections.singletonList(pkg); + final long failureCount = getTriggerFailureCount(); + for (int i = 0; i < failureCount; i++) { + onPackageFailure(pkgList); + } + }); + } + /** * Persists mAllObservers to file. Threshold information is ignored. */ diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 90266f12a5dd..fae853c52c01 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3250,6 +3250,7 @@ public final class ActiveServices { int memFactor = mAm.mProcessStats.getMemFactorLocked(); long now = SystemClock.uptimeMillis(); r.tracker.setExecuting(false, memFactor, now); + r.tracker.setForeground(false, memFactor, now); r.tracker.setBound(false, memFactor, now); r.tracker.setStarted(false, memFactor, now); } @@ -3293,8 +3294,10 @@ public final class ActiveServices { } r.executeFg = false; if (r.tracker != null) { - r.tracker.setExecuting(false, mAm.mProcessStats.getMemFactorLocked(), - SystemClock.uptimeMillis()); + final int memFactor = mAm.mProcessStats.getMemFactorLocked(); + final long now = SystemClock.uptimeMillis(); + r.tracker.setExecuting(false, memFactor, now); + r.tracker.setForeground(false, memFactor, now); if (finishing) { r.tracker.clearCurrentOwner(r, false); r.tracker = null; diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index a0522e3971e7..fcd6a0aacd92 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -241,15 +241,6 @@ import com.android.internal.annotations.GuardedBy; sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); } - /*package*/ void postBluetoothA2dpDeviceConfigChangeExt( - @NonNull BluetoothDevice device, - @AudioService.BtProfileConnectionState int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { - final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile, - suppressNoisyIntent, a2dpVolume); - sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT, SENDMSG_QUEUE, info); - } - private static final class HearingAidDeviceConnectionInfo { final @NonNull BluetoothDevice mDevice; final @AudioService.BtProfileConnectionState int mState; @@ -862,22 +853,6 @@ import com.android.internal.annotations.GuardedBy; info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice); } } break; - case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT: { - final BtDeviceConnectionInfo info = (BtDeviceConnectionInfo) msg.obj; - AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( - "handleBluetoothA2dpActiveDeviceChangeExt " - + " state=" + info.mState - // only querying address as this is the only readily available - // field on the device - + " addr=" + info.mDevice.getAddress() - + " prof=" + info.mProfile + " supprNoisy=" + info.mSupprNoisy - + " vol=" + info.mVolume)).printLog(TAG)); - synchronized (mDeviceStateLock) { - mDeviceInventory.handleBluetoothA2dpActiveDeviceChangeExt( - info.mDevice, info.mState, info.mProfile, - info.mSupprNoisy, info.mVolume); - } - } break; default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -925,10 +900,8 @@ import com.android.internal.annotations.GuardedBy; private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 27; // process external command to (dis)connect a hearing aid device private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 28; - // process external command to (dis)connect or change active A2DP device - private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT = 29; // a ScoClient died in BtHelper - private static final int MSG_L_SCOCLIENT_DIED = 30; + private static final int MSG_L_SCOCLIENT_DIED = 29; private static boolean isMessageHandledUnderWakelock(int msgId) { @@ -943,7 +916,6 @@ import com.android.internal.annotations.GuardedBy; case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: - case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT: return true; default: return false; diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 887c90873bdd..99b97cbf7dbc 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -570,49 +570,6 @@ public final class AudioDeviceInventory { } } - /*package*/ void handleBluetoothA2dpActiveDeviceChangeExt( - @NonNull BluetoothDevice device, - @AudioService.BtProfileConnectionState int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { - if (state == BluetoothProfile.STATE_DISCONNECTED) { - mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - device, state, profile, suppressNoisyIntent, a2dpVolume); - return; - } - // state == BluetoothProfile.STATE_CONNECTED - synchronized (mConnectedDevices) { - final String address = device.getAddress(); - final int a2dpCodec = mDeviceBroker.getA2dpCodec(device); - final String deviceKey = DeviceInfo.makeDeviceListKey( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); - DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey); - if (deviceInfo != null) { - // Device config change for matching A2DP device - mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); - return; - } - for (int i = 0; i < mConnectedDevices.size(); i++) { - deviceInfo = mConnectedDevices.valueAt(i); - if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - continue; - } - // A2DP device exists, handle active device change - final String existingDevicekey = mConnectedDevices.keyAt(i); - mConnectedDevices.remove(existingDevicekey); - mConnectedDevices.put(deviceKey, new DeviceInfo( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, BtHelper.getName(device), - address, a2dpCodec)); - mDeviceBroker.postA2dpActiveDeviceChange( - new BtHelper.BluetoothA2dpDeviceInfo( - device, a2dpVolume, a2dpCodec)); - return; - } - } - // New A2DP device connection - mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - device, state, profile, suppressNoisyIntent, a2dpVolume); - } - /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, String address, String name, String caller) { synchronized (mConnectedDevices) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 15e8851a6a2b..30035c8c365b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4382,27 +4382,6 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); } - /** - * @see AudioManager#handleBluetoothA2dpActiveDeviceChange(BluetoothDevice, int, int, - * boolean, int) - */ - public void handleBluetoothA2dpActiveDeviceChange( - BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, - int a2dpVolume) { - if (device == null) { - throw new IllegalArgumentException("Illegal null device"); - } - if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { - throw new IllegalArgumentException("invalid profile " + profile); - } - if (state != BluetoothProfile.STATE_CONNECTED - && state != BluetoothProfile.STATE_DISCONNECTED) { - throw new IllegalArgumentException("Invalid state " + state); - } - mDeviceBroker.postBluetoothA2dpDeviceConfigChangeExt(device, state, profile, - suppressNoisyIntent, a2dpVolume); - } - private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | AudioSystem.DEVICE_OUT_LINE | diff --git a/services/core/java/com/android/server/connectivity/AutodestructReference.java b/services/core/java/com/android/server/connectivity/AutodestructReference.java new file mode 100644 index 000000000000..009a43e58285 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/AutodestructReference.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.annotation.NonNull; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * A ref that autodestructs at the first usage of it. + * @param <T> The type of the held object + * @hide + */ +public class AutodestructReference<T> { + private final AtomicReference<T> mHeld; + public AutodestructReference(@NonNull T obj) { + if (null == obj) throw new NullPointerException("Autodestruct reference to null"); + mHeld = new AtomicReference<>(obj); + } + + /** Get the ref and destruct it. NPE if already destructed. */ + @NonNull + public T getAndDestroy() { + final T obj = mHeld.getAndSet(null); + if (null == obj) throw new NullPointerException("Already autodestructed"); + return obj; + } +} diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 34772d062fd2..864a793b8f40 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -25,12 +25,12 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMisc; +import android.net.NetworkMonitorManager; import android.net.NetworkRequest; import android.net.NetworkState; import android.os.Handler; import android.os.INetworkManagementService; import android.os.Messenger; -import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; @@ -247,7 +247,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public final Nat464Xlat clatd; // Set after asynchronous creation of the NetworkMonitor. - private volatile INetworkMonitor mNetworkMonitor; + private volatile NetworkMonitorManager mNetworkMonitor; private static final String TAG = ConnectivityService.class.getSimpleName(); private static final boolean VDBG = false; @@ -278,7 +278,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { * Inform NetworkAgentInfo that a new NetworkMonitor was created. */ public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) { - mNetworkMonitor = networkMonitor; + mNetworkMonitor = new NetworkMonitorManager(networkMonitor); } /** @@ -290,13 +290,9 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { */ public void setNetworkCapabilities(NetworkCapabilities nc) { networkCapabilities = nc; - final INetworkMonitor nm = mNetworkMonitor; + final NetworkMonitorManager nm = mNetworkMonitor; if (nm != null) { - try { - nm.notifyNetworkCapabilitiesChanged(nc); - } catch (RemoteException e) { - Log.e(TAG, "Error notifying NetworkMonitor of updated NetworkCapabilities", e); - } + nm.notifyNetworkCapabilitiesChanged(nc); } } @@ -317,11 +313,11 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } /** - * Get the INetworkMonitor in this NetworkAgentInfo. + * Get the NetworkMonitorManager in this NetworkAgentInfo. * * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called. */ - public INetworkMonitor networkMonitor() { + public NetworkMonitorManager networkMonitor() { return mNetworkMonitor; } diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 0910dac27337..f6735d983466 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -237,9 +237,15 @@ public class NetworkNotificationManager { + getTransportName(transportType)); return; } - - final String channelId = highPriority ? SystemNotificationChannels.NETWORK_ALERTS : - SystemNotificationChannels.NETWORK_STATUS; + // When replacing an existing notification for a given network, don't alert, just silently + // update the existing notification. Note that setOnlyAlertOnce() will only work for the + // same id, and the id used here is the NotificationType which is different in every type of + // notification. This is required because the notification metrics only track the ID but not + // the tag. + final boolean hasPreviousNotification = previousNotifyType != null; + final String channelId = (highPriority && !hasPreviousNotification) + ? SystemNotificationChannels.NETWORK_ALERTS + : SystemNotificationChannels.NETWORK_STATUS; Notification.Builder builder = new Notification.Builder(mContext, channelId) .setWhen(System.currentTimeMillis()) .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH) diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 0271d3bcb57c..40a4820355ad 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -953,30 +953,21 @@ public class Vpn { return false; } - LinkProperties lp = makeLinkProperties(); - final boolean hadInternetCapability = mNetworkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_INTERNET); - final boolean willHaveInternetCapability = providesRoutesToMostDestinations(lp); - if (hadInternetCapability != willHaveInternetCapability) { - // A seamless handover would have led to a change to INTERNET capability, which - // is supposed to be immutable for a given network. In this case bail out and do not - // perform handover. - Log.i(TAG, "Handover not possible due to changes to INTERNET capability"); - return false; - } - - agent.sendLinkProperties(lp); + agent.sendLinkProperties(makeLinkProperties()); return true; } private void agentConnect() { LinkProperties lp = makeLinkProperties(); - if (providesRoutesToMostDestinations(lp)) { - mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - } else { - mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - } + // VPN either provide a default route (IPv4 or IPv6 or both), or they are a split tunnel + // that falls back to the default network, which by definition provides INTERNET (unless + // there is no default network, in which case none of this matters in any sense). + // Also, it guarantees that when a VPN applies to an app, the VPN will always be reported + // as the network by getDefaultNetwork and registerDefaultNetworkCallback. This in turn + // protects the invariant that apps calling CM#bindProcessToNetwork(getDefaultNetwork()) + // the same as if they use the default network. + mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null); @@ -1846,10 +1837,11 @@ public class Vpn { if (!profile.searchDomains.isEmpty()) { config.searchDomains = Arrays.asList(profile.searchDomains.split(" +")); } - startLegacyVpn(config, racoon, mtpd); + startLegacyVpn(config, racoon, mtpd, profile); } - private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { + private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd, + VpnProfile profile) { stopLegacyVpnPrivileged(); // Prepare for the new request. @@ -1857,7 +1849,7 @@ public class Vpn { updateState(DetailedState.CONNECTING, "startLegacyVpn"); // Start a new LegacyVpnRunner and we are done! - mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd); + mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile); mLegacyVpnRunner.start(); } @@ -1923,6 +1915,7 @@ public class Vpn { private final String mOuterInterface; private final AtomicInteger mOuterConnection = new AtomicInteger(ConnectivityManager.TYPE_NONE); + private final VpnProfile mProfile; private long mBringupStartTime = -1; @@ -1949,7 +1942,7 @@ public class Vpn { } }; - public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) { + LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) { super(TAG); mConfig = config; mDaemons = new String[] {"racoon", "mtpd"}; @@ -1965,6 +1958,8 @@ public class Vpn { // registering mOuterInterface = mConfig.interfaze; + mProfile = profile; + if (!TextUtils.isEmpty(mOuterInterface)) { final ConnectivityManager cm = ConnectivityManager.from(mContext); for (Network network : cm.getAllNetworks()) { @@ -2177,7 +2172,7 @@ public class Vpn { } // Add a throw route for the VPN server endpoint, if one was specified. - String endpoint = parameters[5]; + String endpoint = parameters[5].isEmpty() ? mProfile.server : parameters[5]; if (!endpoint.isEmpty()) { try { InetAddress addr = InetAddress.parseNumericAddress(endpoint); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 5fb67ddc1266..5804fc8ba72f 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -309,6 +309,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean mAppliedTemporaryAutoBrightnessAdjustment; private boolean mAppliedBrightnessBoost; + // Reason for which the brightness was last changed. See {@link BrightnessReason} for more + // information. + // At the time of this writing, this value is changed within updatePowerState() only, which is + // limited to the thread used by DisplayControllerHandler. + private BrightnessReason mBrightnessReason = new BrightnessReason(); + private BrightnessReason mBrightnessReasonTemp = new BrightnessReason(); + // Brightness animation ramp rates in brightness units per second private final int mBrightnessRampRateFast; private final int mBrightnessRampRateSlow; @@ -733,6 +740,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final boolean mustNotify; final int previousPolicy; boolean mustInitialize = false; + int brightnessAdjustmentFlags = 0; + mBrightnessReasonTemp.set(null); synchronized (mLock) { mPendingUpdatePowerStateLocked = false; @@ -786,6 +795,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } if (!mAllowAutoBrightnessWhileDozingConfig) { brightness = mPowerRequest.dozeScreenBrightness; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE); } break; case DisplayPowerRequest.POLICY_VR: @@ -839,15 +849,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Use zero brightness when screen is off. if (state == Display.STATE_OFF) { brightness = PowerManager.BRIGHTNESS_OFF; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF); } // Always use the VR brightness when in the VR state. if (state == Display.STATE_VR) { brightness = mScreenBrightnessForVr; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR); } if (brightness < 0 && mPowerRequest.screenBrightnessOverride > 0) { brightness = mPowerRequest.screenBrightnessOverride; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE); mAppliedScreenBrightnessOverride = true; } else { mAppliedScreenBrightnessOverride = false; @@ -867,6 +880,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mTemporaryScreenBrightness > 0) { brightness = mTemporaryScreenBrightness; mAppliedTemporaryBrightness = true; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY); } else { mAppliedTemporaryBrightness = false; } @@ -880,9 +894,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final float autoBrightnessAdjustment; if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) { autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment; + brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP; mAppliedTemporaryAutoBrightnessAdjustment = true; } else { autoBrightnessAdjustment = mAutoBrightnessAdjustment; + brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO; mAppliedTemporaryAutoBrightnessAdjustment = false; } @@ -893,6 +909,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mPowerRequest.boostScreenBrightness && brightness != PowerManager.BRIGHTNESS_OFF) { brightness = PowerManager.BRIGHTNESS_ON; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST); mAppliedBrightnessBoost = true; } else { mAppliedBrightnessBoost = false; @@ -936,6 +953,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // it means in absolute terms. putScreenBrightnessSetting(brightness); mAppliedAutoBrightness = true; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC); } else { mAppliedAutoBrightness = false; } @@ -943,19 +961,25 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // If the autobrightness controller has decided to change the adjustment value // used, make sure that's reflected in settings. putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment); + } else { + // Adjustment values resulted in no change + brightnessAdjustmentFlags = 0; } } else { mAppliedAutoBrightness = false; + brightnessAdjustmentFlags = 0; } // Use default brightness when dozing unless overridden. if (brightness < 0 && Display.isDozeState(state)) { brightness = mScreenBrightnessDozeConfig; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); } // Apply manual brightness. if (brightness < 0) { brightness = clampScreenBrightness(mCurrentScreenBrightnessSetting); + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL); } // Apply dimming by at least some minimum amount when user activity @@ -964,6 +988,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (brightness > mScreenBrightnessRangeMinimum) { brightness = Math.max(Math.min(brightness - SCREEN_DIM_MINIMUM_REDUCTION, mScreenBrightnessDimConfig), mScreenBrightnessRangeMinimum); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED); } if (!mAppliedDimming) { slowChange = false; @@ -982,6 +1007,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1); final int lowPowerBrightness = (int) (brightness * brightnessFactor); brightness = Math.max(lowPowerBrightness, mScreenBrightnessRangeMinimum); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER); } if (!mAppliedLowPower) { slowChange = false; @@ -1047,6 +1073,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } + // Log any changes to what is currently driving the brightness setting. + if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) { + Slog.v(TAG, "Brightness [" + brightness + "] reason changing to: '" + + mBrightnessReasonTemp.toString(brightnessAdjustmentFlags) + + "', previous reason: '" + mBrightnessReason + "'."); + mBrightnessReason.set(mBrightnessReasonTemp); + } + // Update display white-balance. if (mDisplayWhiteBalanceController != null) { if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) { @@ -1737,6 +1771,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mPendingScreenBrightnessSetting=" + mPendingScreenBrightnessSetting); pw.println(" mTemporaryScreenBrightness=" + mTemporaryScreenBrightness); pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); + pw.println(" mBrightnessReason=" + mBrightnessReason); pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment); pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment); pw.println(" mScreenBrightnessForVr=" + mScreenBrightnessForVr); @@ -1956,4 +1991,121 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call sendUpdatePowerState(); } } + + /** + * Stores data about why the brightness was changed. Made up of one main + * {@code BrightnessReason.REASON_*} reason and various {@code BrightnessReason.MODIFIER_*} + * modifiers. + */ + private final class BrightnessReason { + static final int REASON_UNKNOWN = 0; + static final int REASON_MANUAL = 1; + static final int REASON_DOZE = 2; + static final int REASON_DOZE_DEFAULT = 3; + static final int REASON_AUTOMATIC = 4; + static final int REASON_SCREEN_OFF = 5; + static final int REASON_VR = 6; + static final int REASON_OVERRIDE = 7; + static final int REASON_TEMPORARY = 8; + static final int REASON_BOOST = 9; + static final int REASON_MAX = REASON_BOOST; + + static final int MODIFIER_DIMMED = 0x1; + static final int MODIFIER_LOW_POWER = 0x2; + static final int MODIFIER_MASK = 0x3; + + // ADJUSTMENT_* + // These things can happen at any point, even if the main brightness reason doesn't + // fundamentally change, so they're not stored. + + // Auto-brightness adjustment factor changed + static final int ADJUSTMENT_AUTO_TEMP = 0x1; + // Temporary adjustment to the auto-brightness adjustment factor. + static final int ADJUSTMENT_AUTO = 0x2; + + // One of REASON_* + public int reason; + // Any number of MODIFIER_* + public int modifier; + + public void set(BrightnessReason other) { + setReason(other == null ? REASON_UNKNOWN : other.reason); + setModifier(other == null ? 0 : other.modifier); + } + + public void setReason(int reason) { + if (reason < REASON_UNKNOWN || reason > REASON_MAX) { + Slog.w(TAG, "brightness reason out of bounds: " + reason); + } else { + this.reason = reason; + } + } + + public void setModifier(int modifier) { + if ((modifier & ~MODIFIER_MASK) != 0) { + Slog.w(TAG, "brightness modifier out of bounds: 0x" + + Integer.toHexString(modifier)); + } else { + this.modifier = modifier; + } + } + + public void addModifier(int modifier) { + setModifier(modifier | this.modifier); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof BrightnessReason)) { + return false; + } + BrightnessReason other = (BrightnessReason) obj; + return other.reason == reason && other.modifier == modifier; + } + + @Override + public String toString() { + return toString(0); + } + + public String toString(int adjustments) { + final StringBuilder sb = new StringBuilder(); + sb.append(reasonToString(reason)); + sb.append(" ["); + if ((adjustments & ADJUSTMENT_AUTO_TEMP) != 0) { + sb.append(" temp_adj"); + } + if ((adjustments & ADJUSTMENT_AUTO) != 0) { + sb.append(" auto_adj"); + } + if ((modifier & MODIFIER_LOW_POWER) != 0) { + sb.append(" low_pwr"); + } + if ((modifier & MODIFIER_DIMMED) != 0) { + sb.append(" dim"); + } + int strlen = sb.length(); + if (sb.charAt(strlen - 1) == '[') { + sb.setLength(strlen - 2); + } else { + sb.append(" ]"); + } + return sb.toString(); + } + + private String reasonToString(int reason) { + switch (reason) { + case REASON_MANUAL: return "manual"; + case REASON_DOZE: return "doze"; + case REASON_DOZE_DEFAULT: return "doze_default"; + case REASON_AUTOMATIC: return "automatic"; + case REASON_SCREEN_OFF: return "screen_off"; + case REASON_VR: return "vr"; + case REASON_OVERRIDE: return "override"; + case REASON_TEMPORARY: return "temporary"; + case REASON_BOOST: return "boost"; + default: return Integer.toString(reason); + } + } + } } diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index b2420b5b320e..64a9e0074cb1 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -394,7 +394,9 @@ public final class ColorDisplayService extends SystemService { private void tearDown() { Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser); - getContext().getContentResolver().unregisterContentObserver(mContentObserver); + if (mContentObserver != null) { + getContext().getContentResolver().unregisterContentObserver(mContentObserver); + } if (mNightDisplayTintController.isAvailable(getContext())) { if (mNightDisplayAutoMode != null) { diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java index 8d4ad7f30821..65bd5c6a14da 100644 --- a/services/core/java/com/android/server/location/GnssVisibilityControl.java +++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java @@ -456,9 +456,8 @@ class GnssVisibilityControl { final String proxyAppPkgName = nfwNotification.mProxyAppPackageName; final ProxyAppState proxyAppState = mProxyAppsState.get(proxyAppPkgName); final boolean isLocationRequestAccepted = nfwNotification.isRequestAccepted(); - final boolean isPermissionMismatched = - (proxyAppState == null) ? isLocationRequestAccepted - : (proxyAppState.mHasLocationPermission != isLocationRequestAccepted); + final boolean isPermissionMismatched = isPermissionMismatched(proxyAppState, + nfwNotification); logEvent(nfwNotification, isPermissionMismatched); if (!nfwNotification.isRequestAttributedToProxyApp()) { @@ -506,14 +505,24 @@ class GnssVisibilityControl { // Log proxy app permission mismatch between framework and GNSS HAL. if (isPermissionMismatched) { - Log.w(TAG, "Permission mismatch. Framework proxy app " + proxyAppPkgName + Log.w(TAG, "Permission mismatch. Proxy app " + proxyAppPkgName + " location permission is set to " + proxyAppState.mHasLocationPermission + + " and GNSS HAL enabled is set to " + mIsGpsEnabled + " but GNSS non-framework location access response type is " + nfwNotification.getResponseTypeAsString() + " for notification: " + nfwNotification); } } + private boolean isPermissionMismatched(ProxyAppState proxyAppState, + NfwNotification nfwNotification) { + // Non-framework non-emergency location requests must be accepted only when IGnss.hal + // is enabled and the proxy app has location permission. + final boolean isLocationRequestAccepted = nfwNotification.isRequestAccepted(); + return (proxyAppState == null || !mIsGpsEnabled) ? isLocationRequestAccepted + : (proxyAppState.mHasLocationPermission != isLocationRequestAccepted); + } + private void showLocationIcon(ProxyAppState proxyAppState, NfwNotification nfwNotification, int uid, String proxyAppPkgName) { // If we receive a new NfwNotification before the location icon is turned off for the diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 19ff2c11d14c..6c34e1313f73 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -217,6 +217,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.os.RoSystemProperties; import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; @@ -1353,9 +1354,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mContext, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT)); final Intent viewIntent = buildViewDataUsageIntent(res, policy.template); - builder.setContentIntent(PendingIntent.getActivity( - mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT)); - + // TODO: Resolve to single code path. + if (isHeadlessSystemUserBuild()) { + builder.setContentIntent(PendingIntent.getActivityAsUser( + mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT, + /* options= */ null, UserHandle.CURRENT)); + } else { + builder.setContentIntent(PendingIntent.getActivity( + mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + } break; } case TYPE_LIMIT: { @@ -1375,8 +1382,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { builder.setSmallIcon(R.drawable.stat_notify_disabled_data); final Intent intent = buildNetworkOverLimitIntent(res, policy.template); - builder.setContentIntent(PendingIntent.getActivity( - mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + // TODO: Resolve to single code path. + if (isHeadlessSystemUserBuild()) { + builder.setContentIntent(PendingIntent.getActivityAsUser( + mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, + /* options= */ null, UserHandle.CURRENT)); + } else { + builder.setContentIntent(PendingIntent.getActivity( + mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + } break; } case TYPE_LIMIT_SNOOZED: { @@ -1399,8 +1413,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { builder.setChannelId(SystemNotificationChannels.NETWORK_STATUS); final Intent intent = buildViewDataUsageIntent(res, policy.template); - builder.setContentIntent(PendingIntent.getActivity( - mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + // TODO: Resolve to single code path. + if (isHeadlessSystemUserBuild()) { + builder.setContentIntent(PendingIntent.getActivityAsUser( + mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, + /* options= */ null, UserHandle.CURRENT)); + } else { + builder.setContentIntent(PendingIntent.getActivity( + mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + } break; } case TYPE_RAPID: { @@ -1419,8 +1440,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mContext, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT)); final Intent viewIntent = buildViewDataUsageIntent(res, policy.template); - builder.setContentIntent(PendingIntent.getActivity( - mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + // TODO: Resolve to single code path. + if (isHeadlessSystemUserBuild()) { + builder.setContentIntent(PendingIntent.getActivityAsUser( + mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT, + /* options= */ null, UserHandle.CURRENT)); + } else { + builder.setContentIntent(PendingIntent.getActivity( + mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + } break; } default: { @@ -5264,6 +5292,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return (bundle != null) ? bundle.getBoolean(key, defaultValue) : defaultValue; } + private static boolean isHeadlessSystemUserBuild() { + return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER; + } + private class NotificationId { private final String mTag; private final int mId; diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java index cebc47217831..7c1c1c7ce403 100644 --- a/services/core/java/com/android/server/net/NetworkStatsAccess.java +++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java @@ -109,7 +109,7 @@ public final class NetworkStatsAccess { final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); boolean hasCarrierPrivileges = tm != null && - tm.checkCarrierPrivilegesForPackage(callingPackage) == + tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d35f952a32ba..a1162373c16f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -14757,7 +14757,7 @@ public class PackageManagerService extends IPackageManager.Stub if (ps != null) { try { - rm.restoreUserData(packageName, installedUsers, appId, ceDataInode, + rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode, seInfo, token); } catch (RemoteException re) { // Cannot happen, the RollbackManager is hosted in the same process. @@ -15032,24 +15032,26 @@ public class PackageManagerService extends IPackageManager.Stub void tryProcessInstallRequest(InstallArgs args, int currentStatus) { mCurrentState.put(args, currentStatus); - boolean success = true; if (mCurrentState.size() != mChildParams.size()) { return; } + int completeStatus = PackageManager.INSTALL_SUCCEEDED; for (Integer status : mCurrentState.values()) { if (status == PackageManager.INSTALL_UNKNOWN) { return; } else if (status != PackageManager.INSTALL_SUCCEEDED) { - success = false; + completeStatus = status; break; } } final List<InstallRequest> installRequests = new ArrayList<>(mCurrentState.size()); for (Map.Entry<InstallArgs, Integer> entry : mCurrentState.entrySet()) { installRequests.add(new InstallRequest(entry.getKey(), - createPackageInstalledInfo(entry.getValue()))); + createPackageInstalledInfo(completeStatus))); } - processInstallRequestsAsync(success, installRequests); + processInstallRequestsAsync( + completeStatus == PackageManager.INSTALL_SUCCEEDED, + installRequests); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index f5c8049dcb25..81de8e263299 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2496,9 +2496,9 @@ class PackageManagerShellCommand extends ShellCommand { default: throw new IllegalArgumentException("Unknown option " + opt); } - if (replaceExisting) { - sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; - } + } + if (replaceExisting) { + sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; } return params; } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 24bf18de13c8..950450cdbeb5 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -36,7 +36,6 @@ import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.ParceledListSlice; import android.content.pm.Signature; import android.content.rollback.IRollbackManager; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -116,12 +115,9 @@ public class StagingManager { final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(packageName); if (packageInfo == null) { - // Only allow installing new apexes if on a debuggable build. - if (!Build.IS_DEBUGGABLE) { - Slog.w(TAG, "Attempted to install new apex " + packageName + " on user build"); - return false; - } - return true; + // Don't allow installation of new APEX. + Slog.e(TAG, "Attempted to install a new apex " + packageName + ". Rejecting"); + return false; } final SigningDetails existingSigningDetails; diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java index 14f1196ab3a2..ed11fd45ec39 100644 --- a/services/core/java/com/android/server/power/AttentionDetector.java +++ b/services/core/java/com/android/server/power/AttentionDetector.java @@ -19,6 +19,8 @@ package com.android.server.power; import static android.provider.Settings.System.ADAPTIVE_SLEEP; import android.Manifest; +import android.app.ActivityManager; +import android.app.SynchronousUserSwitchObserver; import android.attention.AttentionManagerInternal; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; import android.content.ContentResolver; @@ -28,6 +30,7 @@ import android.database.ContentObserver; import android.os.Handler; import android.os.PowerManager; import android.os.PowerManagerInternal; +import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -54,6 +57,8 @@ public class AttentionDetector { private static final String TAG = "AttentionDetector"; private static final boolean DEBUG = false; + private Context mContext; + private boolean mIsSettingEnabled; /** @@ -132,6 +137,7 @@ public class AttentionDetector { } public void systemReady(Context context) { + mContext = context; updateEnabledFromSettings(context); mPackageManager = context.getPackageManager(); mContentResolver = context.getContentResolver(); @@ -141,6 +147,13 @@ public class AttentionDetector { mMaxAttentionApiTimeoutMillis = context.getResources().getInteger( com.android.internal.R.integer.config_attentionApiTimeout); + try { + final UserSwitchObserver observer = new UserSwitchObserver(); + ActivityManager.getService().registerUserSwitchObserver(observer, TAG); + } catch (RemoteException e) { + // Shouldn't happen since in-process. + } + context.getContentResolver().registerContentObserver(Settings.System.getUriFor( Settings.System.ADAPTIVE_SLEEP), false, new ContentObserver(new Handler()) { @@ -326,4 +339,11 @@ public class AttentionDetector { mRequested.set(false); } } + + private final class UserSwitchObserver extends SynchronousUserSwitchObserver { + @Override + public void onUserSwitching(int newUserId) throws RemoteException { + updateEnabledFromSettings(mContext); + } + } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 9a2778dbf535..301f65061045 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -923,8 +923,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } if (rd != null) { - // This is the apk session for a staged session. We have already - // backed up the apks, we just need to do user data backup. + // This is the apk session for a staged session. We do not need to create a new rollback + // for this session. PackageParser.PackageLite newPackage = null; try { newPackage = PackageParser.parsePackageLite( @@ -937,8 +937,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : rd.info.getPackages()) { if (info.getPackageName().equals(packageName)) { info.getInstalledUsers().addAll(IntArray.wrap(installedUsers)); - mAppDataRollbackHelper.snapshotAppData(rd.info.getRollbackId(), info); - saveRollbackData(rd); return true; } } @@ -959,8 +957,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } newRollback.addToken(token); - return enableRollbackForPackageSession(newRollback.data, packageSession, - installedUsers, /* snapshotUserData*/ true); + return enableRollbackForPackageSession(newRollback.data, packageSession, installedUsers); } /** @@ -971,8 +968,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { * @return true on success, false on failure. */ private boolean enableRollbackForPackageSession(RollbackData data, - PackageInstaller.SessionInfo session, @NonNull int[] installedUsers, - boolean snapshotUserData) { + PackageInstaller.SessionInfo session, @NonNull int[] installedUsers) { // TODO: Don't attempt to enable rollback for split installs. final int installFlags = session.installFlags; if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { @@ -1033,10 +1029,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { isApex, IntArray.wrap(installedUsers), new SparseLongArray() /* ceSnapshotInodes */); - if (snapshotUserData && !isApex) { - mAppDataRollbackHelper.snapshotAppData(data.info.getRollbackId(), packageRollbackInfo); - } - try { ApplicationInfo appInfo = pkgInfo.applicationInfo; RollbackStore.backupPackageCodePath(data, packageName, appInfo.sourceDir); @@ -1057,13 +1049,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } @Override - public void restoreUserData(String packageName, int[] userIds, int appId, long ceDataInode, - String seInfo, int token) { + public void snapshotAndRestoreUserData(String packageName, int[] userIds, int appId, + long ceDataInode, String seInfo, int token) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("restoreUserData may only be called by the system."); + throw new SecurityException( + "snapshotAndRestoreUserData may only be called by the system."); } getHandler().post(() -> { + snapshotUserDataInternal(packageName); restoreUserDataInternal(packageName, userIds, appId, ceDataInode, seInfo, token); final PackageManagerInternal pmi = LocalServices.getService( PackageManagerInternal.class); @@ -1071,6 +1065,38 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { }); } + private void snapshotUserDataInternal(String packageName) { + synchronized (mLock) { + // staged installs + ensureRollbackDataLoadedLocked(); + for (int i = 0; i < mRollbacks.size(); i++) { + RollbackData data = mRollbacks.get(i); + if (data.state != RollbackData.ROLLBACK_STATE_ENABLING) { + continue; + } + + for (PackageRollbackInfo info : data.info.getPackages()) { + if (info.getPackageName().equals(packageName)) { + mAppDataRollbackHelper.snapshotAppData(data.info.getRollbackId(), info); + saveRollbackData(data); + return; + } + } + } + // non-staged installs + PackageRollbackInfo info; + for (NewRollback rollback : mNewRollbacks) { + info = getPackageRollbackInfo(rollback.data, packageName); + if (info != null) { + mAppDataRollbackHelper.snapshotAppData(rollback.data.info.getRollbackId(), + info); + saveRollbackData(rollback.data); + return; + } + } + } + } + private void restoreUserDataInternal(String packageName, int[] userIds, int appId, long ceDataInode, String seInfo, int token) { PackageRollbackInfo info = null; @@ -1130,7 +1156,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { if (!session.isMultiPackage()) { if (!enableRollbackForPackageSession(newRollback.data, session, - new int[0], /* snapshotUserData */ false)) { + new int[0])) { Log.e(TAG, "Unable to enable rollback for session: " + sessionId); result.offer(false); return; @@ -1145,7 +1171,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return; } if (!enableRollbackForPackageSession(newRollback.data, childSession, - new int[0], /* snapshotUserData */ false)) { + new int[0])) { Log.e(TAG, "Unable to enable rollback for session: " + sessionId); result.offer(false); return; diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index 476a27309f7e..165055ac828c 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.os.AsyncTask; import android.os.UserHandle; +import android.util.Slog; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; @@ -154,7 +155,10 @@ public class WebViewUpdateServiceImpl { WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); if (fallbackProvider != null) { + Slog.i(TAG, "One-time migration: enabling " + fallbackProvider.packageName); mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName, true); + } else { + Slog.i(TAG, "Skipping one-time migration: no fallback provider"); } mSystemInterface.enableFallbackLogic(false); } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 3d7e50d91b08..cae7612e0fcc 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -1733,17 +1733,13 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return; } - if (mThumbnail == null && getTask() != null) { - final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController; - final ArraySet<Task> tasks = new ArraySet<>(); - tasks.add(getTask()); - snapshotCtrl.snapshotTasks(tasks); - snapshotCtrl.addSkipClosingAppSnapshotTasks(tasks); - final ActivityManager.TaskSnapshot snapshot = snapshotCtrl.getSnapshot( - getTask().mTaskId, getTask().mUserId, false /* restoreFromDisk */, - false /* reducedResolution */); + Task task = getTask(); + if (mThumbnail == null && task != null && !hasCommittedReparentToAnimationLeash()) { + SurfaceControl.ScreenshotGraphicBuffer snapshot = + mWmService.mTaskSnapshotController.createTaskSnapshot( + task, 1 /* scaleFraction */); if (snapshot != null) { - mThumbnail = new AppWindowThumbnail(t, this, snapshot.getSnapshot(), + mThumbnail = new AppWindowThumbnail(t, this, snapshot.getGraphicBuffer(), true /* relative */); } } @@ -2858,7 +2854,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } t.hide(mTransitChangeLeash); - t.reparent(mTransitChangeLeash, null); + t.remove(mTransitChangeLeash); mTransitChangeLeash = null; if (cancel) { onAnimationLeashLost(t); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e1dd3522a808..21f01ff38a0e 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2720,9 +2720,9 @@ public class DisplayPolicy { res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture); // This should calculate how much above the frame we accept gestures. - mBottomGestureAdditionalInset = Math.max(0, + mBottomGestureAdditionalInset = res.getDimensionPixelSize(R.dimen.navigation_bar_gesture_height) - - getNavigationBarFrameHeight(portraitRotation, uiMode)); + - getNavigationBarFrameHeight(portraitRotation, uiMode); updateConfigurationAndScreenSizeDependentBehaviors(); mWindowOutsetBottom = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources()); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 432ca3387ce7..181521850369 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -21,6 +21,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskSnapshot; @@ -241,16 +242,32 @@ class TaskSnapshotController { return null; } - @Nullable private TaskSnapshot snapshotTask(Task task) { - if (!mService.mPolicy.isScreenOn()) { + @Nullable + SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task, + float scaleFraction) { + if (task.getSurfaceControl() == null) { if (DEBUG_SCREENSHOT) { - Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); + Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); } return null; } - if (task.getSurfaceControl() == null) { + task.getBounds(mTmpRect); + mTmpRect.offsetTo(0, 0); + final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = + SurfaceControl.captureLayers( + task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction); + final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer() + : null; + if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + return null; + } + return screenshotBuffer; + } + + @Nullable private TaskSnapshot snapshotTask(Task task) { + if (!mService.mPolicy.isScreenOn()) { if (DEBUG_SCREENSHOT) { - Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); + Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); } return null; } @@ -271,8 +288,6 @@ class TaskSnapshotController { final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); final float scaleFraction = isLowRamDevice ? mPersister.getReducedScale() : 1f; - task.getBounds(mTmpRect); - mTmpRect.offsetTo(0, 0); final WindowState mainWindow = appWindowToken.findMainWindow(); if (mainWindow == null) { @@ -280,18 +295,17 @@ class TaskSnapshotController { return null; } final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = - SurfaceControl.captureLayers( - task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction); - final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer() - : null; - if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + createTaskSnapshot(task, scaleFraction); + + if (screenshotBuffer == null) { if (DEBUG_SCREENSHOT) { Slog.w(TAG_WM, "Failed to take screenshot for " + task); } return null; } final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; - return new TaskSnapshot(appWindowToken.mActivityComponent, buffer, + return new TaskSnapshot( + appWindowToken.mActivityComponent, screenshotBuffer.getGraphicBuffer(), screenshotBuffer.getColorSpace(), appWindowToken.getConfiguration().orientation, getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */, true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task), diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4e1cac905178..203704bf7224 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -749,10 +749,12 @@ public final class SystemServer { if (!disableOtaDexopt) { traceBeginAndSlog("StartOtaDexOptService"); try { + Watchdog.getInstance().pauseWatchingCurrentThread("moveab"); OtaDexoptService.main(mSystemContext, mPackageManagerService); } catch (Throwable e) { reportWtf("starting OtaDexOptService", e); } finally { + Watchdog.getInstance().resumeWatchingCurrentThread("moveab"); traceEnd(); } } diff --git a/services/net/java/android/net/NetworkMonitorManager.java b/services/net/java/android/net/NetworkMonitorManager.java new file mode 100644 index 000000000000..0f41302c0b15 --- /dev/null +++ b/services/net/java/android/net/NetworkMonitorManager.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.RemoteException; +import android.util.Log; + +/** + * A convenience wrapper for INetworkMonitor. + * + * Wraps INetworkMonitor calls, making them a bit more friendly to use. Currently handles: + * - Clearing calling identity + * - Ignoring RemoteExceptions + * - Converting to stable parcelables + * + * By design, all methods on INetworkMonitor are asynchronous oneway IPCs and are thus void. All the + * wrapper methods in this class return a boolean that callers can use to determine whether + * RemoteException was thrown. + */ +public class NetworkMonitorManager { + + @NonNull private final INetworkMonitor mNetworkMonitor; + @NonNull private final String mTag; + + public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager, + @NonNull String tag) { + mNetworkMonitor = networkMonitorManager; + mTag = tag; + } + + public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager) { + this(networkMonitorManager, NetworkMonitorManager.class.getSimpleName()); + } + + private void log(String s, Throwable e) { + Log.e(mTag, s, e); + } + + // CHECKSTYLE:OFF Generated code + + public boolean start() { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.start(); + return true; + } catch (RemoteException e) { + log("Error in start", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean launchCaptivePortalApp() { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.launchCaptivePortalApp(); + return true; + } catch (RemoteException e) { + log("Error in launchCaptivePortalApp", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean notifyCaptivePortalAppFinished(int response) { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.notifyCaptivePortalAppFinished(response); + return true; + } catch (RemoteException e) { + log("Error in notifyCaptivePortalAppFinished", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean setAcceptPartialConnectivity() { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.setAcceptPartialConnectivity(); + return true; + } catch (RemoteException e) { + log("Error in setAcceptPartialConnectivity", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean forceReevaluation(int uid) { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.forceReevaluation(uid); + return true; + } catch (RemoteException e) { + log("Error in forceReevaluation", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean notifyPrivateDnsChanged(PrivateDnsConfigParcel config) { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.notifyPrivateDnsChanged(config); + return true; + } catch (RemoteException e) { + log("Error in notifyPrivateDnsChanged", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean notifyDnsResponse(int returnCode) { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.notifyDnsResponse(returnCode); + return true; + } catch (RemoteException e) { + log("Error in notifyDnsResponse", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.notifyNetworkConnected(lp, nc); + return true; + } catch (RemoteException e) { + log("Error in notifyNetworkConnected", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean notifyNetworkDisconnected() { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.notifyNetworkDisconnected(); + return true; + } catch (RemoteException e) { + log("Error in notifyNetworkDisconnected", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean notifyLinkPropertiesChanged(LinkProperties lp) { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.notifyLinkPropertiesChanged(lp); + return true; + } catch (RemoteException e) { + log("Error in notifyLinkPropertiesChanged", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public boolean notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) { + final long token = Binder.clearCallingIdentity(); + try { + mNetworkMonitor.notifyNetworkCapabilitiesChanged(nc); + return true; + } catch (RemoteException e) { + log("Error in notifyNetworkCapabilitiesChanged", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // CHECKSTYLE:ON Generated code +} diff --git a/services/net/java/android/net/NetworkStackClient.java b/services/net/java/android/net/NetworkStackClient.java index 6b5842ff9065..99da637416c3 100644 --- a/services/net/java/android/net/NetworkStackClient.java +++ b/services/net/java/android/net/NetworkStackClient.java @@ -26,6 +26,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; @@ -33,15 +34,21 @@ import android.net.ip.IIpClientCallbacks; import android.net.util.SharedLog; import android.os.Binder; import android.os.Build; +import android.os.Environment; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemClock; import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.text.format.DateUtils; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import java.io.File; import java.io.PrintWriter; import java.util.ArrayList; @@ -54,6 +61,23 @@ public class NetworkStackClient { private static final int NETWORKSTACK_TIMEOUT_MS = 10_000; private static final String IN_PROCESS_SUFFIX = ".InProcess"; + private static final String PREFS_FILE = "NetworkStackClientPrefs.xml"; + private static final String PREF_KEY_LAST_CRASH_TIME = "lastcrash_time"; + private static final String CONFIG_MIN_CRASH_INTERVAL_MS = "min_crash_interval"; + private static final String CONFIG_MIN_UPTIME_BEFORE_CRASH_MS = "min_uptime_before_crash"; + private static final String CONFIG_ALWAYS_RATELIMIT_NETWORKSTACK_CRASH = + "always_ratelimit_networkstack_crash"; + + // Even if the network stack is lost, do not crash the system more often than this. + // Connectivity would be broken, but if the user needs the device for something urgent + // (like calling emergency services) we should not bootloop the device. + // This is the default value: the actual value can be adjusted via device config. + private static final long DEFAULT_MIN_CRASH_INTERVAL_MS = 6 * DateUtils.HOUR_IN_MILLIS; + + // Even if the network stack is lost, do not crash the system server if it was less than + // this much after boot. This avoids bootlooping the device, and crashes should address very + // infrequent failures, not failures on boot. + private static final long DEFAULT_MIN_UPTIME_BEFORE_CRASH_MS = 30 * DateUtils.MINUTE_IN_MILLIS; private static NetworkStackClient sInstance; @@ -67,12 +91,28 @@ public class NetworkStackClient { @GuardedBy("mLog") private final SharedLog mLog = new SharedLog(TAG); - private volatile boolean mNetworkStackStartRequested = false; + private volatile boolean mWasSystemServerInitialized = false; + + @GuardedBy("mHealthListeners") + private final ArraySet<NetworkStackHealthListener> mHealthListeners = new ArraySet<>(); private interface NetworkStackCallback { void onNetworkStackConnected(INetworkStackConnector connector); } + /** + * Callback interface for severe failures of the NetworkStack. + * + * <p>Useful for health monitors such as PackageWatchdog. + */ + public interface NetworkStackHealthListener { + /** + * Called when there is a severe failure of the network stack. + * @param packageName Package name of the network stack. + */ + void onNetworkStackFailure(@NonNull String packageName); + } + private NetworkStackClient() { } /** @@ -86,6 +126,15 @@ public class NetworkStackClient { } /** + * Add a {@link NetworkStackHealthListener} to listen to network stack health events. + */ + public void registerHealthListener(@NonNull NetworkStackHealthListener listener) { + synchronized (mHealthListeners) { + mHealthListeners.add(listener); + } + } + + /** * Create a DHCP server according to the specified parameters. * * <p>The server will be returned asynchronously through the provided callbacks. @@ -147,6 +196,16 @@ public class NetworkStackClient { } private class NetworkStackConnection implements ServiceConnection { + @NonNull + private final Context mContext; + @NonNull + private final String mPackageName; + + private NetworkStackConnection(@NonNull Context context, @NonNull String packageName) { + mContext = context; + mPackageName = packageName; + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { logi("Network stack service connected"); @@ -155,14 +214,14 @@ public class NetworkStackClient { @Override public void onServiceDisconnected(ComponentName name) { - // The system has lost its network stack (probably due to a crash in the - // network stack process): better crash rather than stay in a bad state where all - // networking is broken. // onServiceDisconnected is not being called on device shutdown, so this method being // called always indicates a bad state for the system server. - maybeCrashWithTerribleFailure("Lost network stack"); + // This code path is only run by the system server: only the system server binds + // to the NetworkStack as a service. Other processes get the NetworkStack from + // the ServiceManager. + maybeCrashWithTerribleFailure("Lost network stack", mContext, mPackageName); } - }; + } private void registerNetworkStackService(@NonNull IBinder service) { final INetworkStackConnector connector = INetworkStackConnector.Stub.asInterface(service); @@ -189,7 +248,7 @@ public class NetworkStackClient { */ public void init() { log("Network stack init"); - mNetworkStackStartRequested = true; + mWasSystemServerInitialized = true; } /** @@ -202,6 +261,7 @@ public class NetworkStackClient { */ public void start(Context context) { log("Starting network stack"); + final PackageManager pm = context.getPackageManager(); // Try to bind in-process if the device was shipped with an in-process version @@ -216,16 +276,19 @@ public class NetworkStackClient { } if (intent == null) { - maybeCrashWithTerribleFailure("Could not resolve the network stack"); + maybeCrashWithTerribleFailure("Could not resolve the network stack", context, null); return; } + final String packageName = intent.getComponent().getPackageName(); + // Start the network stack. The service will be added to the service manager in // NetworkStackConnection.onServiceConnected(). - if (!context.bindServiceAsUser(intent, new NetworkStackConnection(), + if (!context.bindServiceAsUser(intent, new NetworkStackConnection(context, packageName), Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) { maybeCrashWithTerribleFailure( - "Could not bind to network stack in-process, or in app with " + intent); + "Could not bind to network stack in-process, or in app with " + intent, + context, packageName); return; } @@ -274,11 +337,91 @@ public class NetworkStackClient { } } - private void maybeCrashWithTerribleFailure(@NonNull String message) { + private void maybeCrashWithTerribleFailure(@NonNull String message, + @NonNull Context context, @Nullable String packageName) { logWtf(message, null); - if (Build.IS_DEBUGGABLE) { + // uptime is monotonic even after a framework restart + final long uptime = SystemClock.elapsedRealtime(); + final long now = System.currentTimeMillis(); + final long minCrashIntervalMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY, + CONFIG_MIN_CRASH_INTERVAL_MS, DEFAULT_MIN_CRASH_INTERVAL_MS); + final long minUptimeBeforeCrash = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY, + CONFIG_MIN_UPTIME_BEFORE_CRASH_MS, DEFAULT_MIN_UPTIME_BEFORE_CRASH_MS); + final boolean alwaysRatelimit = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CONNECTIVITY, + CONFIG_ALWAYS_RATELIMIT_NETWORKSTACK_CRASH, false); + + final SharedPreferences prefs = getSharedPreferences(context); + final long lastCrashTime = tryGetLastCrashTime(prefs); + + // Only crash if there was enough time since boot, and (if known) enough time passed since + // the last crash. + // time and lastCrashTime may be unreliable if devices have incorrect clock time, but they + // are only used to limit the number of crashes compared to only using the time since boot, + // which would also be OK behavior by itself. + // - If lastCrashTime is incorrectly more than the current time, only look at uptime + // - If it is much less than current time, only look at uptime + // - If current time is during the next few hours after last crash time, don't crash. + // Considering that this only matters if last boot was some time ago, it's likely that + // time will be set correctly. Otherwise, not crashing is not a big problem anyway. Being + // in this last state would also not last for long since the window is only a few hours. + final boolean alwaysCrash = Build.IS_DEBUGGABLE && !alwaysRatelimit; + final boolean justBooted = uptime < minUptimeBeforeCrash; + final boolean haveLastCrashTime = (lastCrashTime != 0) && (lastCrashTime < now); + final boolean haveKnownRecentCrash = + haveLastCrashTime && (now < lastCrashTime + minCrashIntervalMs); + if (alwaysCrash || (!justBooted && !haveKnownRecentCrash)) { + // The system is not bound to its network stack (for example due to a crash in the + // network stack process): better crash rather than stay in a bad state where all + // networking is broken. + // Using device-encrypted SharedPreferences as DeviceConfig does not have a synchronous + // API to persist settings before a crash. + tryWriteLastCrashTime(prefs, now); throw new IllegalStateException(message); } + + // Here the system crashed recently already. Inform listeners that something is + // definitely wrong. + if (packageName != null) { + final ArraySet<NetworkStackHealthListener> listeners; + synchronized (mHealthListeners) { + listeners = new ArraySet<>(mHealthListeners); + } + for (NetworkStackHealthListener listener : listeners) { + listener.onNetworkStackFailure(packageName); + } + } + } + + @Nullable + private SharedPreferences getSharedPreferences(@NonNull Context context) { + try { + final File prefsFile = new File( + Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), PREFS_FILE); + return context.createDeviceProtectedStorageContext() + .getSharedPreferences(prefsFile, Context.MODE_PRIVATE); + } catch (Throwable e) { + logWtf("Error loading shared preferences", e); + return null; + } + } + + private long tryGetLastCrashTime(@Nullable SharedPreferences prefs) { + if (prefs == null) return 0L; + try { + return prefs.getLong(PREF_KEY_LAST_CRASH_TIME, 0L); + } catch (Throwable e) { + logWtf("Error getting last crash time", e); + return 0L; + } + } + + private void tryWriteLastCrashTime(@Nullable SharedPreferences prefs, long value) { + if (prefs == null) return; + try { + prefs.edit().putLong(PREF_KEY_LAST_CRASH_TIME, value).commit(); + } catch (Throwable e) { + logWtf("Error writing last crash time", e); + } } /** @@ -350,7 +493,7 @@ public class NetworkStackClient { "Only the system server should try to bind to the network stack."); } - if (!mNetworkStackStartRequested) { + if (!mWasSystemServerInitialized) { // The network stack is not being started in this process, e.g. this process is not // the system server. Get a remote connector registered by the system server. final INetworkStackConnector connector = getRemoteConnector(); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 5175c1daf3c0..a125e319d35f 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1973,26 +1973,6 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_WIFI_STRING_ARRAY = "carrier_wifi_string_array"; /** - * Base64 Encoding method the carrier will use for encoding encrypted IMSI and SSID. - * The value set as below: - * 2045 - RFC2045 (default value) - * 4648 - RFC4648 - * - * @hide - */ - public static final String KEY_IMSI_ENCODING_METHOD_INT = "imsi_encoding_method_int"; - - /** - * Defines the sequence of sending an encrypted IMSI identity for EAP-SIM/AKA authentication. - * The value set as below: - * 1 - encrypted IMSI as EAP-RESPONSE/IDENTITY (default one). - * 2 - anonymous as EAP-RESPONSE/IDENTITY -> encrypted IMSI as EAP-RESPONSE/AKA|SIM-IDENTITY. - * - * @hide - */ - public static final String KEY_EAP_IDENTITY_SEQUENCE_INT = "imsi_eap_identity_sequence_int"; - - /** * Time delay (in ms) after which we show the notification to switch the preferred * network. * @hide @@ -3265,8 +3245,6 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL, false); sDefaults.putBoolean(KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL, false); sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null); - sDefaults.putInt(KEY_IMSI_ENCODING_METHOD_INT, 2045); - sDefaults.putInt(KEY_EAP_IDENTITY_SEQUENCE_INT, 1); sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1); sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1); sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true); diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 984d8f705a65..5d39a2cc194a 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -341,6 +341,7 @@ public class ServiceState implements Parcelable { private String mOperatorAlphaLongRaw; private String mOperatorAlphaShortRaw; + private boolean mIsIwlanPreferred; /** * get String description of roaming type @@ -427,6 +428,7 @@ public class ServiceState implements Parcelable { mNrFrequencyRange = s.mNrFrequencyRange; mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw; mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw; + mIsIwlanPreferred = s.mIsIwlanPreferred; } /** @@ -463,6 +465,7 @@ public class ServiceState implements Parcelable { mNrFrequencyRange = in.readInt(); mOperatorAlphaLongRaw = in.readString(); mOperatorAlphaShortRaw = in.readString(); + mIsIwlanPreferred = in.readBoolean(); } public void writeToParcel(Parcel out, int flags) { @@ -492,6 +495,7 @@ public class ServiceState implements Parcelable { out.writeInt(mNrFrequencyRange); out.writeString(mOperatorAlphaLongRaw); out.writeString(mOperatorAlphaShortRaw); + out.writeBoolean(mIsIwlanPreferred); } public int describeContents() { @@ -853,7 +857,8 @@ public class ServiceState implements Parcelable { mNetworkRegistrationInfos, mNrFrequencyRange, mOperatorAlphaLongRaw, - mOperatorAlphaShortRaw); + mOperatorAlphaShortRaw, + mIsIwlanPreferred); } } @@ -885,7 +890,8 @@ public class ServiceState implements Parcelable { && equalsHandlesNulls(mOperatorAlphaShortRaw, s.mOperatorAlphaShortRaw) && mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size() && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos) - && mNrFrequencyRange == s.mNrFrequencyRange; + && mNrFrequencyRange == s.mNrFrequencyRange + && mIsIwlanPreferred == s.mIsIwlanPreferred; } } @@ -1043,6 +1049,7 @@ public class ServiceState implements Parcelable { .append(", mNrFrequencyRange=").append(mNrFrequencyRange) .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw) .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw) + .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred) .append("}").toString(); } } @@ -1085,6 +1092,7 @@ public class ServiceState implements Parcelable { } mOperatorAlphaLongRaw = null; mOperatorAlphaShortRaw = null; + mIsIwlanPreferred = false; } public void setStateOutOfService() { @@ -1459,20 +1467,9 @@ public class ServiceState implements Parcelable { /** @hide */ @UnsupportedAppUsage public int getRilDataRadioTechnology() { - NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - NetworkRegistrationInfo wlanRegInfo = getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN); - if (wlanRegInfo != null - && wlanRegInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN - && wlanRegInfo.getRegistrationState() - == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { - return RIL_RADIO_TECHNOLOGY_IWLAN; - } else if (wwanRegInfo != null) { - return networkTypeToRilRadioTechnology(wwanRegInfo.getAccessNetworkTechnology()); - } - return RIL_RADIO_TECHNOLOGY_UNKNOWN; + return networkTypeToRilRadioTechnology(getDataNetworkType()); } + /** * @hide * @Deprecated to be removed Q3 2013 use {@link #getRilDataRadioTechnology} or @@ -1608,26 +1605,40 @@ public class ServiceState implements Parcelable { } } - /** @hide */ + /** + * Get current data network type. + * + * Note that for IWLAN AP-assisted mode device, which is reporting both camped access networks + * (cellular RAT and IWLAN)at the same time, this API is simulating the old legacy mode device + * behavior, + * + * @return Current data network type + * @hide + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public @TelephonyManager.NetworkType int getDataNetworkType() { - final NetworkRegistrationInfo iwlanRegState = getNetworkRegistrationInfo( + final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN); - if (iwlanRegState != null && iwlanRegState.getRegistrationState() - == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { - // If the device is on IWLAN, return IWLAN as the network type. This is to simulate the - // behavior of legacy mode device. In the future caller should use - // requestNetworkRegistrationInfo() to retrieve the actual data network type on cellular - // or on IWLAN. - return iwlanRegState.getAccessNetworkTechnology(); + final NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + + // For legacy mode device, or AP-assisted mode device but IWLAN is out of service, use + // the RAT from cellular. + if (iwlanRegInfo == null || !iwlanRegInfo.isInService()) { + return (wwanRegInfo != null) ? wwanRegInfo.getAccessNetworkTechnology() + : TelephonyManager.NETWORK_TYPE_UNKNOWN; } - final NetworkRegistrationInfo regState = getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - if (regState != null) { - return regState.getAccessNetworkTechnology(); + // At this point, it must be an AP-assisted mode device and IWLAN is in service. We should + // use the RAT from IWLAN service is cellular is out of service, or when both are in service + // and any APN type of data is preferred on IWLAN. + if (!wwanRegInfo.isInService() || mIsIwlanPreferred) { + return iwlanRegInfo.getAccessNetworkTechnology(); } - return TelephonyManager.NETWORK_TYPE_UNKNOWN; + + // If both cellular and IWLAN are in service, but no APN is preferred on IWLAN, still use + // the RAT from cellular. + return wwanRegInfo.getAccessNetworkTechnology(); } /** @hide */ @@ -1976,4 +1987,14 @@ public class ServiceState implements Parcelable { public String getOperatorAlphaShortRaw() { return mOperatorAlphaShortRaw; } + + /** + * Set to {@code true} if any data network is preferred on IWLAN. + * + * @param isIwlanPreferred {@code true} if IWLAN is preferred. + * @hide + */ + public void setIwlanPreferred(boolean isIwlanPreferred) { + mIsIwlanPreferred = isIwlanPreferred; + } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index addd9e0591b0..e010d280e208 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -775,6 +775,14 @@ public class SubscriptionManager { public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET; /** + * TelephonyProvider column name IMSI (International Mobile Subscriber Identity). + * <P>Type: TEXT </P> + * @hide + */ + //TODO: add @SystemApi + public static final String IMSI = "imsi"; + + /** * Broadcast Action: The user has changed one of the default subs related to * data, phone calls, or sms</p> * diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp index b07996568839..88d92c4d94fe 100644 --- a/tests/PackageWatchdog/Android.bp +++ b/tests/PackageWatchdog/Android.bp @@ -18,11 +18,18 @@ android_test { srcs: ["src/**/*.java"], static_libs: [ "junit", + "mockito-target-extended-minus-junit4", "frameworks-base-testutils", "androidx.test.rules", "services.core", + "services.net", ], libs: ["android.test.runner"], + jni_libs: [ + // mockito-target-extended dependencies + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], platform_apis: true, test_suites: ["device-tests"], } diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index eb19361d86a3..232b5cb17023 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -23,10 +23,20 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.Manifest; import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; +import android.net.NetworkStackClient; +import android.net.NetworkStackClient.NetworkStackHealthListener; import android.os.Handler; import android.os.test.TestLooper; import android.provider.DeviceConfig; @@ -41,6 +51,10 @@ import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.File; import java.util.ArrayList; @@ -70,13 +84,29 @@ public class PackageWatchdogTest { private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); private TestLooper mTestLooper; + private Context mSpyContext; + @Mock + private NetworkStackClient mMockNetworkStackClient; + @Mock + private PackageManager mMockPackageManager; + @Captor + private ArgumentCaptor<NetworkStackHealthListener> mNetworkStackCallbackCaptor; @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG); mTestLooper = new TestLooper(); + mSpyContext = spy(InstrumentationRegistry.getContext()); + when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> { + final PackageInfo res = new PackageInfo(); + res.packageName = inv.getArgument(0); + res.setLongVersionCode(VERSION_CODE); + return res; + }); } @After @@ -696,6 +726,26 @@ public class PackageWatchdogTest { assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(LONG_DURATION)); } + @Test + public void testNetworkStackFailure() { + final PackageWatchdog wd = createWatchdog(); + + // Start observing with failure handling + TestObserver observer = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_HIGH); + wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); + + // Notify of NetworkStack failure + mNetworkStackCallbackCaptor.getValue().onNetworkStackFailure(APP_A); + + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + + // Verify the NetworkStack observer is notified + assertEquals(1, observer.mFailedPackages.size()); + assertEquals(APP_A, observer.mFailedPackages.get(0)); + } + private void adoptShellPermissions(String... permissions) { InstrumentationRegistry .getInstrumentation() @@ -727,18 +777,23 @@ public class PackageWatchdogTest { } private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) { - Context context = InstrumentationRegistry.getContext(); AtomicFile policyFile = - new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); + new AtomicFile(new File(mSpyContext.getFilesDir(), "package-watchdog.xml")); Handler handler = new Handler(mTestLooper.getLooper()); PackageWatchdog watchdog = - new PackageWatchdog(context, policyFile, handler, handler, controller); + new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller, + mMockNetworkStackClient); // Verify controller is not automatically started assertFalse(controller.mIsEnabled); if (withPackagesReady) { + // Only capture the NetworkStack callback for the latest registered watchdog + reset(mMockNetworkStackClient); watchdog.onPackagesReady(); // Verify controller by default is started when packages are ready assertTrue(controller.mIsEnabled); + + verify(mMockNetworkStackClient).registerHealthListener( + mNetworkStackCallbackCaptor.capture()); } return watchdog; } diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 1cf3d364a8d5..aec40558ad51 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -95,7 +95,7 @@ android_test { ":RollbackTestAppASplitV2", ], test_config: "RollbackTest.xml", - sdk_version: "test_current", + // TODO: sdk_version: "test_current" when Intent#resolveSystemservice is TestApi } java_test_host { diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java index 81629aaaec76..a9e20cdb191b 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java @@ -28,6 +28,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -82,13 +83,31 @@ class RollbackTestUtils { * Returns -1 if the package is not currently installed. */ static long getInstalledVersion(String packageName) { + PackageInfo pi = getPackageInfo(packageName); + if (pi == null) { + return -1; + } else { + return pi.getLongVersionCode(); + } + } + + private static boolean isSystemAppWithoutUpdate(String packageName) { + PackageInfo pi = getPackageInfo(packageName); + if (pi == null) { + return false; + } else { + return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) + && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0); + } + } + + private static PackageInfo getPackageInfo(String packageName) { Context context = InstrumentationRegistry.getContext(); PackageManager pm = context.getPackageManager(); try { - PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); - return info.getLongVersionCode(); + return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); } catch (PackageManager.NameNotFoundException e) { - return -1; + return null; } } @@ -109,8 +128,8 @@ class RollbackTestUtils { * @throws AssertionError if package can't be uninstalled. */ static void uninstall(String packageName) throws InterruptedException, IOException { - // No need to uninstall if the package isn't installed. - if (getInstalledVersion(packageName) == -1) { + // No need to uninstall if the package isn't installed or is installed on /system. + if (getInstalledVersion(packageName) == -1 || isSystemAppWithoutUpdate(packageName)) { return; } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 1ddfa6ee54ce..1a29c4c11457 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -21,7 +21,9 @@ import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfo import android.Manifest; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.VersionedPackage; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; @@ -30,6 +32,8 @@ import androidx.test.InstrumentationRegistry; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -54,6 +58,8 @@ public class StagedRollbackTest { private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A"; private static final String TEST_APP_A_V1 = "RollbackTestAppAv1.apk"; private static final String TEST_APP_A_CRASHING_V2 = "RollbackTestAppACrashingV2.apk"; + private static final String NETWORK_STACK_CONNECTOR_CLASS = + "android.net.INetworkStackConnector"; /** * Adopts common shell permissions needed for rollback tests. @@ -157,4 +163,44 @@ public class StagedRollbackTest { assertTrue(rollback.isStaged()); assertNotEquals(-1, rollback.getCommittedSessionId()); } + + @Test + public void resetNetworkStack() throws Exception { + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + String networkStack = getNetworkStackPackageName(); + + rm.expireRollbackForPackage(networkStack); + RollbackTestUtils.uninstall(networkStack); + + assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + networkStack)); + } + + @Test + public void assertNetworkStackRollbackAvailable() throws Exception { + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + assertNotNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + getNetworkStackPackageName())); + } + + @Test + public void assertNetworkStackRollbackCommitted() throws Exception { + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + assertNotNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())); + } + + @Test + public void assertNoNetworkStackRollbackCommitted() throws Exception { + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + assertNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())); + } + + private String getNetworkStackPackageName() { + Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); + ComponentName comp = intent.resolveSystemService( + InstrumentationRegistry.getContext().getPackageManager(), 0); + return comp.getPackageName(); + } } diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 75a95adf460a..bad294794337 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -23,6 +23,8 @@ import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +45,20 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { phase)); } + @Before + public void setUp() throws Exception { + // Disconnect internet so we can test network health triggered rollbacks + getDevice().executeShellCommand("svc wifi disable"); + getDevice().executeShellCommand("svc data disable"); + } + + @After + public void tearDown() throws Exception { + // Reconnect internet after testing network health triggered rollbacks + getDevice().executeShellCommand("svc wifi enable"); + getDevice().executeShellCommand("svc data enable"); + } + /** * Tests watchdog triggered staged rollbacks involving only apks. */ @@ -63,6 +79,90 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } getDevice().waitForDeviceAvailable(); + runPhase("testBadApkOnlyConfirmRollback"); } + + /** + * Tests failed network health check triggers watchdog staged rollbacks. + */ + @Test + public void testNetworkFailedRollback() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ + runPhase("resetNetworkStack"); + // Reduce health check deadline + getDevice().executeShellCommand("device_config put rollback " + + "watchdog_request_timeout_millis 300000"); + // Simulate re-installation of new NetworkStack with rollbacks enabled + getDevice().executeShellCommand("pm install -r --staged --enable-rollback " + + "/system/priv-app/NetworkStack/NetworkStack.apk"); + + // Sleep to allow writes to disk before reboot + Thread.sleep(5000); + // Reboot device to activate staged package + getDevice().reboot(); + getDevice().waitForDeviceAvailable(); + + // Verify rollback was enabled + runPhase("assertNetworkStackRollbackAvailable"); + + // Sleep for < health check deadline + Thread.sleep(5000); + // Verify rollback was not executed before health check deadline + runPhase("assertNoNetworkStackRollbackCommitted"); + try { + // This is expected to fail due to the device being rebooted out + // from underneath the test. If this fails for reasons other than + // the device reboot, those failures should result in failure of + // the assertNetworkStackExecutedRollback phase. + CLog.logAndDisplay(LogLevel.INFO, "Sleep and expect to fail while sleeping"); + // Sleep for > health check deadline + Thread.sleep(260000); + } catch (AssertionError e) { + // AssertionError is expected. + } + + getDevice().waitForDeviceAvailable(); + // Verify rollback was executed after health check deadline + runPhase("assertNetworkStackRollbackCommitted"); + } + + /** + * Tests passed network health check does not trigger watchdog staged rollbacks. + */ + @Test + public void testNetworkPassedDoesNotRollback() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ + runPhase("resetNetworkStack"); + // Reduce health check deadline, here unlike the network failed case, we use + // a longer deadline because joining a network can take a much longer time for + // reasons external to the device than 'not joining' + getDevice().executeShellCommand("device_config put rollback " + + "watchdog_request_timeout_millis 300000"); + // Simulate re-installation of new NetworkStack with rollbacks enabled + getDevice().executeShellCommand("pm install -r --staged --enable-rollback " + + "/system/priv-app/NetworkStack/NetworkStack.apk"); + + // Sleep to allow writes to disk before reboot + Thread.sleep(5000); + // Reboot device to activate staged package + getDevice().reboot(); + getDevice().waitForDeviceAvailable(); + + // Verify rollback was enabled + runPhase("assertNetworkStackRollbackAvailable"); + + // Connect to internet so network health check passes + getDevice().executeShellCommand("svc wifi enable"); + getDevice().executeShellCommand("svc data enable"); + + // Wait for device available because emulator device may restart after turning + // on mobile data + getDevice().waitForDeviceAvailable(); + + // Sleep for > health check deadline + Thread.sleep(310000); + // Verify rollback was not executed after health check deadline + runPhase("assertNoNetworkStackRollbackCommitted"); + } } diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 2cae2509026c..ce50bef53d75 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -727,94 +727,4 @@ public class VpnTest { "::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6", "fe00::/8", "2605:ef80:e:af1d::/64"); } - - @Test - public void testProvidesRoutesToMostDestinations() { - final LinkProperties lp = new LinkProperties(); - - // Default route provides routes to all IPv4 destinations. - lp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // Empty LP provides routes to no destination - lp.clear(); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - // All IPv4 routes except for local networks. This is the case most relevant - // to this function. It provides routes to almost the entire space. - // (clone the stream so that we can reuse it later) - publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // Removing a 16-bit prefix, which is 65536 addresses. This is still enough to - // provide routes to "most" destinations. - lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16"))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // Remove the /2 route, which represent a quarter of the available routing space. - // This LP does not provides routes to "most" destinations any more. - lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2"))); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - lp.clear(); - publicIpV6Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - lp.removeRoute(new RouteInfo(new IpPrefix("::/1"))); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - // V6 does not provide sufficient coverage but v4 does - publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // V4 still does - lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16"))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // V4 does not any more - lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2"))); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - // V4 does not, but V6 has sufficient coverage again - lp.addRoute(new RouteInfo(new IpPrefix("::/1"))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - lp.clear(); - // V4-unreachable route should not be treated as sufficient coverage - lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - lp.clear(); - // V6-unreachable route should not be treated as sufficient coverage - lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - } - - @Test - public void testDoesNotLockUpWithTooManyRoutes() { - final LinkProperties lp = new LinkProperties(); - final byte[] ad = new byte[4]; - // Actually evaluating this many routes under 1500ms is impossible on - // current hardware and for some time, as the algorithm is O(n²). - // Make sure the system has a safeguard against this and does not - // lock up. - final int MAX_ROUTES = 4000; - final long MAX_ALLOWED_TIME_MS = 1500; - for (int i = 0; i < MAX_ROUTES; ++i) { - ad[0] = (byte)((i >> 24) & 0xFF); - ad[1] = (byte)((i >> 16) & 0xFF); - ad[2] = (byte)((i >> 8) & 0xFF); - ad[3] = (byte)(i & 0xFF); - try { - lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.getByAddress(ad), 32))); - } catch (UnknownHostException e) { - // UnknownHostException is only thrown for an address of illegal length, - // which can't happen in the case above. - } - } - final long start = SystemClock.currentThreadTimeMillis(); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - final long end = SystemClock.currentThreadTimeMillis(); - assertTrue(end - start < MAX_ALLOWED_TIME_MS); - } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java index 6e725dd69cb7..858358c74f80 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java @@ -161,7 +161,7 @@ public class NetworkStatsAccessTest { } private void setHasCarrierPrivileges(boolean hasPrivileges) { - when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn( + when(mTm.checkCarrierPrivilegesForPackageAnyPhone(TEST_PKG)).thenReturn( hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS); } |