diff options
62 files changed, 1627 insertions, 1211 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/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/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/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/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/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/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/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/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/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/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/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/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/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java index 7ad6dfd2672b..b1be811be9cf 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java @@ -35,6 +35,8 @@ 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; /** @@ -92,12 +94,12 @@ public class DefaultUiController implements AssistManager.UiController { if (progress == 1) { animateInvocationCompletion(type, 0); } else if (progress == 0) { - mInvocationInProgress = false; hide(); } else { if (!mInvocationInProgress) { attach(); mInvocationInProgress = true; + updateAssistHandleVisibility(); } setProgressInternal(type, progress); } @@ -129,6 +131,7 @@ public class DefaultUiController implements AssistManager.UiController { } mInvocationLightsView.hide(); mInvocationInProgress = false; + updateAssistHandleVisibility(); } /** @@ -139,6 +142,12 @@ public class DefaultUiController implements AssistManager.UiController { 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); 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..86472008688f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -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/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/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/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/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/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/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/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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d35f952a32ba..2f866327782d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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/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/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/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/net/java/android/net/NetworkStackClient.java b/services/net/java/android/net/NetworkStackClient.java index 6b5842ff9065..09c9b6d360a9 100644 --- a/services/net/java/android/net/NetworkStackClient.java +++ b/services/net/java/android/net/NetworkStackClient.java @@ -26,22 +26,27 @@ 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; 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.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 +59,15 @@ 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_UPTIME = "lastcrash"; + private static final String CONFIG_MIN_CRASH_INTERVAL_MS = "min_crash_interval"; + + // 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 * 3_600_000L; private static NetworkStackClient sInstance; @@ -67,12 +81,34 @@ public class NetworkStackClient { @GuardedBy("mLog") private final SharedLog mLog = new SharedLog(TAG); - private volatile boolean mNetworkStackStartRequested = false; + private volatile boolean mWasSystemServerInitialized = false; + + /** + * If non-zero, indicates that the last framework start happened after a crash of the + * NetworkStack which was at the specified uptime. + */ + private volatile long mLastCrashUptime = 0L; + + @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 +122,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 +192,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 +210,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 +244,7 @@ public class NetworkStackClient { */ public void init() { log("Network stack init"); - mNetworkStackStartRequested = true; + mWasSystemServerInitialized = true; } /** @@ -202,6 +257,13 @@ public class NetworkStackClient { */ public void start(Context context) { log("Starting network stack"); + + final SharedPreferences prefs = getSharedPreferences(context); + mLastCrashUptime = prefs.getLong(PREF_KEY_LAST_CRASH_UPTIME, 0L); + // Remove the preference after getting the last crash uptime, so mLastCrashUptime always + // indicates this is the first start since the last crash. + prefs.edit().remove(PREF_KEY_LAST_CRASH_UPTIME).commit(); + final PackageManager pm = context.getPackageManager(); // Try to bind in-process if the device was shipped with an in-process version @@ -216,16 +278,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 +339,47 @@ 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 minCrashIntervalMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY, + CONFIG_MIN_CRASH_INTERVAL_MS, DEFAULT_MIN_CRASH_INTERVAL_MS); + + // Either the framework was not restarted after a crash of the NetworkStack, or the min + // crash interval has passed since then. + if (mLastCrashUptime == 0L || uptime - mLastCrashUptime > minCrashIntervalMs) { + // 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. + final SharedPreferences prefs = getSharedPreferences(context); + if (!prefs.edit().putLong(PREF_KEY_LAST_CRASH_UPTIME, uptime).commit()) { + logWtf("Could not persist last crash uptime", null); + } 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); + } + } + } + + private SharedPreferences getSharedPreferences(Context context) { + final File prefsFile = new File( + Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), PREFS_FILE); + return context.createDeviceProtectedStorageContext() + .getSharedPreferences(prefsFile, Context.MODE_PRIVATE); } /** @@ -350,7 +451,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/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..97716377ea2e 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.fail; import android.Manifest; import android.content.Context; import android.content.pm.VersionedPackage; +import android.net.NetworkStackClient; import android.os.Handler; import android.os.test.TestLooper; import android.provider.DeviceConfig; @@ -41,6 +42,8 @@ import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.File; import java.util.ArrayList; @@ -70,9 +73,12 @@ 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; + @Mock + private NetworkStackClient mNetworkStackClient; @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG); @@ -732,7 +738,8 @@ public class PackageWatchdogTest { new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); Handler handler = new Handler(mTestLooper.getLooper()); PackageWatchdog watchdog = - new PackageWatchdog(context, policyFile, handler, handler, controller); + new PackageWatchdog(context, policyFile, handler, handler, controller, + mNetworkStackClient); // Verify controller is not automatically started assertFalse(controller.mIsEnabled); if (withPackagesReady) { 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"); + } } |