diff options
81 files changed, 1463 insertions, 718 deletions
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index 15a2f22e0fea..8a0f66040711 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -88,27 +88,4 @@ java_sdk_library { "com.android.os.statsd", "test_com.android.os.statsd", ], -} - -android_test { - name: "FrameworkStatsdTest", - platform_apis: true, - srcs: [ - // TODO(b/147705194): Use framework-statsd as a lib dependency instead. - ":framework-statsd-sources", - "test/**/*.java", - ], - manifest: "test/AndroidManifest.xml", - static_libs: [ - "androidx.test.rules", - "truth-prebuilt", - ], - libs: [ - "android.test.runner.stubs", - "android.test.base.stubs", - ], - test_suites: [ - "device-tests", - ], -} - +}
\ No newline at end of file diff --git a/apex/statsd/framework/test/Android.bp b/apex/statsd/framework/test/Android.bp new file mode 100644 index 000000000000..b113d595b57c --- /dev/null +++ b/apex/statsd/framework/test/Android.bp @@ -0,0 +1,36 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "FrameworkStatsdTest", + platform_apis: true, + srcs: [ + // TODO(b/147705194): Use framework-statsd as a lib dependency instead. + ":framework-statsd-sources", + "**/*.java", + ], + manifest: "AndroidManifest.xml", + static_libs: [ + "androidx.test.rules", + "truth-prebuilt", + ], + libs: [ + "android.test.runner.stubs", + "android.test.base.stubs", + ], + test_suites: [ + "device-tests", + "mts", + ], +}
\ No newline at end of file diff --git a/apex/statsd/framework/test/AndroidTest.xml b/apex/statsd/framework/test/AndroidTest.xml new file mode 100644 index 000000000000..fb519150ecd5 --- /dev/null +++ b/apex/statsd/framework/test/AndroidTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Tests for Statsd."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="FrameworkStatsdTest.apk" /> + <option name="install-arg" value="-g" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="mts" /> + <option name="test-tag" value="FrameworkStatsdTest" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.os.statsd.framework.test" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.os.statsd" /> + </object> +</configuration>
\ No newline at end of file diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 7a445a4cf8a8..042983be9ab7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -482,6 +482,7 @@ message Atom { BlobLeased blob_leased = 299 [(module) = "framework"]; BlobOpened blob_opened = 300 [(module) = "framework"]; ContactsProviderStatusReported contacts_provider_status_reported = 301; + KeystoreKeyEventReported keystore_key_event_reported = 302; // StatsdStats tracks platform atoms with ids upto 500. // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. @@ -10926,6 +10927,114 @@ message MediametricsAudioDeviceConnectionReported { optional int32 connection_count = 6; } +/** + * Logs: i) creation of different types of cryptographic keys in the keystore, + * ii) operations performed using the keys, + * iii) attestation of the keys + * Logged from: system/security/keystore/key_event_log_handler.cpp + */ +message KeystoreKeyEventReported { + + enum Algorithm { + /** Asymmetric algorithms. */ + RSA = 1; + // 2 removed, do not reuse. + EC = 3; + /** Block cipher algorithms */ + AES = 32; + TRIPLE_DES = 33; + /** MAC algorithms */ + HMAC = 128; + }; + /** Algorithm associated with the key */ + optional Algorithm algorithm = 1; + + /** Size of the key */ + optional int32 key_size = 2; + + enum KeyOrigin { + /** Generated in keymaster. Should not exist outside the TEE. */ + GENERATED = 0; + /** Derived inside keymaster. Likely exists off-device. */ + DERIVED = 1; + /** Imported into keymaster. Existed as cleartext in Android. */ + IMPORTED = 2; + /** Keymaster did not record origin. */ + UNKNOWN = 3; + /** Securely imported into Keymaster. */ + SECURELY_IMPORTED = 4; + }; + /* Logs whether the key was generated, imported, securely imported, or derived.*/ + optional KeyOrigin key_origin = 3; + + enum HardwareAuthenticatorType { + NONE = 0; + PASSWORD = 1; + FINGERPRINT = 2; + // Additional entries must be powers of 2. + }; + /** + * What auth types does this key require? If none, + * then no auth required. + */ + optional HardwareAuthenticatorType user_auth_type = 4; + + /** + * If user authentication is required, is the requirement time based? If it + * is not time based then this field will not be used and the key is per + * operation. Per operation keys must be user authenticated on each usage. + */ + optional int32 user_auth_key_timeout_secs = 5; + + /** + * padding mode, digest, block_mode and purpose should ideally be repeated + * fields. However, since statsd does not support repeated fields in + * pushed atoms, they are represented using bitmaps. + */ + + /** Track which padding mode is being used.*/ + optional int32 padding_mode_bitmap = 6; + + /** Track which digest is being used. */ + optional int32 digest_bitmap = 7; + + /** Track what block mode is being used (for encryption). */ + optional int32 block_mode_bitmap = 8; + + /** Track what purpose is this key serving. */ + optional int32 purpose_bitmap = 9; + + enum EcCurve { + P_224 = 0; + P_256 = 1; + P_384 = 2; + P_521 = 3; + }; + /** Which ec curve was selected if elliptic curve cryptography is in use **/ + optional EcCurve ec_curve = 10; + + enum KeyBlobUsageRequirements { + STANDALONE = 0; + REQUIRES_FILE_SYSTEM = 1; + }; + /** Standalone or is a file system required */ + optional KeyBlobUsageRequirements key_blob_usage_reqs = 11; + + enum Type { + key_operation = 0; + key_creation = 1; + key_attestation = 2; + } + /** Key creation event, operation event or attestation event? */ + optional Type type = 12; + + /** Was the key creation, operation, or attestation successful? */ + optional bool was_successful = 13; + + /** Response code or error code */ + optional int32 error_code = 14; +} + // Blob Committer stats // Keep in sync between: // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index 23f8ca4e74e6..a21eb9b9147f 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -33,6 +33,12 @@ namespace android { namespace os { namespace statsd { +// These constants must be kept in sync with those in StatsDimensionsValue.java. +const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + namespace { void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, const vector<int>& attributionUids, const vector<string>& attributionTags, @@ -291,34 +297,76 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) { } } -//TODO(b/149050405) Update this test for StatsDimensionValueParcel -//TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { -// HashableDimensionKey dim; -// -// int pos1[] = {1, 1, 1}; -// int pos2[] = {1, 1, 2}; -// int pos3[] = {1, 1, 3}; -// int pos4[] = {2, 0, 0}; -// -// Field field1(10, pos1, 2); -// Field field2(10, pos2, 2); -// Field field3(10, pos3, 2); -// Field field4(10, pos4, 0); -// -// Value value1((int32_t)10025); -// Value value2("tag"); -// Value value3((int32_t)987654); -// Value value4((int32_t)99999); -// -// dim.addValue(FieldValue(field1, value1)); -// dim.addValue(FieldValue(field2, value2)); -// dim.addValue(FieldValue(field3, value3)); -// dim.addValue(FieldValue(field4, value4)); -// -// SubscriberReporter::getStatsDimensionsValue(dim); -// // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't -// // have any read api. -//} +void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel, + int32_t nodeDepthInAttributionChain, + int32_t uid, string tag) { + EXPECT_EQ(attributionNodeParcel.field, nodeDepthInAttributionChain /*position at depth 1*/); + ASSERT_EQ(attributionNodeParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionNodeParcel.tupleValue.size(), 2); + + StatsDimensionsValueParcel uidParcel = attributionNodeParcel.tupleValue[0]; + EXPECT_EQ(uidParcel.field, 1 /*position at depth 2*/); + EXPECT_EQ(uidParcel.valueType, STATS_DIMENSIONS_VALUE_INT_TYPE); + EXPECT_EQ(uidParcel.intValue, uid); + + StatsDimensionsValueParcel tagParcel = attributionNodeParcel.tupleValue[1]; + EXPECT_EQ(tagParcel.field, 2 /*position at depth 2*/); + EXPECT_EQ(tagParcel.valueType, STATS_DIMENSIONS_VALUE_STRING_TYPE); + EXPECT_EQ(tagParcel.stringValue, tag); +} + +// Test conversion of a HashableDimensionKey into a StatsDimensionValueParcel +TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { + int atomId = 10; + // First four fields form an attribution chain + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 2, 1}; + int pos4[] = {1, 2, 2}; + int pos5[] = {2, 1, 1}; + + Field field1(atomId, pos1, /*depth=*/2); + Field field2(atomId, pos2, /*depth=*/2); + Field field3(atomId, pos3, /*depth=*/2); + Field field4(atomId, pos4, /*depth=*/2); + Field field5(atomId, pos5, /*depth=*/0); + + Value value1((int32_t)1); + Value value2("string2"); + Value value3((int32_t)3); + Value value4("string4"); + Value value5((float)5.0); + + HashableDimensionKey dimensionKey; + dimensionKey.addValue(FieldValue(field1, value1)); + dimensionKey.addValue(FieldValue(field2, value2)); + dimensionKey.addValue(FieldValue(field3, value3)); + dimensionKey.addValue(FieldValue(field4, value4)); + dimensionKey.addValue(FieldValue(field5, value5)); + + StatsDimensionsValueParcel rootParcel = dimensionKey.toStatsDimensionsValueParcel(); + EXPECT_EQ(rootParcel.field, atomId); + ASSERT_EQ(rootParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(rootParcel.tupleValue.size(), 2); + + // Check that attribution chain is populated correctly + StatsDimensionsValueParcel attributionChainParcel = rootParcel.tupleValue[0]; + EXPECT_EQ(attributionChainParcel.field, 1 /*position at depth 0*/); + ASSERT_EQ(attributionChainParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionChainParcel.tupleValue.size(), 2); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[0], + /*nodeDepthInAttributionChain=*/1, + value1.int_value, value2.str_value); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[1], + /*nodeDepthInAttributionChain=*/2, + value3.int_value, value4.str_value); + + // Check that the float is populated correctly + StatsDimensionsValueParcel floatParcel = rootParcel.tupleValue[1]; + EXPECT_EQ(floatParcel.field, 2 /*position at depth 0*/); + EXPECT_EQ(floatParcel.valueType, STATS_DIMENSIONS_VALUE_FLOAT_TYPE); + EXPECT_EQ(floatParcel.floatValue, value5.float_value); +} TEST(AtomMatcherTest, TestWriteDimensionToProto) { HashableDimensionKey dim; diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java index 24be45cb20fe..8e687413b7e1 100644 --- a/core/java/android/companion/BluetoothDeviceFilterUtils.java +++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java @@ -51,13 +51,6 @@ public class BluetoothDeviceFilterUtils { return s == null ? null : Pattern.compile(s); } - static boolean matches(ScanFilter filter, BluetoothDevice device) { - boolean result = matchesAddress(filter.getDeviceAddress(), device) - && matchesServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask(), device); - if (DEBUG) debugLogMatchResult(result, device, filter); - return result; - } - static boolean matchesAddress(String deviceAddress, BluetoothDevice device) { final boolean result = deviceAddress == null || (device != null && deviceAddress.equals(device.getAddress())); diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java index dccfb0346c9c..8c071fe99104 100644 --- a/core/java/android/companion/BluetoothLeDeviceFilter.java +++ b/core/java/android/companion/BluetoothLeDeviceFilter.java @@ -37,7 +37,6 @@ import android.util.Log; import com.android.internal.util.BitUtils; import com.android.internal.util.ObjectUtils; -import com.android.internal.util.Preconditions; import libcore.util.HexEncoding; @@ -166,21 +165,18 @@ public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> { /** @hide */ @Override - public boolean matches(ScanResult device) { - boolean result = matches(device.getDevice()) + public boolean matches(ScanResult scanResult) { + BluetoothDevice device = scanResult.getDevice(); + boolean result = getScanFilter().matches(scanResult) + && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device) && (mRawDataFilter == null - || BitUtils.maskedEquals(device.getScanRecord().getBytes(), + || BitUtils.maskedEquals(scanResult.getScanRecord().getBytes(), mRawDataFilter, mRawDataFilterMask)); if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device + ") -> " + result); return result; } - private boolean matches(BluetoothDevice device) { - return BluetoothDeviceFilterUtils.matches(getScanFilter(), device) - && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); - } - /** @hide */ @Override public int getMediumType() { diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index a3fd60e9d3b0..004f84422b44 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -900,9 +900,17 @@ public final class NetworkCapabilities implements Parcelable { * <p>For NetworkCapability instances being sent from ConnectivityService, this value MUST be * reset to Process.INVALID_UID unless all the following conditions are met: * + * <p>The caller is the network owner, AND one of the following sets of requirements is met: + * + * <ol> + * <li>The described Network is a VPN + * </ol> + * + * <p>OR: + * * <ol> - * <li>The destination app is the network owner - * <li>The destination app has the ACCESS_FINE_LOCATION permission granted + * <li>The calling app is the network owner + * <li>The calling app has the ACCESS_FINE_LOCATION permission granted * <li>The user's location toggle is on * </ol> * @@ -928,7 +936,16 @@ public final class NetworkCapabilities implements Parcelable { /** * Retrieves the UID of the app that owns this network. * - * <p>For user privacy reasons, this field will only be populated if: + * <p>For user privacy reasons, this field will only be populated if the following conditions + * are met: + * + * <p>The caller is the network owner, AND one of the following sets of requirements is met: + * + * <ol> + * <li>The described Network is a VPN + * </ol> + * + * <p>OR: * * <ol> * <li>The calling app is the network owner @@ -936,8 +953,8 @@ public final class NetworkCapabilities implements Parcelable { * <li>The user's location toggle is on * </ol> * - * Instances of NetworkCapabilities sent to apps without the appropriate permissions will - * have this field cleared out. + * Instances of NetworkCapabilities sent to apps without the appropriate permissions will have + * this field cleared out. */ public int getOwnerUid() { return mOwnerUid; diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index e550f85e6b9a..98760761736d 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -26,7 +26,6 @@ import android.net.util.NetUtils; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import android.util.Pair; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -554,15 +553,45 @@ public final class RouteInfo implements Parcelable { } /** - * A helper class that contains the destination and the gateway in a {@code RouteInfo}, - * used by {@link ConnectivityService#updateRoutes} or + * A helper class that contains the destination, the gateway and the interface in a + * {@code RouteInfo}, used by {@link ConnectivityService#updateRoutes} or * {@link LinkProperties#addRoute} to calculate the list to be updated. + * {@code RouteInfo} objects with different interfaces are treated as different routes because + * *usually* on Android different interfaces use different routing tables, and moving a route + * to a new routing table never constitutes an update, but is always a remove and an add. * * @hide */ - public static class RouteKey extends Pair<IpPrefix, InetAddress> { - RouteKey(@NonNull IpPrefix destination, @Nullable InetAddress gateway) { - super(destination, gateway); + public static class RouteKey { + @NonNull private final IpPrefix mDestination; + @Nullable private final InetAddress mGateway; + @Nullable private final String mInterface; + + RouteKey(@NonNull IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface) { + mDestination = destination; + mGateway = gateway; + mInterface = iface; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RouteKey)) { + return false; + } + RouteKey p = (RouteKey) o; + // No need to do anything special for scoped addresses. Inet6Address#equals does not + // consider the scope ID, but the netd route IPCs (e.g., INetd#networkAddRouteParcel) + // and the kernel ignore scoped addresses both in the prefix and in the nexthop and only + // look at RTA_OIF. + return Objects.equals(p.mDestination, mDestination) + && Objects.equals(p.mGateway, mGateway) + && Objects.equals(p.mInterface, mInterface); + } + + @Override + public int hashCode() { + return Objects.hash(mDestination, mGateway, mInterface); } } @@ -574,7 +603,7 @@ public final class RouteInfo implements Parcelable { */ @NonNull public RouteKey getRouteKey() { - return new RouteKey(mDestination, mGateway); + return new RouteKey(mDestination, mGateway, mInterface); } /** diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index c2234bad3803..95cc64ae8aab 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -564,9 +564,9 @@ public abstract class AugmentedAutofillService extends Service { } void reportResult(@Nullable List<Dataset> inlineSuggestionsData, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, boolean showingFillWindow) { try { - mCallback.onSuccess(inlineSuggestionsData, clientState); + mCallback.onSuccess(inlineSuggestionsData, clientState, showingFillWindow); } catch (RemoteException e) { Log.e(TAG, "Error calling back with the inline suggestions data: " + e); } diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index 8ba5c173890c..fc3baf1c9836 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -56,23 +56,24 @@ public final class FillCallback { if (response == null) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE); - mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ null); + mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ + null, /* showingFillWindow */ false); return; } - List<Dataset> inlineSuggestions = response.getInlineSuggestions(); - Bundle clientState = response.getClientState(); - // We need to report result regardless of whether inline suggestions are returned or not. - mProxy.reportResult(inlineSuggestions, clientState); + final List<Dataset> inlineSuggestions = response.getInlineSuggestions(); + final Bundle clientState = response.getClientState(); + final FillWindow fillWindow = response.getFillWindow(); + boolean showingFillWindow = false; if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE); - return; - } - - final FillWindow fillWindow = response.getFillWindow(); - if (fillWindow != null) { + } else if (fillWindow != null) { fillWindow.show(); + showingFillWindow = true; } + // We need to report result regardless of whether inline suggestions are returned or not. + mProxy.reportResult(inlineSuggestions, clientState, showingFillWindow); + // TODO(b/123099468): must notify the server so it can update the session state to avoid // showing conflicting UIs (for example, if a new request is made to the main autofill // service and it now wants to show something). diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index 609e382e2b96..4dfdd4db27e5 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -30,7 +30,9 @@ import java.util.List; */ interface IFillCallback { void onCancellable(in ICancellationSignal cancellation); - void onSuccess(in @nullable List<Dataset> inlineSuggestionsData, in @nullable Bundle clientState); + void onSuccess(in @nullable List<Dataset> inlineSuggestionsData, + in @nullable Bundle clientState, + boolean showingFillWindow); boolean isCompleted(); void cancel(); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 6f73e8985a8a..e455155a319e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -230,7 +230,6 @@ public final class SurfaceControl implements Parcelable { */ public long mNativeObject; private long mNativeHandle; - private Throwable mReleaseStack = null; // TODO: Move this to native. private final Object mSizeLock = new Object(); @@ -442,13 +441,6 @@ public final class SurfaceControl implements Parcelable { } mNativeObject = nativeObject; mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0; - if (mNativeObject == 0) { - if (Build.IS_DEBUGGABLE) { - mReleaseStack = new Throwable("assigned zero nativeObject here"); - } - } else { - mReleaseStack = null; - } } /** @@ -1024,22 +1016,11 @@ public final class SurfaceControl implements Parcelable { nativeRelease(mNativeObject); mNativeObject = 0; mNativeHandle = 0; - if (Build.IS_DEBUGGABLE) { - mReleaseStack = new Throwable("released here"); - } mCloseGuard.close(); } } /** - * Returns the call stack that assigned mNativeObject to zero. - * @hide - */ - public Throwable getReleaseStack() { - return mReleaseStack; - } - - /** * Disconnect any client still connected to the surface. * @hide */ @@ -1050,11 +1031,8 @@ public final class SurfaceControl implements Parcelable { } private void checkNotReleased() { - if (mNativeObject == 0) { - Log.wtf(TAG, "Invalid " + this + " caused by:", mReleaseStack); - throw new NullPointerException( - "mNativeObject of " + this + " is null. Have you called release() already?"); - } + if (mNativeObject == 0) throw new NullPointerException( + "Invalid " + this + ", mNativeObject is null. Have you called release() already?"); } /** diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 0a1e3a0caeff..dc4c8fd76e8e 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -195,6 +195,7 @@ public class ChooserActivity extends ResolverActivity implements private boolean mIsAppPredictorComponentAvailable; private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache; private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache; + private Map<ComponentName, ComponentName> mChooserTargetComponentNameCache; public static final int TARGET_TYPE_DEFAULT = 0; public static final int TARGET_TYPE_CHOOSER_TARGET = 1; @@ -511,6 +512,11 @@ public class ChooserActivity extends ResolverActivity implements adapterForUserHandle.addServiceResults(sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET, /* directShareShortcutInfoCache */ null, mServiceConnections); + if (!sri.resultTargets.isEmpty() && sri.originalTarget != null) { + mChooserTargetComponentNameCache.put( + sri.resultTargets.get(0).getComponentName(), + sri.originalTarget.getResolvedComponentName()); + } } } unbindService(sri.connection); @@ -772,6 +778,7 @@ public class ChooserActivity extends ResolverActivity implements target.getAction() ); mDirectShareShortcutInfoCache = new HashMap<>(); + mChooserTargetComponentNameCache = new HashMap<>(); } @Override @@ -1063,6 +1070,10 @@ public class ChooserActivity extends ResolverActivity implements @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + ViewPager viewPager = findViewById(R.id.profile_pager); + if (shouldShowTabs() && viewPager.isLayoutRtl()) { + mMultiProfilePagerAdapter.setupViewPager(viewPager); + } mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); adjustPreviewWidth(newConfig.orientation, null); @@ -2238,15 +2249,18 @@ public class ChooserActivity extends ResolverActivity implements List<AppTargetId> targetIds = new ArrayList<>(); for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) { ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget(); - String componentName = chooserTarget.getComponentName().flattenToString(); + ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( + chooserTarget.getComponentName(), chooserTarget.getComponentName()); if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) { String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId(); targetIds.add(new AppTargetId( - String.format("%s/%s/%s", shortcutId, componentName, SHORTCUT_TARGET))); + String.format("%s/%s/%s", shortcutId, componentName.flattenToString(), + SHORTCUT_TARGET))); } else { String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString()); targetIds.add(new AppTargetId( - String.format("%s/%s/%s", titleHash, componentName, CHOOSER_TARGET))); + String.format("%s/%s/%s", titleHash, componentName.flattenToString(), + CHOOSER_TARGET))); } } directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds); @@ -2268,7 +2282,8 @@ public class ChooserActivity extends ResolverActivity implements } if (mChooserTargetRankingEnabled && appTarget == null) { // Send ChooserTarget sharing info to AppPredictor. - ComponentName componentName = chooserTarget.getComponentName(); + ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( + chooserTarget.getComponentName(), chooserTarget.getComponentName()); try { appTarget = new AppTarget.Builder( new AppTargetId(componentName.flattenToString()), @@ -2796,17 +2811,7 @@ public class ChooserActivity extends ResolverActivity implements || chooserListAdapter.mDisplayList.isEmpty()) { chooserListAdapter.notifyDataSetChanged(); } else { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... voids) { - chooserListAdapter.updateAlphabeticalList(); - return null; - } - @Override - protected void onPostExecute(Void aVoid) { - chooserListAdapter.notifyDataSetChanged(); - } - }.execute(); + chooserListAdapter.updateAlphabeticalList(); } // don't support direct share on low ram devices diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index d6ff7b13c934..05169023000f 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -275,33 +275,43 @@ public class ChooserListAdapter extends ResolverListAdapter { } void updateAlphabeticalList() { - mSortedList.clear(); - List<DisplayResolveInfo> tempList = new ArrayList<>(); - tempList.addAll(mDisplayList); - tempList.addAll(mCallerTargets); - if (mEnableStackedApps) { - // Consolidate multiple targets from same app. - Map<String, DisplayResolveInfo> consolidated = new HashMap<>(); - for (DisplayResolveInfo info : tempList) { - String packageName = info.getResolvedComponentName().getPackageName(); - DisplayResolveInfo multiDri = consolidated.get(packageName); - if (multiDri == null) { - consolidated.put(packageName, info); - } else if (multiDri instanceof MultiDisplayResolveInfo) { - ((MultiDisplayResolveInfo) multiDri).addTarget(info); - } else { - // create consolidated target from the single DisplayResolveInfo - MultiDisplayResolveInfo multiDisplayResolveInfo = + new AsyncTask<Void, Void, List<DisplayResolveInfo>>() { + @Override + protected List<DisplayResolveInfo> doInBackground(Void... voids) { + List<DisplayResolveInfo> allTargets = new ArrayList<>(); + allTargets.addAll(mDisplayList); + allTargets.addAll(mCallerTargets); + if (!mEnableStackedApps) { + return allTargets; + } + // Consolidate multiple targets from same app. + Map<String, DisplayResolveInfo> consolidated = new HashMap<>(); + for (DisplayResolveInfo info : allTargets) { + String packageName = info.getResolvedComponentName().getPackageName(); + DisplayResolveInfo multiDri = consolidated.get(packageName); + if (multiDri == null) { + consolidated.put(packageName, info); + } else if (multiDri instanceof MultiDisplayResolveInfo) { + ((MultiDisplayResolveInfo) multiDri).addTarget(info); + } else { + // create consolidated target from the single DisplayResolveInfo + MultiDisplayResolveInfo multiDisplayResolveInfo = new MultiDisplayResolveInfo(packageName, multiDri); - multiDisplayResolveInfo.addTarget(info); - consolidated.put(packageName, multiDisplayResolveInfo); + multiDisplayResolveInfo.addTarget(info); + consolidated.put(packageName, multiDisplayResolveInfo); + } } + List<DisplayResolveInfo> groupedTargets = new ArrayList<>(); + groupedTargets.addAll(consolidated.values()); + Collections.sort(groupedTargets, new ChooserActivity.AzInfoComparator(mContext)); + return groupedTargets; } - mSortedList.addAll(consolidated.values()); - } else { - mSortedList.addAll(tempList); - } - Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext)); + @Override + protected void onPostExecute(List<DisplayResolveInfo> newList) { + mSortedList = newList; + notifyDataSetChanged(); + } + }.execute(); } @Override diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index daacd459a1a7..86c13a0581c2 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1938,7 +1938,7 @@ public class ResolverActivity extends Activity implements ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); activeListAdapter.notifyDataSetChanged(); - if (activeListAdapter.getCount() == 0) { + if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) { // We no longer have any items... just finish the activity. finish(); } @@ -1948,6 +1948,13 @@ public class ResolverActivity extends Activity implements } } + private boolean inactiveListAdapterHasItems() { + if (!shouldShowTabs()) { + return false; + } + return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0; + } + private BroadcastReceiver createWorkProfileStateReceiver() { return new BroadcastReceiver() { @Override diff --git a/core/java/com/android/internal/app/ResolverViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java index 9cdfc2f5c763..478cc18f13ee 100644 --- a/core/java/com/android/internal/app/ResolverViewPager.java +++ b/core/java/com/android/internal/app/ResolverViewPager.java @@ -74,12 +74,16 @@ public class ResolverViewPager extends ViewPager { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + /** + * Sets whether swiping sideways should happen. + * <p>Note that swiping is always disabled for RTL layouts (b/159110029 for context). + */ void setSwipingEnabled(boolean swipingEnabled) { mSwipingEnabled = swipingEnabled; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - return mSwipingEnabled && super.onInterceptTouchEvent(ev); + return !isLayoutRtl() && mSwipingEnabled && super.onInterceptTouchEvent(ev); } } diff --git a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java index c11b939098c4..69ca9922e81f 100644 --- a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java +++ b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java @@ -59,7 +59,7 @@ import java.util.Objects; * #getProcessCpuUsageDiffed()} result. * * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of - * WestWorld, because the thresholding should be done after diffing, not before. This is because of + * statsd, because the thresholding should be done after diffing, not before. This is because of * two issues with thresholding before diffing: * * <ul> diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java index 7bfed91c42b9..6fe1d8140371 100644 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -16,6 +16,7 @@ package com.android.internal.policy; +import android.graphics.Insets; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; @@ -69,16 +70,14 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame private ColorDrawable mNavigationBarColor; private boolean mOldFullscreen; private boolean mFullscreen; - private final Rect mOldSystemInsets = new Rect(); - private final Rect mOldStableInsets = new Rect(); - private final Rect mSystemInsets = new Rect(); - private final Rect mStableInsets = new Rect(); + private final Rect mOldSystemBarInsets = new Rect(); + private final Rect mSystemBarInsets = new Rect(); private final Rect mTmpRect = new Rect(); public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, - boolean fullscreen, Rect systemInsets, Rect stableInsets) { + boolean fullscreen, Insets systemBarInsets) { setName("ResizeFrame"); mRenderer = renderer; @@ -95,10 +94,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mTargetRect.set(initialBounds); mFullscreen = fullscreen; mOldFullscreen = fullscreen; - mSystemInsets.set(systemInsets); - mStableInsets.set(stableInsets); - mOldSystemInsets.set(systemInsets); - mOldStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets.toRect()); + mOldSystemBarInsets.set(systemBarInsets.toRect()); // Kick off our draw thread. start(); @@ -154,16 +151,13 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame * * @param newTargetBounds The new target bounds. * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemInsets The current visible system insets for the window. - * @param stableInsets The stable insets for the window. + * @param systemBarInsets The current visible system insets for the window. */ - public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { + public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemBarInsets) { synchronized (this) { mFullscreen = fullscreen; mTargetRect.set(newTargetBounds); - mSystemInsets.set(systemInsets); - mStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets); // Notify of a bounds change. pingRenderLocked(false /* drawImmediate */); } @@ -247,14 +241,12 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mNewTargetRect.set(mTargetRect); if (!mNewTargetRect.equals(mOldTargetRect) || mOldFullscreen != mFullscreen - || !mStableInsets.equals(mOldStableInsets) - || !mSystemInsets.equals(mOldSystemInsets) + || !mSystemBarInsets.equals(mOldSystemBarInsets) || mReportNextDraw) { mOldFullscreen = mFullscreen; mOldTargetRect.set(mNewTargetRect); - mOldSystemInsets.set(mSystemInsets); - mOldStableInsets.set(mStableInsets); - redrawLocked(mNewTargetRect, mFullscreen, mSystemInsets, mStableInsets); + mOldSystemBarInsets.set(mSystemBarInsets); + redrawLocked(mNewTargetRect, mFullscreen); } } @@ -304,11 +296,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame * * @param newBounds The window bounds which needs to be drawn. * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemInsets The current visible system insets for the window. - * @param stableInsets The stable insets for the window. */ - private void redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { + private void redrawLocked(Rect newBounds, boolean fullscreen) { // While a configuration change is taking place the view hierarchy might become // inaccessible. For that case we remember the previous metrics to avoid flashes. @@ -355,7 +344,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame } mFrameAndBackdropNode.endRecording(); - drawColorViews(left, top, width, height, fullscreen, systemInsets, stableInsets); + drawColorViews(left, top, width, height, fullscreen); // We need to render the node explicitly mRenderer.drawRenderNode(mFrameAndBackdropNode); @@ -363,14 +352,13 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame reportDrawIfNeeded(); } - private void drawColorViews(int left, int top, int width, int height, - boolean fullscreen, Rect systemInsets, Rect stableInsets) { + private void drawColorViews(int left, int top, int width, int height, boolean fullscreen) { if (mSystemBarBackgroundNode == null) { return; } RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(width, height); mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); - final int topInset = DecorView.getColorViewTopInset(mStableInsets.top, mSystemInsets.top); + final int topInset = mSystemBarInsets.top; if (mStatusBarColor != null) { mStatusBarColor.setBounds(0, 0, left + width, topInset); mStatusBarColor.draw(canvas); @@ -380,7 +368,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame // don't want the navigation bar background be moving around when resizing in docked mode. // However, we need it for the transitions into/out of docked mode. if (mNavigationBarColor != null && fullscreen) { - DecorView.getNavigationBarRect(width, height, stableInsets, systemInsets, mTmpRect, 1f); + DecorView.getNavigationBarRect(width, height, mSystemBarInsets, mTmpRect, 1f); mNavigationBarColor.setBounds(mTmpRect); mNavigationBarColor.draw(canvas); } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index c6135f2c81d3..b12c5e9ba5b0 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1050,22 +1050,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return false; } - public static int getColorViewTopInset(int stableTop, int systemTop) { - return Math.min(stableTop, systemTop); - } - - public static int getColorViewBottomInset(int stableBottom, int systemBottom) { - return Math.min(stableBottom, systemBottom); - } - - public static int getColorViewRightInset(int stableRight, int systemRight) { - return Math.min(stableRight, systemRight); - } - - public static int getColorViewLeftInset(int stableLeft, int systemLeft) { - return Math.min(stableLeft, systemLeft); - } - public static boolean isNavBarToRightEdge(int bottomInset, int rightInset) { return bottomInset == 0 && rightInset > 0; } @@ -1079,14 +1063,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind : isNavBarToLeftEdge(bottomInset, leftInset) ? leftInset : bottomInset; } - public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect stableInsets, - Rect contentInsets, Rect outRect, float scale) { - final int bottomInset = - (int) (getColorViewBottomInset(stableInsets.bottom, contentInsets.bottom) * scale); - final int leftInset = - (int) (getColorViewLeftInset(stableInsets.left, contentInsets.left) * scale); - final int rightInset = - (int) (getColorViewLeftInset(stableInsets.right, contentInsets.right) * scale); + public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect systemBarInsets, + Rect outRect, float scale) { + final int bottomInset = (int) (systemBarInsets.bottom * scale); + final int leftInset = (int) (systemBarInsets.left * scale); + final int rightInset = (int) (systemBarInsets.right * scale); final int size = getNavBarSize(bottomInset, rightInset, leftInset); if (isNavBarToRightEdge(bottomInset, rightInset)) { outRect.set(canvasWidth - size, 0, canvasWidth, canvasHeight); @@ -1113,31 +1094,30 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLastWindowFlags = attrs.flags; if (insets != null) { - mLastTopInset = getColorViewTopInset(insets.getStableInsetTop(), - insets.getSystemWindowInsetTop()); - mLastBottomInset = getColorViewBottomInset(insets.getStableInsetBottom(), - insets.getSystemWindowInsetBottom()); - mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(), - insets.getSystemWindowInsetRight()); - mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(), - insets.getSystemWindowInsetLeft()); + final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars()); + final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( + WindowInsets.Type.systemBars()); + mLastTopInset = systemBarInsets.top; + mLastBottomInset = systemBarInsets.bottom; + mLastRightInset = systemBarInsets.right; + mLastLeftInset = systemBarInsets.left; // Don't animate if the presence of stable insets has changed, because that // indicates that the window was either just added and received them for the // first time, or the window size or position has changed. - boolean hasTopStableInset = insets.getStableInsetTop() != 0; + boolean hasTopStableInset = stableBarInsets.top != 0; disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset); mLastHasTopStableInset = hasTopStableInset; - boolean hasBottomStableInset = insets.getStableInsetBottom() != 0; + boolean hasBottomStableInset = stableBarInsets.bottom != 0; disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset); mLastHasBottomStableInset = hasBottomStableInset; - boolean hasRightStableInset = insets.getStableInsetRight() != 0; + boolean hasRightStableInset = stableBarInsets.right != 0; disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset); mLastHasRightStableInset = hasRightStableInset; - boolean hasLeftStableInset = insets.getStableInsetLeft() != 0; + boolean hasLeftStableInset = stableBarInsets.left != 0; disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset); mLastHasLeftStableInset = hasLeftStableInset; @@ -2296,7 +2276,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets) { if (mBackdropFrameRenderer != null) { - mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets, stableInsets); + mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets); } } @@ -2314,11 +2294,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final ThreadedRenderer renderer = getThreadedRenderer(); if (renderer != null) { loadBackgroundDrawablesIfNeeded(); + WindowInsets rootInsets = getRootWindowInsets(); mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), - getCurrentColor(mNavigationColorViewState), fullscreen, systemInsets, - stableInsets); + getCurrentColor(mNavigationColorViewState), fullscreen, + rootInsets.getInsets(WindowInsets.Type.systemBars())); // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. // If we want to get the shadow shown while resizing, we would need to elevate a new diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index b64923fb5bf8..5d4407bf8370 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -233,13 +233,20 @@ public class ConversationLayout extends FrameLayout oldVisibility = mImportanceRingView.getVisibility(); wasGone = oldVisibility == GONE; visibility = !mImportantConversation ? GONE : visibility; - isGone = visibility == GONE; - if (wasGone != isGone) { + boolean isRingGone = visibility == GONE; + if (wasGone != isRingGone) { // Keep the badge visibility in sync with the icon. This is necessary in cases // Where the icon is being hidden externally like in group children. mImportanceRingView.animate().cancel(); mImportanceRingView.setVisibility(visibility); } + + oldVisibility = mConversationIconBadge.getVisibility(); + wasGone = oldVisibility == GONE; + if (wasGone != isGone) { + mConversationIconBadge.animate().cancel(); + mConversationIconBadge.setVisibility(visibility); + } }); // When the small icon is gone, hide the rest of the badge mIcon.setOnForceHiddenChangedListener((forceHidden) -> { diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 53272f7eebf9..f312d1d4f25d 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -42,6 +42,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RemoteViews; +import android.widget.TextView; import com.android.internal.R; @@ -109,6 +110,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private ViewGroup mMessagingIconContainer; private int mConversationContentStart; private int mNonConversationMarginEnd; + private int mNotificationTextMarginTop; public MessagingGroup(@NonNull Context context) { super(context); @@ -148,6 +150,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou R.dimen.conversation_content_start); mNonConversationMarginEnd = getResources().getDimensionPixelSize( R.dimen.messaging_layout_margin_end); + mNotificationTextMarginTop = getResources().getDimensionPixelSize( + R.dimen.notification_text_margin_top); } public void updateClipRect() { @@ -612,7 +616,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return 0; } - public View getSenderView() { + public TextView getSenderView() { return mSenderView; } @@ -664,10 +668,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public void setSingleLine(boolean singleLine) { if (singleLine != mSingleLine) { mSingleLine = singleLine; + MarginLayoutParams p = (MarginLayoutParams) mMessageContainer.getLayoutParams(); + p.topMargin = singleLine ? 0 : mNotificationTextMarginTop; + mMessageContainer.setLayoutParams(p); mContentContainer.setOrientation( singleLine ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); MarginLayoutParams layoutParams = (MarginLayoutParams) mSenderView.getLayoutParams(); layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0); + mSenderView.setSingleLine(singleLine); updateMaxDisplayedLines(); updateClipRect(); updateSenderVisibility(); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index ac04862d9a7d..4ee647a42116 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -300,6 +300,27 @@ public class MessagingLinearLayout extends ViewGroup { return mMessagingLayout; } + @Override + public int getBaseline() { + // When placed in a horizontal linear layout (as is the case in a single-line MessageGroup), + // align with the last visible child (which is the one that will be displayed in the single- + // line group. + int childCount = getChildCount(); + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + if (isGone(child)) { + continue; + } + final int childBaseline = child.getBaseline(); + if (childBaseline == -1) { + return -1; + } + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + return lp.topMargin + childBaseline; + } + return super.getBaseline(); + } + public interface MessagingChild { int MEASURED_NORMAL = 0; int MEASURED_SHORTENED = 1; diff --git a/core/proto/android/stats/launcher/launcher.proto b/core/proto/android/stats/launcher/launcher.proto index dbd0e038c40c..fc177d57b193 100644 --- a/core/proto/android/stats/launcher/launcher.proto +++ b/core/proto/android/stats/launcher/launcher.proto @@ -32,10 +32,12 @@ enum LauncherAction { } enum LauncherState { - BACKGROUND = 0; - HOME = 1; - OVERVIEW = 2; - ALLAPPS = 3; + LAUNCHER_STATE_UNSPECIFIED = 0; + BACKGROUND = 1; + HOME = 2; + OVERVIEW = 3; + ALLAPPS = 4; + UNCHANGED = 5; } message LauncherTarget { diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml index 3188861a52a5..5e3b657353b6 100644 --- a/core/res/res/layout/notification_template_messaging_group.xml +++ b/core/res/res/layout/notification_template_messaging_group.xml @@ -37,6 +37,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" + android:baselineAligned="true" android:orientation="vertical"> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/message_name" diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp index d6542de91e08..e5492714b29f 100644 --- a/data/etc/car/Android.bp +++ b/data/etc/car/Android.bp @@ -129,6 +129,13 @@ prebuilt_etc { } prebuilt_etc { + name: "privapp_whitelist_com.android.car.companiondevicesupport", + sub_dir: "permissions", + src: "com.android.car.companiondevicesupport.xml", + filename_from_src: true, +} + +prebuilt_etc { name: "privapp_whitelist_com.google.android.car.kitchensink", sub_dir: "permissions", src: "com.google.android.car.kitchensink.xml", diff --git a/data/etc/car/com.android.car.companiondevicesupport.xml b/data/etc/car/com.android.car.companiondevicesupport.xml new file mode 100644 index 000000000000..2067bab20d3d --- /dev/null +++ b/data/etc/car/com.android.car.companiondevicesupport.xml @@ -0,0 +1,24 @@ +<?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 + --> +<permissions> + <privapp-permissions package="com.android.car.companiondevicesupport"> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.PROVIDE_TRUST_AGENT"/> + <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> + </privapp-permissions> +</permissions> diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml index 7313d54c599a..0f561cb933e6 100644 --- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml +++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml @@ -41,7 +41,8 @@ android:paddingStart="16dp" android:paddingBottom="16dp" android:fontFamily="@*android:string/config_bodyFontFamilyMedium" - android:maxLines="1" + android:maxLines="2" + android:ellipsize="end" android:text="@string/bubbles_user_education_manage_title" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/> @@ -52,6 +53,8 @@ android:paddingStart="16dp" android:paddingBottom="24dp" android:text="@string/bubbles_user_education_manage" + android:maxLines="2" + android:ellipsize="end" android:fontFamily="@*android:string/config_bodyFontFamily" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/> diff --git a/packages/SystemUI/res/layout/home_handle.xml b/packages/SystemUI/res/layout/home_handle.xml index 7c5db105a09e..54a0b9fba39c 100644 --- a/packages/SystemUI/res/layout/home_handle.xml +++ b/packages/SystemUI/res/layout/home_handle.xml @@ -21,6 +21,7 @@ android:layout_width="@dimen/navigation_home_handle_width" android:layout_height="match_parent" android:layout_weight="0" + android:contentDescription="@string/accessibility_home" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" /> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index 55be77ca3be5..a86a46960bcb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -100,6 +100,7 @@ public class BadgedImageView extends ImageView { mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE); setFocusable(true); + setClickable(true); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 7f78ddf2cf1c..6377b4f0a9c5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -15,7 +15,6 @@ */ package com.android.systemui.bubbles; -import static android.app.Notification.FLAG_BUBBLE; import static android.os.AsyncTask.Status.FINISHED; import static android.view.Display.INVALID_DISPLAY; @@ -29,21 +28,19 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Path; -import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Bundle; +import android.graphics.drawable.Icon; import android.os.UserHandle; import android.provider.Settings; -import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.InstanceId; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -57,17 +54,12 @@ import java.util.Objects; class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; - /** - * NotificationEntry associated with the bubble. A null value implies this bubble is loaded - * from disk. - */ - @Nullable - private NotificationEntry mEntry; private final String mKey; private long mLastUpdated; private long mLastAccessed; + @Nullable private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; /** Whether the bubble should show a dot for the notification indicating updated content. */ @@ -75,8 +67,6 @@ class Bubble implements BubbleViewProvider { /** Whether flyout text should be suppressed, regardless of any other flags or state. */ private boolean mSuppressFlyout; - /** Whether this bubble should auto expand regardless of the normal flag, used for overflow. */ - private boolean mShouldAutoExpand; // Items that are typically loaded later private String mAppName; @@ -92,6 +82,7 @@ class Bubble implements BubbleViewProvider { * Presentational info about the flyout. */ public static class FlyoutMessage { + @Nullable public Icon senderIcon; @Nullable public Drawable senderAvatar; @Nullable public CharSequence senderName; @Nullable public CharSequence message; @@ -109,16 +100,39 @@ class Bubble implements BubbleViewProvider { private UserHandle mUser; @NonNull private String mPackageName; + @Nullable + private String mTitle; + @Nullable + private Icon mIcon; + private boolean mIsBubble; + private boolean mIsVisuallyInterruptive; + private boolean mIsClearable; + private boolean mShouldSuppressNotificationDot; + private boolean mShouldSuppressNotificationList; + private boolean mShouldSuppressPeek; private int mDesiredHeight; @DimenRes private int mDesiredHeightResId; + /** for logging **/ + @Nullable + private InstanceId mInstanceId; + @Nullable + private String mChannelId; + private int mNotificationId; + private int mAppUid = -1; + + @Nullable + private PendingIntent mIntent; + @Nullable + private PendingIntent mDeleteIntent; + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, - final int desiredHeight, final int desiredHeightResId) { + final int desiredHeight, final int desiredHeightResId, @Nullable final String title) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mShortcutInfo = shortcutInfo; @@ -126,8 +140,10 @@ class Bubble implements BubbleViewProvider { mFlags = 0; mUser = shortcutInfo.getUserHandle(); mPackageName = shortcutInfo.getPackage(); + mIcon = shortcutInfo.getIcon(); mDesiredHeight = desiredHeight; mDesiredHeightResId = desiredHeightResId; + mTitle = title; } /** Used in tests when no UI is required. */ @@ -145,12 +161,6 @@ class Bubble implements BubbleViewProvider { return mKey; } - @Nullable - public NotificationEntry getEntry() { - return mEntry; - } - - @NonNull public UserHandle getUser() { return mUser; } @@ -203,14 +213,7 @@ class Bubble implements BubbleViewProvider { @Nullable public String getTitle() { - final CharSequence titleCharSeq; - if (mEntry == null) { - titleCharSeq = null; - } else { - titleCharSeq = mEntry.getSbn().getNotification().extras.getCharSequence( - Notification.EXTRA_TITLE); - } - return titleCharSeq != null ? titleCharSeq.toString() : null; + return mTitle; } /** @@ -331,17 +334,45 @@ class Bubble implements BubbleViewProvider { void setEntry(@NonNull final NotificationEntry entry) { Objects.requireNonNull(entry); Objects.requireNonNull(entry.getSbn()); - mEntry = entry; mLastUpdated = entry.getSbn().getPostTime(); - mFlags = entry.getSbn().getNotification().flags; + mIsBubble = entry.getSbn().getNotification().isBubbleNotification(); mPackageName = entry.getSbn().getPackageName(); mUser = entry.getSbn().getUser(); + mTitle = getTitle(entry); + mIsClearable = entry.isClearable(); + mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot(); + mShouldSuppressNotificationList = entry.shouldSuppressNotificationList(); + mShouldSuppressPeek = entry.shouldSuppressPeek(); + mChannelId = entry.getSbn().getNotification().getChannelId(); + mNotificationId = entry.getSbn().getId(); + mAppUid = entry.getSbn().getUid(); + mInstanceId = entry.getSbn().getInstanceId(); + mFlyoutMessage = BubbleViewInfoTask.extractFlyoutMessage(entry); + mShortcutInfo = (entry.getBubbleMetadata() != null + && entry.getBubbleMetadata().getShortcutId() != null + && entry.getRanking() != null) ? entry.getRanking().getShortcutInfo() : null; + if (entry.getRanking() != null) { + mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive(); + } if (entry.getBubbleMetadata() != null) { + mFlags = entry.getBubbleMetadata().getFlags(); mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); + mIcon = entry.getBubbleMetadata().getIcon(); + mIntent = entry.getBubbleMetadata().getIntent(); + mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); } } + @Nullable + Icon getIcon() { + return mIcon; + } + + boolean isVisuallyInterruptive() { + return mIsVisuallyInterruptive; + } + /** * @return the last time this bubble was updated or accessed, whichever is most recent. */ @@ -364,6 +395,19 @@ class Bubble implements BubbleViewProvider { return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; } + public InstanceId getInstanceId() { + return mInstanceId; + } + + @Nullable + public String getChannelId() { + return mChannelId; + } + + public int getNotificationId() { + return mNotificationId; + } + /** * Should be invoked whenever a Bubble is accessed (selected while expanded). */ @@ -384,24 +428,19 @@ class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { - if (mEntry == null) return false; - return !shouldSuppressNotification() || !mEntry.isClearable(); + return !shouldSuppressNotification() || !mIsClearable; } /** * Sets whether this notification should be suppressed in the shade. */ void setSuppressNotification(boolean suppressNotification) { - if (mEntry == null) return; boolean prevShowInShade = showInShade(); - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - int flags = data.getFlags(); if (suppressNotification) { - flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } else { - flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } - data.setFlags(flags); if (showInShade() != prevShowInShade && mSuppressionListener != null) { mSuppressionListener.onBubbleNotificationSuppressionChange(this); @@ -424,9 +463,8 @@ class Bubble implements BubbleViewProvider { */ @Override public boolean showDot() { - if (mEntry == null) return false; return mShowBubbleUpdateDot - && !mEntry.shouldSuppressNotificationDot() + && !mShouldSuppressNotificationDot && !shouldSuppressNotification(); } @@ -434,10 +472,9 @@ class Bubble implements BubbleViewProvider { * Whether the flyout for the bubble should be shown. */ boolean showFlyout() { - if (mEntry == null) return false; - return !mSuppressFlyout && !mEntry.shouldSuppressPeek() + return !mSuppressFlyout && !mShouldSuppressPeek && !shouldSuppressNotification() - && !mEntry.shouldSuppressNotificationList(); + && !mShouldSuppressNotificationList; } /** @@ -480,25 +517,14 @@ class Bubble implements BubbleViewProvider { } } - /** - * Whether shortcut information should be used to populate the bubble. - * <p> - * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}. - * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}. - */ - boolean usingShortcutInfo() { - return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null - || mShortcutInfo != null; + @Nullable + PendingIntent getBubbleIntent() { + return mIntent; } @Nullable - PendingIntent getBubbleIntent() { - if (mEntry == null) return null; - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - if (data != null) { - return data.getIntent(); - } - return null; + PendingIntent getDeleteIntent() { + return mDeleteIntent; } Intent getSettingsIntent(final Context context) { @@ -514,8 +540,12 @@ class Bubble implements BubbleViewProvider { return intent; } + public int getAppUid() { + return mAppUid; + } + private int getUid(final Context context) { - if (mEntry != null) return mEntry.getSbn().getUid(); + if (mAppUid != -1) return mAppUid; final PackageManager pm = context.getPackageManager(); if (pm == null) return -1; try { @@ -548,24 +578,27 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { - if (mEntry == null) return true; - return mEntry.getBubbleMetadata() != null - && mEntry.getBubbleMetadata().isNotificationSuppressed(); + return isEnabled(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); } - boolean shouldAutoExpand() { - if (mEntry == null) return mShouldAutoExpand; - Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); - return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand; + public boolean shouldAutoExpand() { + return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } void setShouldAutoExpand(boolean shouldAutoExpand) { - mShouldAutoExpand = shouldAutoExpand; + if (shouldAutoExpand) { + enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + } else { + disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + } + } + + public void setIsBubble(final boolean isBubble) { + mIsBubble = isBubble; } public boolean isBubble() { - if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0; - return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0; + return mIsBubble; } public void enable(int option) { @@ -576,6 +609,10 @@ class Bubble implements BubbleViewProvider { mFlags &= ~option; } + public boolean isEnabled(int option) { + return (mFlags & option) != 0; + } + @Override public String toString() { return "Bubble{" + mKey + '}'; @@ -610,34 +647,24 @@ class Bubble implements BubbleViewProvider { @Override public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) { - if (this.getEntry() == null - || this.getEntry().getSbn() == null) { - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - null /* package name */, - null /* notification channel */, - 0 /* notification ID */, - 0 /* bubble position */, - bubbleCount, - action, - normalX, - normalY, - false /* unread bubble */, - false /* on-going bubble */, - false /* isAppForeground (unused) */); - } else { - StatusBarNotification notification = this.getEntry().getSbn(); - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - notification.getPackageName(), - notification.getNotification().getChannelId(), - notification.getId(), - index, - bubbleCount, - action, - normalX, - normalY, - this.showInShade(), - false /* isOngoing (unused) */, - false /* isAppForeground (unused) */); - } + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + mPackageName, + mChannelId, + mNotificationId, + index, + bubbleCount, + action, + normalX, + normalY, + showInShade(), + false /* isOngoing (unused) */, + false /* isAppForeground (unused) */); + } + + @Nullable + private static String getTitle(@NonNull final NotificationEntry e) { + final CharSequence titleCharSeq = e.getSbn().getNotification().extras.getCharSequence( + Notification.EXTRA_TITLE); + return titleCharSeq == null ? null : titleCharSeq.toString(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index c4c5da42ec06..c42920965ed3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -67,6 +67,7 @@ import android.util.Log; import android.util.Pair; import android.util.SparseSetArray; import android.view.Display; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -394,6 +395,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi : statusBarService; mBubbleScrim = new ScrimView(mContext); + mBubbleScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); mSavedBubbleKeysPerUser = new SparseSetArray<>(); mCurrentUserId = mNotifUserManager.getCurrentUserId(); @@ -505,8 +507,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi addNotifCallback(new NotifCallback() { @Override public void removeNotification(NotificationEntry entry, int reason) { - mNotificationEntryManager.performRemoveNotification(entry.getSbn(), - reason); + mNotificationEntryManager.performRemoveNotification(entry.getSbn(), reason); } @Override @@ -637,8 +638,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mStackView.setExpandListener(mExpandListener); } - mStackView.setUnbubbleConversationCallback(notificationEntry -> - onUserChangedBubble(notificationEntry, false /* shouldBubble */)); + mStackView.setUnbubbleConversationCallback(key -> { + final NotificationEntry entry = + mNotificationEntryManager.getPendingOrActiveNotif(key); + if (entry != null) { + onUserChangedBubble(entry, false /* shouldBubble */); + } + }); } addToWindowManagerMaybe(); @@ -1024,10 +1030,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param entry the notification to change bubble state for. * @param shouldBubble whether the notification should show as a bubble or not. */ - public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) { - if (entry == null) { - return; - } + public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) { NotificationChannel channel = entry.getChannel(); final String appPkg = entry.getSbn().getPackageName(); final int appUid = entry.getSbn().getUid(); @@ -1103,7 +1106,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleData.removeSuppressedSummary(groupKey); // Remove any associated bubble children with the summary - final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager); for (int i = 0; i < bubbleChildren.size(); i++) { removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); } @@ -1161,21 +1165,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) { Objects.requireNonNull(b); - if (isBubble) { - b.enable(FLAG_BUBBLE); - } else { - b.disable(FLAG_BUBBLE); - } - if (b.getEntry() != null) { + b.setIsBubble(isBubble); + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(b.getKey()); + if (entry != null) { // Updating the entry to be a bubble will trigger our normal update flow - setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand()); + setIsBubble(entry, isBubble, b.shouldAutoExpand()); } else if (isBubble) { - // If we have no entry to update, it's a persisted bubble so - // we need to add it to the stack ourselves + // If bubble doesn't exist, it's a persisted bubble so we need to add it to the + // stack ourselves Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, !bubble.shouldAutoExpand() /* showInShade */); - } } @@ -1214,6 +1215,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (reason == DISMISS_NOTIF_CANCEL) { bubblesToBeRemovedFromRepository.add(bubble); } + final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif( + bubble.getKey()); if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey()) && (!bubble.showInShade() @@ -1222,26 +1225,27 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { - if (bubble.getEntry() != null) { - cb.removeNotification(bubble.getEntry(), REASON_CANCEL); + if (entry != null) { + cb.removeNotification(entry, REASON_CANCEL); } } } else { if (bubble.isBubble()) { setIsBubble(bubble, false /* isBubble */); } - if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) { - bubble.getEntry().getRow().updateBubbleButton(); + if (entry != null && entry.getRow() != null) { + entry.getRow().updateBubbleButton(); } } } - if (bubble.getEntry() != null) { - final String groupKey = bubble.getEntry().getSbn().getGroupKey(); - if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { + if (entry != null) { + final String groupKey = entry.getSbn().getGroupKey(); + if (mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager).isEmpty()) { // Time to potentially remove the summary for (NotifCallback cb : mCallbacks) { - cb.maybeCancelSummary(bubble.getEntry()); + cb.maybeCancelSummary(entry); } } } @@ -1266,9 +1270,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (update.selectionChanged && mStackView != null) { mStackView.setSelectedBubble(update.selectedBubble); - if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) { - mNotificationGroupManager.updateSuppression( - update.selectedBubble.getEntry()); + if (update.selectedBubble != null) { + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(update.selectedBubble.getKey()); + if (entry != null) { + mNotificationGroupManager.updateSuppression(entry); + } } } @@ -1341,7 +1348,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } String groupKey = entry.getSbn().getGroupKey(); - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager); boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey())); boolean isSummary = entry.getSbn().getNotification().isGroupSummary(); @@ -1361,9 +1369,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // As far as group manager is concerned, once a child is no longer shown // in the shade, it is essentially removed. Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey()); - mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); - bubbleChild.setSuppressNotification(true); - bubbleChild.setShowDot(false /* show */); + if (bubbleChild != null) { + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(bubbleChild.getKey()); + if (entry != null) { + mNotificationGroupManager.onEntryRemoved(entry); + } + bubbleChild.setSuppressNotification(true); + bubbleChild.setShowDot(false /* show */); + } } else { // non-bubbled children can be removed for (NotifCallback cb : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 20a9a8cf324c..c8706126c1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -22,7 +22,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.NonNull; -import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.util.Log; @@ -34,6 +33,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController.DismissReason; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.io.FileDescriptor; @@ -256,8 +256,7 @@ public class BubbleData { } mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); - suppressFlyout |= bubble.getEntry() == null - || !bubble.getEntry().getRanking().visuallyInterruptive(); + suppressFlyout |= !bubble.isVisuallyInterruptive(); if (prevBubble == null) { // Create a new bubble @@ -335,13 +334,15 @@ public class BubbleData { * Retrieves any bubbles that are part of the notification group represented by the provided * group key. */ - ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) { + ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey, @NonNull + NotificationEntryManager nem) { ArrayList<Bubble> bubbleChildren = new ArrayList<>(); if (groupKey == null) { return bubbleChildren; } for (Bubble b : mBubbles) { - if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) { + final NotificationEntry entry = nem.getPendingOrActiveNotif(b.getKey()); + if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) { bubbleChildren.add(b); } } @@ -439,9 +440,7 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } - if (bubbleToRemove.getEntry() != null) { - maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); - } + maybeSendDeleteIntent(reason, bubbleToRemove); } void overflowBubble(@DismissReason int reason, Bubble bubble) { @@ -611,21 +610,14 @@ public class BubbleData { return true; } - private void maybeSendDeleteIntent(@DismissReason int reason, - @NonNull final NotificationEntry entry) { - if (reason == BubbleController.DISMISS_USER_GESTURE) { - Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); - PendingIntent deleteIntent = bubbleMetadata != null - ? bubbleMetadata.getDeleteIntent() - : null; - if (deleteIntent != null) { - try { - deleteIntent.send(); - } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "Failed to send delete intent for bubble with key: " - + entry.getKey()); - } - } + private void maybeSendDeleteIntent(@DismissReason int reason, @NonNull final Bubble bubble) { + if (reason != BubbleController.DISMISS_USER_GESTURE) return; + PendingIntent deleteIntent = bubble.getDeleteIntent(); + if (deleteIntent == null) return; + try { + deleteIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Failed to send delete intent for bubble with key: " + bubble.getKey()); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt index d20f40559b5d..0c25d144938c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt @@ -74,11 +74,15 @@ internal class BubbleDataRepository @Inject constructor( private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> - var shortcutId = b.shortcutInfo?.id - if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId - if (shortcutId == null) return@mapNotNull null - BubbleEntity(userId, b.packageName, shortcutId, b.key, b.rawDesiredHeight, - b.rawDesiredHeightResId) + BubbleEntity( + userId, + b.packageName, + b.shortcutInfo?.id ?: return@mapNotNull null, + b.key, + b.rawDesiredHeight, + b.rawDesiredHeightResId, + b.title + ) } } @@ -159,8 +163,13 @@ internal class BubbleDataRepository @Inject constructor( val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id } - ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo, entity.desiredHeight, - entity.desiredHeightResId) } + ?.let { shortcutInfo -> Bubble( + entity.key, + shortcutInfo, + entity.desiredHeight, + entity.desiredHeightResId, + entity.title + ) } } uiScope.launch { cb(bubbles) } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index bc03bf981914..959130bbdd0f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -171,7 +171,7 @@ public class BubbleExpandedView extends LinearLayout { return; } try { - if (!mIsOverflow && mBubble.usingShortcutInfo()) { + if (!mIsOverflow && mBubble.getShortcutInfo() != null) { options.setApplyActivityFlagsForBubbles(true); mActivityView.startShortcutActivity(mBubble.getShortcutInfo(), options, null /* sourceBounds */); @@ -668,7 +668,7 @@ public class BubbleExpandedView extends LinearLayout { desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight); } float height = Math.min(desiredHeight, getMaxExpandedHeight()); - height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight); + height = Math.max(height, mMinHeight); ViewGroup.LayoutParams lp = mActivityView.getLayoutParams(); mNeedsNewHeight = lp.height != height; if (!mKeyboardVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index 8c76cda3290f..1fa3aaae5e61 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -31,6 +31,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.text.TextUtils; import android.view.LayoutInflater; @@ -223,9 +224,10 @@ public class BubbleFlyoutView extends FrameLayout { float[] dotCenter, boolean hideDot) { - if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) { + final Drawable senderAvatar = flyoutMessage.senderAvatar; + if (senderAvatar != null && flyoutMessage.isGroupChat) { mSenderAvatar.setVisibility(VISIBLE); - mSenderAvatar.setImageDrawable(flyoutMessage.senderAvatar); + mSenderAvatar.setImageDrawable(senderAvatar); } else { mSenderAvatar.setVisibility(GONE); mSenderAvatar.setTranslationX(0); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java index 74231c648f00..a799f2d739e5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java @@ -15,7 +15,8 @@ */ package com.android.systemui.bubbles; -import android.app.Notification; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; @@ -50,15 +51,14 @@ public class BubbleIconFactory extends BaseIconFactory { /** * Returns the drawable that the developer has provided to display in the bubble. */ - Drawable getBubbleDrawable(Context context, ShortcutInfo shortcutInfo, - Notification.BubbleMetadata metadata) { + Drawable getBubbleDrawable(@NonNull final Context context, + @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) { if (shortcutInfo != null) { LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE); int density = context.getResources().getConfiguration().densityDpi; return launcherApps.getShortcutIconDrawable(shortcutInfo, density); } else { - Icon ic = metadata.getIcon(); if (ic != null) { if (ic.getType() == Icon.TYPE_URI || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java index c5faae0d703e..c1dd8c36ff6f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java @@ -16,8 +16,6 @@ package com.android.systemui.bubbles; -import android.service.notification.StatusBarNotification; - import com.android.internal.logging.UiEventLoggerImpl; /** @@ -32,12 +30,11 @@ public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger * @param e UI event */ public void log(Bubble b, UiEventEnum e) { - if (b.getEntry() == null) { + if (b.getInstanceId() == null) { // Added from persistence -- TODO log this with specific event? return; } - StatusBarNotification sbn = b.getEntry().getSbn(); - logWithInstanceId(e, sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId()); + logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index b644079be565..b9437078a330 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -27,7 +27,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; @@ -291,9 +290,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V }); // If the bubble was persisted, the entry is null but it should have shortcut info - ShortcutInfo info = b.getEntry() == null - ? b.getShortcutInfo() - : b.getEntry().getRanking().getShortcutInfo(); + ShortcutInfo info = b.getShortcutInfo(); if (info == null) { Log.d(TAG, "ShortcutInfo required to bubble but none found for " + b); } else { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 26ed1d121acb..297d92e3135f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -92,7 +92,6 @@ import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; @@ -303,7 +302,7 @@ public class BubbleStackView extends FrameLayout private BubbleController.BubbleExpandListener mExpandListener; /** Callback to run when we want to unbubble the given notification's conversation. */ - private Consumer<NotificationEntry> mUnbubbleConversationCallback; + private Consumer<String> mUnbubbleConversationCallback; private SysUiState mSysUiState; @@ -890,14 +889,7 @@ public class BubbleStackView extends FrameLayout (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { mExpandedAnimationController.updateResources(mOrientation, mDisplaySize); mStackAnimationController.updateResources(mOrientation); - - // Reposition & adjust the height for new orientation - if (mIsExpanded) { - mExpandedViewContainer.setTranslationY(getExpandedViewY()); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(getLocationOnScreen()); - } - } + mBubbleOverflow.updateDimensions(); // Need to update the padding around the view WindowInsets insets = getRootWindowInsets(); @@ -921,9 +913,15 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded) { // Re-draw bubble row and pointer for new orientation. + beforeExpandedViewAnimation(); + updateOverflowVisibility(); + updatePointerPosition(); mExpandedAnimationController.expandFromStack(() -> { - updatePointerPosition(); + afterExpandedViewAnimation(); } /* after */); + mExpandedViewContainer.setTranslationX(0); + mExpandedViewContainer.setTranslationY(getExpandedViewY()); + mExpandedViewContainer.setAlpha(1f); } if (mVerticalPosPercentBeforeRotation >= 0) { mStackAnimationController.moveStackToSimilarPositionAfterRotation( @@ -1057,10 +1055,7 @@ public class BubbleStackView extends FrameLayout mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener( view -> { showManageMenu(false /* show */); - final Bubble bubble = mBubbleData.getSelectedBubble(); - if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { - mUnbubbleConversationCallback.accept(bubble.getEntry()); - } + mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey()); }); mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener( @@ -1412,7 +1407,7 @@ public class BubbleStackView extends FrameLayout /** Sets the function to call to un-bubble the given conversation. */ public void setUnbubbleConversationCallback( - Consumer<NotificationEntry> unbubbleConversationCallback) { + Consumer<String> unbubbleConversationCallback) { mUnbubbleConversationCallback = unbubbleConversationCallback; } @@ -2515,6 +2510,10 @@ public class BubbleStackView extends FrameLayout .spring(DynamicAnimation.SCALE_Y, 1f) .spring(DynamicAnimation.TRANSLATION_X, targetX) .spring(DynamicAnimation.TRANSLATION_Y, targetY) + .withEndActions(() -> { + View child = mManageMenu.getChildAt(0); + child.requestAccessibilityFocus(); + }) .start(); mManageMenu.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index 525d5b56cc8e..3e4ff5262bbd 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -37,8 +37,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.Parcelable; -import android.os.UserHandle; -import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.util.PathParser; @@ -53,6 +51,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.lang.ref.WeakReference; import java.util.List; +import java.util.Objects; /** * Simple task to inflate views & load necessary info to display a bubble. @@ -129,35 +128,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @Nullable static BubbleViewInfo populate(Context c, BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) { - final NotificationEntry entry = b.getEntry(); - if (entry == null) { - // populate from ShortcutInfo when NotificationEntry is not available - final ShortcutInfo s = b.getShortcutInfo(); - return populate(c, stackView, iconFactory, skipInflation || b.isInflated(), - s.getPackage(), s.getUserHandle(), s, null); - } - final StatusBarNotification sbn = entry.getSbn(); - final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId(); - final ShortcutInfo si = bubbleShortcutId == null - ? null : entry.getRanking().getShortcutInfo(); - return populate( - c, stackView, iconFactory, skipInflation || b.isInflated(), - sbn.getPackageName(), sbn.getUser(), si, entry); - } - - private static BubbleViewInfo populate( - @NonNull final Context c, - @NonNull final BubbleStackView stackView, - @NonNull final BubbleIconFactory iconFactory, - final boolean isInflated, - @NonNull final String packageName, - @NonNull final UserHandle user, - @Nullable final ShortcutInfo shortcutInfo, - @Nullable final NotificationEntry entry) { BubbleViewInfo info = new BubbleViewInfo(); // View inflation: only should do this once per bubble - if (!isInflated) { + if (!skipInflation && !b.isInflated()) { LayoutInflater inflater = LayoutInflater.from(c); info.imageView = (BadgedImageView) inflater.inflate( R.layout.bubble_view, stackView, false /* attachToRoot */); @@ -167,8 +141,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask info.expandedView.setStackView(stackView); } - if (shortcutInfo != null) { - info.shortcutInfo = shortcutInfo; + if (b.getShortcutInfo() != null) { + info.shortcutInfo = b.getShortcutInfo(); } // App name & app icon @@ -178,7 +152,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Drawable appIcon; try { appInfo = pm.getApplicationInfo( - packageName, + b.getPackageName(), PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_UNAWARE @@ -186,17 +160,17 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (appInfo != null) { info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); } - appIcon = pm.getApplicationIcon(packageName); - badgedIcon = pm.getUserBadgedIcon(appIcon, user); + appIcon = pm.getApplicationIcon(b.getPackageName()); + badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); } catch (PackageManager.NameNotFoundException exception) { // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find package: " + packageName); + Log.w(TAG, "Unable to find package: " + b.getPackageName()); return null; } // Badged bubble image Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, - entry == null ? null : entry.getBubbleMetadata()); + b.getIcon()); if (bubbleDrawable == null) { // Default to app icon bubbleDrawable = appIcon; @@ -222,8 +196,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Color.WHITE, WHITE_SCRIM_ALPHA); // Flyout - if (entry != null) { - info.flyoutMessage = extractFlyoutMessage(c, entry); + info.flyoutMessage = b.getFlyoutMessage(); + if (info.flyoutMessage != null) { + info.flyoutMessage.senderAvatar = + loadSenderAvatar(c, info.flyoutMessage.senderIcon); } return info; } @@ -235,8 +211,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask * notification, based on its type. Returns null if there should not be an update message. */ @NonNull - static Bubble.FlyoutMessage extractFlyoutMessage(Context context, - NotificationEntry entry) { + static Bubble.FlyoutMessage extractFlyoutMessage(NotificationEntry entry) { + Objects.requireNonNull(entry); final Notification underlyingNotif = entry.getSbn().getNotification(); final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle(); @@ -264,20 +240,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (latestMessage != null) { bubbleMessage.message = latestMessage.getText(); Person sender = latestMessage.getSenderPerson(); - bubbleMessage.senderName = sender != null - ? sender.getName() - : null; - + bubbleMessage.senderName = sender != null ? sender.getName() : null; bubbleMessage.senderAvatar = null; - if (sender != null && sender.getIcon() != null) { - if (sender.getIcon().getType() == Icon.TYPE_URI - || sender.getIcon().getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { - context.grantUriPermission(context.getPackageName(), - sender.getIcon().getUri(), - Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - bubbleMessage.senderAvatar = sender.getIcon().loadDrawable(context); - } + bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null; return bubbleMessage; } } else if (Notification.InboxStyle.class.equals(style)) { @@ -306,4 +271,15 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask return bubbleMessage; } + + @Nullable + static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { + Objects.requireNonNull(context); + if (icon == null) return null; + if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + context.grantUriPermission(context.getPackageName(), + icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return icon.loadDrawable(context); + } } 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 cb8995a72dc3..8e232520a196 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -203,12 +203,22 @@ public class ExpandedAnimationController public void updateResources(int orientation, Point displaySize) { mScreenOrientation = orientation; mDisplaySize = displaySize; - if (mLayout != null) { - Resources res = mLayout.getContext().getResources(); - mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); - mStatusBarHeight = res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); + if (mLayout == null) { + return; } + Resources res = mLayout.getContext().getResources(); + mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); + mStatusBarHeight = res.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); + mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); + + // Includes overflow button. + float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) + - (mBubblesMaxRendered + 1) * mBubbleSizePx; + mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered; } /** @@ -464,18 +474,7 @@ public class ExpandedAnimationController @Override void onActiveControllerForLayout(PhysicsAnimationLayout layout) { - final Resources res = layout.getResources(); - mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); - mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); - mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); - mStatusBarHeight = - res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); - - // Includes overflow button. - float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) - - (mBubblesMaxRendered + 1) * mBubbleSizePx; - mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered; + updateResources(mScreenOrientation, mDisplaySize); // Ensure that all child views are at 1x scale, and visible, in case they were animating // in. diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt index 355c4b115c8d..24768cd84a76 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt @@ -24,5 +24,6 @@ data class BubbleEntity( val shortcutId: String, val key: String, val desiredHeight: Int, - @DimenRes val desiredHeightResId: Int + @DimenRes val desiredHeightResId: Int, + val title: String? = null ) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt index a8faf258da07..66fff3386ae1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt @@ -33,6 +33,7 @@ private const val ATTR_SHORTCUT_ID = "sid" private const val ATTR_KEY = "key" private const val ATTR_DESIRED_HEIGHT = "h" private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid" +private const val ATTR_TITLE = "t" /** * Writes the bubbles in xml format into given output stream. @@ -63,6 +64,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { serializer.attribute(null, ATTR_KEY, bubble.key) serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString()) serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString()) + bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) } serializer.endTag(null, TAG_BUBBLE) } catch (e: IOException) { throw RuntimeException(e) @@ -92,7 +94,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null, parser.getAttributeWithName(ATTR_KEY) ?: return null, parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null, - parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null + parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null, + parser.getAttributeWithName(ATTR_TITLE) ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt index 9b9a6b4b13ab..bccc3abd8a27 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt @@ -13,6 +13,7 @@ import androidx.core.view.GestureDetectorCompat import com.android.systemui.R import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.VisualStabilityManager +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.requiresRemeasuring import javax.inject.Inject @@ -31,7 +32,8 @@ class MediaViewManager @Inject constructor( private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityManager: VisualStabilityManager, private val mediaHostStatesManager: MediaHostStatesManager, - mediaManager: MediaDataCombineLatest + mediaManager: MediaDataCombineLatest, + configurationController: ConfigurationController ) { /** @@ -74,6 +76,7 @@ class MediaViewManager @Inject constructor( private val mediaCarousel: HorizontalScrollView val mediaFrame: ViewGroup val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf() + private val mediaData: MutableMap<String, MediaData> = mutableMapOf() private val mediaContent: ViewGroup private val pageIndicator: PageIndicator private val gestureDetector: GestureDetectorCompat @@ -120,6 +123,11 @@ class MediaViewManager @Inject constructor( return this@MediaViewManager.onTouch(view, motionEvent) } } + private val configListener = object : ConfigurationController.ConfigurationListener { + override fun onDensityOrFontScaleChanged() { + recreatePlayers() + } + } init { gestureDetector = GestureDetectorCompat(context, gestureListener) @@ -130,6 +138,7 @@ class MediaViewManager @Inject constructor( mediaCarousel.setOnTouchListener(touchListener) mediaCarousel.setOverScrollMode(View.OVER_SCROLL_NEVER) mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) + configurationController.addCallback(configListener) visualStabilityCallback = VisualStabilityManager.Callback { if (needsReordering) { needsReordering = false @@ -142,29 +151,14 @@ class MediaViewManager @Inject constructor( true /* persistent */) mediaManager.addListener(object : MediaDataManager.Listener { override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - updateView(key, oldKey, data) - updatePlayerVisibilities() - mediaCarousel.requiresRemeasuring = true + oldKey?.let { mediaData.remove(it) } + mediaData.put(key, data) + addOrUpdatePlayer(key, oldKey, data) } override fun onMediaDataRemoved(key: String) { - val removed = mediaPlayers.remove(key) - removed?.apply { - val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= - activeMediaIndex - mediaContent.removeView(removed.view?.player) - removed.onDestroy() - updateMediaPaddings() - if (beforeActive) { - // also update the index here since the scroll below might not always lead - // to a scrolling changed - activeMediaIndex = Math.max(0, activeMediaIndex - 1) - mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - - playerWidthPlusPadding, 0) - } - updatePlayerVisibilities() - updatePageIndicator() - } + mediaData.remove(key) + removePlayer(key) } }) mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback { @@ -253,7 +247,7 @@ class MediaViewManager @Inject constructor( } } - private fun updateView(key: String, oldKey: String?, data: MediaData) { + private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) { // If the key was changed, update entry val oldData = mediaPlayers[oldKey] if (oldData != null) { @@ -288,6 +282,39 @@ class MediaViewManager @Inject constructor( existingPlayer?.bind(data) updateMediaPaddings() updatePageIndicator() + updatePlayerVisibilities() + mediaCarousel.requiresRemeasuring = true + } + + private fun removePlayer(key: String) { + val removed = mediaPlayers.remove(key) + removed?.apply { + val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= + activeMediaIndex + mediaContent.removeView(removed.view?.player) + removed.onDestroy() + updateMediaPaddings() + if (beforeActive) { + // also update the index here since the scroll below might not always lead + // to a scrolling changed + activeMediaIndex = Math.max(0, activeMediaIndex - 1) + mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - + playerWidthPlusPadding, 0) + } + updatePlayerVisibilities() + updatePageIndicator() + } + } + + private fun recreatePlayers() { + // Note that this will scramble the order of players. Actively playing sessions will, at + // least, still be put in the front. If we want to maintain order, then more work is + // needed. + mediaData.forEach { + key, data -> + removePlayer(key) + addOrUpdatePlayer(key = key, oldKey = null, data = data) + } } private fun updateMediaPaddings() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index b93e07e65c73..9daa876038d5 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -708,6 +708,12 @@ public class PipTaskOrganizer extends TaskOrganizer implements Log.w(TAG, "Abort animation, invalid leash"); return; } + + if (startBounds.isEmpty() || destinationBounds.isEmpty()) { + Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting."); + return; + } + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds); tx.apply(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 856c19290af6..06c98d00cca7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; import android.util.Log; @@ -38,6 +39,9 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; import java.util.function.Consumer; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; + /** * A helper to animate and manipulate the PiP. */ @@ -74,9 +78,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, new SfVsyncFrameCallbackProvider(); /** - * Bounds that are animated using the physics animator. + * Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP + * using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into + * and expanding out of the magnetic dismiss target. + * + * Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary + * bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to + * its new bounds. */ - private final Rect mAnimatedBounds = new Rect(); + private final Rect mTemporaryBounds = new Rect(); /** The destination bounds to which PIP is animating. */ private final Rect mAnimatingToBounds = new Rect(); @@ -85,20 +95,20 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private FloatingContentCoordinator mFloatingContentCoordinator; /** Callback that re-sizes PIP to the animated bounds. */ - private final Choreographer.FrameCallback mResizePipVsyncCallback = - l -> resizePipUnchecked(mAnimatedBounds); + private final Choreographer.FrameCallback mResizePipVsyncCallback; /** - * PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations. + * PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations. */ - private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance( - mAnimatedBounds); + private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( + mTemporaryBounds); + + private MagnetizedObject<Rect> mMagnetizedPip; /** - * Update listener that resizes the PIP to {@link #mAnimatedBounds}. + * Update listener that resizes the PIP to {@link #mTemporaryBounds}. */ - final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener = - (target, values) -> mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); + private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener; /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ private PhysicsAnimator.FlingConfig mFlingConfigX; @@ -124,6 +134,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private boolean mSpringingToTouch = false; /** + * Whether PIP was released in the dismiss target, and will be animated out and dismissed + * shortly. + */ + private boolean mDismissalPending = false; + + /** * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is * used to show menu activity when the expand animation is completed. */ @@ -155,6 +171,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback); + + mResizePipVsyncCallback = l -> { + if (!mTemporaryBounds.isEmpty()) { + mPipTaskOrganizer.scheduleUserResizePip( + mBounds, mTemporaryBounds, null); + } + }; + + mResizePipUpdateListener = (target, values) -> + mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); } @NonNull @@ -186,19 +212,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } } - /** - * Synchronizes the current bounds with either the pinned stack, or the ongoing animation. This - * is done to prepare for a touch gesture. - */ - void synchronizePinnedStackBoundsForTouchGesture() { - if (mAnimatingToBounds.isEmpty()) { - // If we're not animating anywhere, sync normally. - synchronizePinnedStackBounds(); - } else { - // If we're animating, set the current bounds to the animated bounds. That way, the - // touch gesture will begin at the most recent animated location of the bounds. - mBounds.set(mAnimatedBounds); - } + boolean isAnimating() { + return mTemporaryBoundsPhysicsAnimator.isRunning(); } /** @@ -224,32 +239,54 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // If we are moving PIP directly to the touch event locations, cancel any animations and // move PIP to the given bounds. cancelAnimations(); - resizePipUnchecked(toBounds); - mBounds.set(toBounds); + + if (!isDragging) { + resizePipUnchecked(toBounds); + mBounds.set(toBounds); + } else { + mTemporaryBounds.set(toBounds); + mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds, null); + } } else { // If PIP is 'catching up' after being stuck in the dismiss target, update the animation // to spring towards the new touch location. - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) - .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig) - .withEndActions(() -> mSpringingToTouch = false); + .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig); startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */, false /* dismiss */); } } - /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ - void setSpringingToTouch(boolean springingToTouch) { - if (springingToTouch) { - mAnimatedBounds.set(mBounds); - } + /** Animates the PIP into the dismiss target, scaling it down. */ + void animateIntoDismissTarget( + MagnetizedObject.MagneticTarget target, + float velX, float velY, + boolean flung, Function0<Unit> after) { + final PointF targetCenter = target.getCenterOnScreen(); - mSpringingToTouch = springingToTouch; + final float desiredWidth = mBounds.width() / 2; + final float desiredHeight = mBounds.height() / 2; + + final float destinationX = targetCenter.x - (desiredWidth / 2f); + final float destinationY = targetCenter.y - (desiredHeight / 2f); + + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig) + .spring(FloatProperties.RECT_Y, destinationY, velY, mSpringConfig) + .spring(FloatProperties.RECT_WIDTH, desiredWidth, mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mSpringConfig) + .withEndActions(after); + + startBoundsAnimator(destinationX, destinationY, false); } - void prepareForAnimation() { - mAnimatedBounds.set(mBounds); + /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ + void setSpringingToTouch(boolean springingToTouch) { + mSpringingToTouch = springingToTouch; } /** @@ -309,13 +346,22 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** + * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds + * otherwise. + */ + Rect getPossiblyAnimatingBounds() { + return mTemporaryBounds.isEmpty() ? mBounds : mTemporaryBounds; + } + + /** * Flings the PiP to the closest snap target. */ void flingToSnapTarget( float velocityX, float velocityY, @Nullable Runnable updateAction, @Nullable Runnable endAction) { - mAnimatedBounds.set(mBounds); - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) .flingThenSpring( FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig, true /* flingMustReachMinOrMax */) @@ -324,13 +370,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, .withEndActions(endAction); if (updateAction != null) { - mAnimatedBoundsPhysicsAnimator.addUpdateListener( + mTemporaryBoundsPhysicsAnimator.addUpdateListener( (target, values) -> updateAction.run()); } final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right; final float estimatedFlingYEndValue = - PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY); + PhysicsAnimator.estimateFlingEndValue( + mTemporaryBounds.top, velocityY, mFlingConfigY); startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */, false /* dismiss */); @@ -341,8 +388,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * configuration */ void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) { - mAnimatedBounds.set(mBounds); - mAnimatedBoundsPhysicsAnimator + if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { + // Animate from the current bounds if we're not already animating. + mTemporaryBounds.set(mBounds); + } + + mTemporaryBoundsPhysicsAnimator .spring(FloatProperties.RECT_X, bounds.left, springConfig) .spring(FloatProperties.RECT_Y, bounds.top, springConfig); startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */, @@ -353,18 +404,19 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Animates the dismissal of the PiP off the edge of the screen. */ void animateDismiss() { - mAnimatedBounds.set(mBounds); - // Animate off the bottom of the screen, then dismiss PIP. - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator .spring(FloatProperties.RECT_Y, - mBounds.bottom + mBounds.height(), + mMovementBounds.bottom + mBounds.height() * 2, 0, mSpringConfig) .withEndActions(this::dismissPip); - startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */, + startBoundsAnimator( + mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */, true /* dismiss */); + + mDismissalPending = false; } /** @@ -415,7 +467,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Cancels all existing animations. */ private void cancelAnimations() { - mAnimatedBoundsPhysicsAnimator.cancel(); + mTemporaryBoundsPhysicsAnimator.cancel(); mAnimatingToBounds.setEmpty(); mSpringingToTouch = false; } @@ -449,15 +501,36 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (int) toY + mBounds.height()); setAnimatingToBounds(mAnimatingToBounds); - mAnimatedBoundsPhysicsAnimator - .withEndActions(() -> { - if (!dismiss) { - mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds); - } - mAnimatingToBounds.setEmpty(); - }) - .addUpdateListener(mResizePipUpdateListener) - .start(); + if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { + mTemporaryBoundsPhysicsAnimator + .addUpdateListener(mResizePipUpdateListener) + .withEndActions(this::onBoundsAnimationEnd); + } + + mTemporaryBoundsPhysicsAnimator.start(); + } + + /** + * Notify that PIP was released in the dismiss target and will be animated out and dismissed + * shortly. + */ + void notifyDismissalPending() { + mDismissalPending = true; + } + + private void onBoundsAnimationEnd() { + if (!mDismissalPending + && !mSpringingToTouch + && !mMagnetizedPip.getObjectStuckToTarget()) { + mBounds.set(mTemporaryBounds); + mPipTaskOrganizer.scheduleFinishResizePip(mBounds); + + mTemporaryBounds.setEmpty(); + } + + mAnimatingToBounds.setEmpty(); + mSpringingToTouch = false; + mDismissalPending = false; } /** @@ -503,25 +576,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * magnetic dismiss target so it can calculate PIP's size and position. */ MagnetizedObject<Rect> getMagnetizedPip() { - return new MagnetizedObject<Rect>( - mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { - @Override - public float getWidth(@NonNull Rect animatedPipBounds) { - return animatedPipBounds.width(); - } - - @Override - public float getHeight(@NonNull Rect animatedPipBounds) { - return animatedPipBounds.height(); - } + if (mMagnetizedPip == null) { + mMagnetizedPip = new MagnetizedObject<Rect>( + mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { + @Override + public float getWidth(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.width(); + } + + @Override + public float getHeight(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.height(); + } + + @Override + public void getLocationOnScreen( + @NonNull Rect animatedPipBounds, @NonNull int[] loc) { + loc[0] = animatedPipBounds.left; + loc[1] = animatedPipBounds.top; + } + }; + } - @Override - public void getLocationOnScreen( - @NonNull Rect animatedPipBounds, @NonNull int[] loc) { - loc[0] = animatedPipBounds.left; - loc[1] = animatedPipBounds.top; - } - }; + return mMagnetizedPip; } public void dump(PrintWriter pw, String prefix) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 3cc9127068bf..2f9b29d13744 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -70,6 +70,8 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; +import kotlin.Unit; + /** * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding * the PIP. @@ -262,12 +264,14 @@ public class PipTouchHandler { mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); updateMagneticTargetSize(); - mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener); + mMagnetizedPip.setAnimateStuckToTarget( + (target, velX, velY, flung, after) -> { + mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after); + return Unit.INSTANCE; + }); mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { @Override public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mMotionHelper.prepareForAnimation(); - // Show the dismiss target, in case the initial touch event occurred within the // magnetic field radius. showDismissTargetMaybe(); @@ -286,12 +290,13 @@ public class PipTouchHandler { @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mMotionHelper.notifyDismissalPending(); + mHandler.post(() -> { mMotionHelper.animateDismiss(); hideDismissTarget(); }); - MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, PipUtils.getTopPipActivity(mContext, mActivityManager)); } @@ -617,11 +622,16 @@ public class PipTouchHandler { } MotionEvent ev = (MotionEvent) inputEvent; - - if (mPipResizeGestureHandler.isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) { + if (!mTouchState.isDragging() + && !mMagnetizedPip.getObjectStuckToTarget() + && !mMotionHelper.isAnimating() + && mPipResizeGestureHandler.isWithinTouchRegion( + (int) ev.getRawX(), (int) ev.getRawY())) { return true; } - if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) { + + if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting()) + && mMagnetizedPip.maybeConsumeMotionEvent(ev)) { // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event // to the touch state. Touch state needs a DOWN event in order to later process MOVE // events it'll receive if the object is dragged out of the magnetic field. @@ -643,7 +653,6 @@ public class PipTouchHandler { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { - mMotionHelper.synchronizePinnedStackBoundsForTouchGesture(); mGesture.onDown(mTouchState); break; } @@ -688,11 +697,11 @@ public class PipTouchHandler { break; } case MotionEvent.ACTION_HOVER_EXIT: { - mHideMenuAfterShown = true; // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably // on and changing MotionEvents into HoverEvents. // Let's not enable menu show/hide for a11y services. if (!mAccessibilityManager.isTouchExplorationEnabled()) { + mHideMenuAfterShown = true; mMenuController.hideMenu(); } if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) { @@ -872,7 +881,7 @@ public class PipTouchHandler { return; } - Rect bounds = mMotionHelper.getBounds(); + Rect bounds = mMotionHelper.getPossiblyAnimatingBounds(); mDelta.set(0f, 0f); mStartPosition.set(bounds.left, bounds.top); mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom; @@ -914,7 +923,7 @@ public class PipTouchHandler { mDelta.x += left - lastX; mDelta.y += top - lastY; - mTmpBounds.set(mMotionHelper.getBounds()); + mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds()); mTmpBounds.offsetTo((int) left, (int) top); mMotionHelper.movePip(mTmpBounds, true /* isDragging */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java index 5ee4693a32bf..e0532c3e2b28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -17,9 +17,11 @@ package com.android.systemui.statusbar.notification; import android.content.res.Resources; +import android.text.Layout; import android.util.Pools; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import com.android.internal.widget.IMessagingLayout; import com.android.internal.widget.MessagingGroup; @@ -229,6 +231,15 @@ public class MessagingLayoutTransformState extends TransformState { return result; } + private boolean hasEllipses(TextView textView) { + Layout layout = textView.getLayout(); + return layout != null && layout.getEllipsisCount(layout.getLineCount() - 1) > 0; + } + + private boolean needsReflow(TextView own, TextView other) { + return hasEllipses(own) != hasEllipses(other); + } + /** * Transform two groups towards each other. * @@ -238,13 +249,20 @@ public class MessagingLayoutTransformState extends TransformState { float transformationAmount, boolean to) { boolean useLinearTransformation = otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating(); - transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(), - true /* sameAsAny */, useLinearTransformation); + TextView ownSenderView = ownGroup.getSenderView(); + TextView otherSenderView = otherGroup.getSenderView(); + transformView(transformationAmount, to, ownSenderView, otherSenderView, + // Normally this would be handled by the TextViewMessageState#sameAs check, but in + // this case it doesn't work because our text won't match, due to the appended colon + // in the collapsed view. + !needsReflow(ownSenderView, otherSenderView), + useLinearTransformation); int totalAvatarTranslation = transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(), true /* sameAsAny */, useLinearTransformation); List<MessagingMessage> ownMessages = ownGroup.getMessages(); List<MessagingMessage> otherMessages = otherGroup.getMessages(); float previousTranslation = 0; + boolean isLastView = true; for (int i = 0; i < ownMessages.size(); i++) { View child = ownMessages.get(ownMessages.size() - 1 - i).getView(); if (isGone(child)) { @@ -278,6 +296,9 @@ public class MessagingLayoutTransformState extends TransformState { mMessagingLayout.setMessagingClippingDisabled(true); } if (otherChild == null) { + if (isLastView) { + previousTranslation = ownSenderView.getTranslationY(); + } child.setTranslationY(previousTranslation); setClippingDeactivated(child, true); } else if (ownGroup.getIsolatedMessage() == child || otherIsIsolated) { @@ -287,6 +308,7 @@ public class MessagingLayoutTransformState extends TransformState { } else { previousTranslation = child.getTranslationY(); } + isLastView = false; } ownGroup.updateClipRect(); return totalAvatarTranslation; @@ -382,6 +404,9 @@ public class MessagingLayoutTransformState extends TransformState { if (view.getParent() == null) { return true; } + if (view.getWidth() == 0) { + return true; + } final ViewGroup.LayoutParams lp = view.getLayoutParams(); if (lp instanceof MessagingLinearLayout.LayoutParams && ((MessagingLinearLayout.LayoutParams) lp).hide) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java index 83d398d3e7ae..0d6597f1b11b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java @@ -39,7 +39,7 @@ import javax.inject.Singleton; public class LockscreenGestureLogger { /** - * Contains Lockscreen related Westworld UiEvent enums. + * Contains Lockscreen related statsd UiEvent enums. */ public enum LockscreenUiEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "Lockscreen > Pull shade open") diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt index ecd3afd687b3..a284a747da21 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt @@ -67,6 +67,40 @@ class FloatProperties { } /** + * Represents the width of a [Rect]. Typically used to animate resizing a Rect horizontally. + * + * This property's getter returns [Rect.width], and its setter changes the value of + * [Rect.right] by adding the animated width value to [Rect.left]. + */ + @JvmField + val RECT_WIDTH = object : FloatPropertyCompat<Rect>("RectWidth") { + override fun getValue(rect: Rect): Float { + return rect.width().toFloat() + } + + override fun setValue(rect: Rect, value: Float) { + rect.right = rect.left + value.toInt() + } + } + + /** + * Represents the height of a [Rect]. Typically used to animate resizing a Rect vertically. + * + * This property's getter returns [Rect.height], and its setter changes the value of + * [Rect.bottom] by adding the animated height value to [Rect.top]. + */ + @JvmField + val RECT_HEIGHT = object : FloatPropertyCompat<Rect>("RectHeight") { + override fun getValue(rect: Rect): Float { + return rect.height().toFloat() + } + + override fun setValue(rect: Rect, value: Float) { + rect.bottom = rect.top + value.toInt() + } + } + + /** * Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF * horizontally. * diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt index 5a2b064c5389..47b607fc6285 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt @@ -178,6 +178,18 @@ abstract class MagnetizedObject<T : Any>( var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null /** + * Method that is called when the object should be animated stuck to the target. The default + * implementation uses the object's x and y properties to animate the object centered inside the + * target. You can override this if you need custom animation. + * + * The method is invoked with the MagneticTarget that the object is sticking to, the X and Y + * velocities of the gesture that brought the object into the magnetic radius, whether or not it + * was flung, and a callback you must call after your animation completes. + */ + var animateStuckToTarget: (MagneticTarget, Float, Float, Boolean, (() -> Unit)?) -> Unit = + ::animateStuckToTargetInternal + + /** * Sets whether forcefully flinging the object vertically towards a target causes it to be * attracted to the target and then released immediately, despite never being dragged within the * magnetic field. @@ -373,7 +385,7 @@ abstract class MagnetizedObject<T : Any>( targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf cancelAnimations() magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!) - animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false) + animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null) vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) } else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) { @@ -430,8 +442,8 @@ abstract class MagnetizedObject<T : Any>( targetObjectIsStuckTo = flungToTarget animateStuckToTarget(flungToTarget, velX, velY, true) { - targetObjectIsStuckTo = null magnetListener.onReleasedInTarget(flungToTarget) + targetObjectIsStuckTo = null vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) } @@ -465,7 +477,7 @@ abstract class MagnetizedObject<T : Any>( } /** Animates sticking the object to the provided target with the given start velocities. */ - private fun animateStuckToTarget( + private fun animateStuckToTargetInternal( target: MagneticTarget, velX: Float, velY: Float, @@ -581,10 +593,10 @@ abstract class MagnetizedObject<T : Any>( * multiple objects. */ class MagneticTarget( - internal val targetView: View, + val targetView: View, var magneticFieldRadiusPx: Int ) { - internal val centerOnScreen = PointF() + val centerOnScreen = PointF() private val tempLoc = IntArray(2) diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index 6131e3b504af..369552fc814d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -340,7 +340,7 @@ public class Events { } /** - * Logs an event to the event log and UiEvent (Westworld) logging. Compare writeEvent, which + * Logs an event to the event log and UiEvent (statsd) logging. Compare writeEvent, which * adds more log destinations. * @param tag One of the EVENT_* codes above. * @param list Any additional event-specific arguments, documented above. diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 59f8c4e329a4..36398a6fc122 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -304,6 +304,10 @@ public class BubbleControllerTest extends SysuiTestCase { public void testPromoteBubble_autoExpand() throws Exception { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); mBubbleController.removeBubble( mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); @@ -331,6 +335,10 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */ false, /* showInShade */ true); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); mBubbleController.removeBubble( mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); @@ -433,15 +441,16 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mSysUiStateBubblesExpanded); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Switch which bubble is expanded - mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey( + mRow.getEntry().getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -543,27 +552,27 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -839,6 +848,12 @@ public class BubbleControllerTest extends SysuiTestCase { mRow2.getEntry(), /* suppressFlyout */ false, /* showInShade */ false); mBubbleController.updateBubble( mRow3.getEntry(), /* suppressFlyout */ false, /* showInShade */ false); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow3.getEntry().getKey())) + .thenReturn(mRow3.getEntry()); assertEquals(mBubbleData.getBubbles().size(), 3); mBubbleData.setMaxOverflowBubbles(1); @@ -908,6 +923,8 @@ public class BubbleControllerTest extends SysuiTestCase { // GIVEN a group summary with a bubble child ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -927,6 +944,8 @@ public class BubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -948,6 +967,8 @@ public class BubbleControllerTest extends SysuiTestCase { // GIVEN a group summary with two (non-bubble) children and one bubble child ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java index 72f816ff56b5..be03923e7264 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java @@ -86,8 +86,7 @@ public class BubbleTest extends SysuiTestCase { final String msg = "Hello there!"; doReturn(Notification.Style.class).when(mNotif).getNotificationStyle(); mExtras.putCharSequence(Notification.EXTRA_TEXT, msg); - assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -98,8 +97,7 @@ public class BubbleTest extends SysuiTestCase { mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg); // Should be big text, not the small text. - assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -107,8 +105,7 @@ public class BubbleTest extends SysuiTestCase { doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle(); // Media notifs don't get update messages. - assertNull(BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertNull(BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -124,7 +121,7 @@ public class BubbleTest extends SysuiTestCase { // Should be the last one only. assertEquals("Really? I prefer them that way.", - BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message); + BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -139,11 +136,8 @@ public class BubbleTest extends SysuiTestCase { "Oh, hello!", 0, "Mady").toBundle()}); // Should be the last one only. - assertEquals("Oh, hello!", - BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message); - assertEquals("Mady", - BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).senderName); + assertEquals("Oh, hello!", BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); + assertEquals("Mady", BubbleViewInfoTask.extractFlyoutMessage(mEntry).senderName); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 58e06b5178c6..1c70db3a548e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -302,6 +302,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { public void testRemoveBubble_withDismissedNotif_notInOverflow() { mEntryListener.onEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); @@ -388,14 +390,14 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { true, mRow.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry())); // Switch which bubble is expanded mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -488,27 +490,27 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble( mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one mBubbleController.removeBubble( mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -767,6 +769,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -785,6 +789,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -807,6 +813,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); // WHEN the summary is dismissed diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt index 1d02b8dba910..9b8fd11febe3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -32,7 +32,7 @@ class BubblePersistentRepositoryTest : SysuiTestCase() { private val bubbles = listOf( BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428), + BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title"), BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) ) private lateinit var repository: BubblePersistentRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt index f9d611c2bb33..76c58339726c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -37,9 +37,10 @@ class BubbleVolatileRepositoryTest : SysuiTestCase() { private val user0 = UserHandle.of(0) private val user10 = UserHandle.of(10) - private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1", 120, 0) - private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2", 0, 16537428) - private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3", 120, 0) + private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0) + private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", + "key-2", 0, 16537428, "title") + private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) private val bubbles = listOf(bubble1, bubble2, bubble3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt index 49467874dd8b..81687c7fbe1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt @@ -31,17 +31,17 @@ import java.io.ByteArrayOutputStream class BubbleXmlHelperTest : SysuiTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), + BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title"), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) ) @Test fun testWriteXml() { val expectedEntries = """ - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -54,12 +54,12 @@ class BubbleXmlHelperTest : SysuiTestCase() { @Test fun testReadXml() { val src = """ - <?xml version='1.0' encoding='utf-8' standalone='yes' ?> - <bs> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> - </bs> +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<bs> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +</bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual) diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 4e0970f1e40b..11f901538868 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -167,14 +167,16 @@ final class RemoteAugmentedAutofillService new IFillCallback.Stub() { @Override public void onSuccess(@Nullable List<Dataset> inlineSuggestionsData, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, boolean showingFillWindow) { mCallbacks.resetLastResponse(); maybeRequestShowInlineSuggestions(sessionId, inlineSuggestionsRequest, inlineSuggestionsData, clientState, focusedId, focusedValue, inlineSuggestionsCallback, client, onErrorCallback, remoteRenderService); - requestAutofill.complete(null); + if (!showingFillWindow) { + requestAutofill.complete(null); + } } @Override diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 158ed8ca6906..712b413c6e1a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -3279,16 +3279,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState }; // When the inline suggestion render service is available and the view is focused, there - // are 2 cases when augmented autofill should ask IME for inline suggestion request, + // are 3 cases when augmented autofill should ask IME for inline suggestion request, // because standard autofill flow didn't: // 1. the field is augmented autofill only (when standard autofill provider is None or // when it returns null response) // 2. standard autofill provider doesn't support inline suggestion + // 3. we re-entered the autofill session and standard autofill was not re-triggered, this is + // recognized by seeing mExpiredResponse == true final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); if (remoteRenderService != null && (mForAugmentedAutofillOnly - || !isInlineSuggestionsEnabledByAutofillProviderLocked()) + || !isInlineSuggestionsEnabledByAutofillProviderLocked() + || mExpiredResponse) && isViewFocusedLocked(flags)) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback( diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 2958fd2ae63a..36ba610085e1 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1698,6 +1698,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return newNc; } + // Allow VPNs to see ownership of their own VPN networks - not location sensitive. + if (nc.hasTransport(TRANSPORT_VPN)) { + // Owner UIDs already checked above. No need to re-check. + return newNc; + } + Binder.withCleanCallingIdentity( () -> { if (!mLocationPermissionChecker.checkLocationPermission( diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index e654af706fca..1f85d1046523 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1106,7 +1106,8 @@ public class Vpn { NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig(); networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown; - mNetworkCapabilities.setOwnerUid(Binder.getCallingUid()); + mNetworkCapabilities.setOwnerUid(mOwnerUID); + mNetworkCapabilities.setAdministratorUids(new int[] {mOwnerUID}); mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle, mConfig.allowedApplications, mConfig.disallowedApplications)); long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 769956d797b0..e3eeb6c41d9f 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -47,6 +47,10 @@ import android.service.dreams.IDreamManager; import android.util.Slog; import android.view.Display; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -77,6 +81,8 @@ public final class DreamManagerService extends SystemService { private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; private final ActivityTaskManagerInternal mAtmInternal; + private final UiEventLogger mUiEventLogger; + private final ComponentName mAmbientDisplayComponent; private Binder mCurrentDreamToken; private ComponentName mCurrentDreamName; @@ -91,6 +97,26 @@ public final class DreamManagerService extends SystemService { private AmbientDisplayConfiguration mDozeConfig; + @VisibleForTesting + public enum DreamManagerEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The screensaver has started.") + DREAM_START(577), + + @UiEvent(doc = "The screensaver has stopped.") + DREAM_STOP(578); + + private final int mId; + + DreamManagerEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + public DreamManagerService(Context context) { super(context); mContext = context; @@ -102,6 +128,9 @@ public final class DreamManagerService extends SystemService { mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); + mUiEventLogger = new UiEventLoggerImpl(); + AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext); + mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent()); } @Override @@ -388,6 +417,9 @@ public final class DreamManagerService extends SystemService { .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream"); mHandler.post(wakeLock.wrap(() -> { mAtmInternal.notifyDreamStateChanged(true); + if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { + mUiEventLogger.log(DreamManagerEvent.DREAM_START); + } mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock); })); } @@ -415,6 +447,9 @@ public final class DreamManagerService extends SystemService { } private void cleanupDreamLocked() { + if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { + mUiEventLogger.log(DreamManagerEvent.DREAM_STOP); + } mCurrentDreamToken = null; mCurrentDreamName = null; mCurrentDreamIsTest = false; diff --git a/services/core/java/com/android/server/notification/NotificationChannelLogger.java b/services/core/java/com/android/server/notification/NotificationChannelLogger.java index a7b18778f868..5c127c31d6c2 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelLogger.java +++ b/services/core/java/com/android/server/notification/NotificationChannelLogger.java @@ -99,6 +99,16 @@ public interface NotificationChannelLogger { } /** + * Log blocking or unblocking of the entire app's notifications. + * @param uid UID of the app. + * @param pkg Package name of the app. + * @param enabled If true, notifications are now allowed. + */ + default void logAppNotificationsAllowed(int uid, String pkg, boolean enabled) { + logAppEvent(NotificationChannelEvent.getBlocked(enabled), uid, pkg); + } + + /** * Low-level interface for logging events, to be implemented. * @param event Event to log. * @param channel Notification channel. @@ -124,6 +134,13 @@ public interface NotificationChannelLogger { boolean wasBlocked); /** + * Low-level interface for logging app-as-a-whole events, to be implemented. + * @param uid UID of app. + * @param pkg Package of app. + */ + void logAppEvent(@NonNull NotificationChannelEvent event, int uid, String pkg); + + /** * The UiEvent enums that this class can log. */ enum NotificationChannelEvent implements UiEventLogger.UiEventEnum { @@ -144,8 +161,11 @@ public interface NotificationChannelLogger { @UiEvent(doc = "System created a new conversation (sub-channel in a notification channel)") NOTIFICATION_CHANNEL_CONVERSATION_CREATED(272), @UiEvent(doc = "System deleted a new conversation (sub-channel in a notification channel)") - NOTIFICATION_CHANNEL_CONVERSATION_DELETED(274); - + NOTIFICATION_CHANNEL_CONVERSATION_DELETED(274), + @UiEvent(doc = "All notifications for the app were blocked.") + APP_NOTIFICATIONS_BLOCKED(557), + @UiEvent(doc = "Notifications for the app as a whole were unblocked.") + APP_NOTIFICATIONS_UNBLOCKED(558); private final int mId; NotificationChannelEvent(int id) { @@ -178,6 +198,10 @@ public interface NotificationChannelLogger { ? NotificationChannelEvent.NOTIFICATION_CHANNEL_GROUP_CREATED : NotificationChannelEvent.NOTIFICATION_CHANNEL_GROUP_DELETED; } + + public static NotificationChannelEvent getBlocked(boolean enabled) { + return enabled ? APP_NOTIFICATIONS_UNBLOCKED : APP_NOTIFICATIONS_BLOCKED; + } } /** diff --git a/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java index 2f7772eec2d2..fd3dd568f634 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java @@ -19,6 +19,8 @@ package com.android.server.notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.util.FrameworkStatsLog; /** @@ -27,6 +29,8 @@ import com.android.internal.util.FrameworkStatsLog; * should live in the interface so it can be tested. */ public class NotificationChannelLoggerImpl implements NotificationChannelLogger { + UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + @Override public void logNotificationChannel(NotificationChannelEvent event, NotificationChannel channel, int uid, String pkg, @@ -51,4 +55,9 @@ public class NotificationChannelLoggerImpl implements NotificationChannelLogger /* int old_importance*/ NotificationChannelLogger.getImportance(wasBlocked), /* int importance*/ NotificationChannelLogger.getImportance(channelGroup)); } + + @Override + public void logAppEvent(NotificationChannelEvent event, int uid, String pkg) { + mUiEventLogger.log(event, uid, pkg); + } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index e472e3097777..afc75572ae4f 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1654,6 +1654,7 @@ public class PreferencesHelper implements RankingConfig { } setImportance(packageName, uid, enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE); + mNotificationChannelLogger.logAppNotificationsAllowed(uid, packageName, enabled); } /** diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d55883968b56..c24c1e466133 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -32,6 +32,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Build.VERSION_CODES.N; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.util.DisplayMetrics.DENSITY_DEFAULT; @@ -1526,12 +1527,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } /** - * Sets the provided record to {@link mFixedRotationLaunchingApp} if possible to apply fixed + * Sets the provided record to {@link #mFixedRotationLaunchingApp} if possible to apply fixed * rotation transform to it and indicate that the display may be rotated after it is launched. */ void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Surface.Rotation int rotation) { final WindowToken prevRotatedLaunchingApp = mFixedRotationLaunchingApp; - if (prevRotatedLaunchingApp != null && prevRotatedLaunchingApp == r + if (prevRotatedLaunchingApp == r && r.getWindowConfiguration().getRotation() == rotation) { // The given launching app and target rotation are the same as the existing ones. return; @@ -5659,6 +5660,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + /** + * Return {@code true} if there is an ongoing animation to the "Recents" activity and this + * activity as a fixed orientation so shouldn't be rotated. + */ + boolean isFixedOrientationRecentsAnimating() { + return mAnimatingRecents != null + && mAnimatingRecents.getRequestedConfigurationOrientation() + != ORIENTATION_UNDEFINED; + } + @Override public void onAppTransitionFinishedLocked(IBinder token) { final ActivityRecord r = getActivityRecord(token); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 831491dd145e..f093fd34bcc0 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -430,6 +430,15 @@ public class DisplayRotation { "Deferring rotation, still finishing previous rotation"); return false; } + + if (mDisplayContent.mFixedRotationTransitionListener + .isFixedOrientationRecentsAnimating()) { + // During the recents animation, the closing app might still be considered on top. + // In order to ignore its requested orientation to avoid a sensor led rotation (e.g + // user rotating the device while the recents animation is running), we ignore + // rotation update while the animation is running. + return false; + } } if (!mService.mDisplayEnabled) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index c749125ec531..6670dbfea282 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2927,9 +2927,17 @@ class Task extends WindowContainer<WindowContainer> { // Don't crop HOME/RECENTS windows to stack bounds. This is because in split-screen // they extend past their stack and sysui uses the stack surface to control cropping. // TODO(b/158242495): get rid of this when drag/drop can use surface bounds. - final boolean isTopHomeOrRecents = (isActivityTypeHome() || isActivityTypeRecents()) - && getRootTask().getTopMostTask() == this; - return isResizeable() && !isTopHomeOrRecents; + if (isActivityTypeHome() || isActivityTypeRecents()) { + // Make sure this is the top-most non-organizer root task (if not top-most, it means + // another translucent task could be above this, so this needs to stay cropped. + final Task rootTask = getRootTask(); + final Task topNonOrgTask = + rootTask.mCreatedByOrganizer ? rootTask.getTopMostTask() : rootTask; + if (isDescendantOf(topNonOrgTask)) { + return false; + } + } + return isResizeable(); } /** diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 1a2672bd0132..51cf858715b4 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -28,6 +28,7 @@ import android.app.ActivityManager.TaskSnapshot; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.RecordingCanvas; @@ -37,8 +38,11 @@ import android.os.Environment; import android.os.Handler; import android.util.ArraySet; import android.util.Slog; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.ThreadedRenderer; +import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; @@ -475,9 +479,12 @@ class TaskSnapshotController { final int color = ColorUtils.setAlphaComponent( task.getTaskDescription().getBackgroundColor(), 255); final LayoutParams attrs = mainWindow.getAttrs(); + final InsetsPolicy insetsPolicy = mainWindow.getDisplayContent().getInsetsPolicy(); + final InsetsState insetsState = insetsPolicy.getInsetsForDispatch(mainWindow); + final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrameLw(), insetsState); final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags, attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(), - mHighResTaskSnapshotScale, mainWindow.getRequestedInsetsState()); + mHighResTaskSnapshotScale, insetsState); final int taskWidth = task.getBounds().width(); final int taskHeight = task.getBounds().height(); final int width = (int) (taskWidth * mHighResTaskSnapshotScale); @@ -488,7 +495,7 @@ class TaskSnapshotController { node.setClipToBounds(false); final RecordingCanvas c = node.start(width, height); c.drawColor(color); - decorPainter.setInsets(mainWindow.getContentInsets(), mainWindow.getStableInsets()); + decorPainter.setInsets(systemBarInsets); decorPainter.drawDecors(c, null /* statusBarExcludeFrame */); node.end(c); final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); @@ -593,6 +600,13 @@ class TaskSnapshotController { return 0; } + static Rect getSystemBarInsets(Rect frame, InsetsState state) { + return state.calculateInsets(frame, null /* ignoringVisibilityState */, + false /* isScreenRound */, false /* alwaysConsumeSystemBars */, + null /* displayCutout */, 0 /* legacySoftInputMode */, 0 /* legacySystemUiFlags */, + null /* typeSideMap */).getInsets(WindowInsets.Type.systemBars()).toRect(); + } + void dump(PrintWriter pw, String prefix) { pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale); mCache.dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index e26f1e1fe06f..f1f576220a9a 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -39,10 +39,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; -import static com.android.internal.policy.DecorView.getColorViewLeftInset; -import static com.android.internal.policy.DecorView.getColorViewTopInset; import static com.android.internal.policy.DecorView.getNavigationBarRect; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; +import static com.android.server.wm.TaskSnapshotController.getSystemBarInsets; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -131,9 +130,8 @@ class TaskSnapshotSurface implements StartingSurface { private final IWindowSession mSession; private final WindowManagerService mService; private final Rect mTaskBounds; - private final Rect mStableInsets = new Rect(); - private final Rect mContentInsets = new Rect(); private final Rect mFrame = new Rect(); + private final Rect mSystemBarInsets = new Rect(); private TaskSnapshot mSnapshot; private final RectF mTmpSnapshotSize = new RectF(); private final RectF mTmpDstFrame = new RectF(); @@ -174,6 +172,7 @@ class TaskSnapshotSurface implements StartingSurface { final int windowFlags; final int windowPrivateFlags; final int currentOrientation; + final InsetsState insetsState; synchronized (service.mGlobalLock) { final WindowState mainWindow = activity.findMainWindow(); final Task task = activity.getTask(); @@ -241,6 +240,10 @@ class TaskSnapshotSurface implements StartingSurface { taskBounds = new Rect(); task.getBounds(taskBounds); currentOrientation = topFullscreenOpaqueWindow.getConfiguration().orientation; + + final InsetsPolicy insetsPolicy = topFullscreenOpaqueWindow.getDisplayContent() + .getInsetsPolicy(); + insetsState = insetsPolicy.getInsetsForDispatch(topFullscreenOpaqueWindow); } try { final int res = session.addToDisplay(window, window.mSeq, layoutParams, @@ -255,8 +258,7 @@ class TaskSnapshotSurface implements StartingSurface { } final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window, surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis, - windowFlags, windowPrivateFlags, taskBounds, - currentOrientation, topFullscreenOpaqueWindow.getRequestedInsetsState()); + windowFlags, windowPrivateFlags, taskBounds, currentOrientation, insetsState); window.setOuter(snapshotSurface); try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, @@ -266,7 +268,9 @@ class TaskSnapshotSurface implements StartingSurface { } catch (RemoteException e) { // Local call. } - snapshotSurface.setFrames(tmpFrame, tmpContentInsets, tmpStableInsets); + + final Rect systemBarInsets = getSystemBarInsets(tmpFrame, insetsState); + snapshotSurface.setFrames(tmpFrame, systemBarInsets); snapshotSurface.drawSnapshot(); return snapshotSurface; } @@ -315,13 +319,12 @@ class TaskSnapshotSurface implements StartingSurface { } @VisibleForTesting - void setFrames(Rect frame, Rect contentInsets, Rect stableInsets) { + void setFrames(Rect frame, Rect systemBarInsets) { mFrame.set(frame); - mContentInsets.set(contentInsets); - mStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets); mSizeMismatch = (mFrame.width() != mSnapshot.getSnapshot().getWidth() || mFrame.height() != mSnapshot.getSnapshot().getHeight()); - mSystemBarBackgroundPainter.setInsets(contentInsets, stableInsets); + mSystemBarBackgroundPainter.setInsets(systemBarInsets); } private void drawSnapshot() { @@ -453,9 +456,7 @@ class TaskSnapshotSurface implements StartingSurface { ); // However, we also need to make space for the navigation bar on the left side. - final int colorViewLeftInset = getColorViewLeftInset(mStableInsets.left, - mContentInsets.left); - frame.offset(colorViewLeftInset, 0); + frame.offset(mSystemBarInsets.left, 0); return frame; } @@ -540,8 +541,6 @@ class TaskSnapshotSurface implements StartingSurface { */ static class SystemBarBackgroundPainter { - private final Rect mContentInsets = new Rect(); - private final Rect mStableInsets = new Rect(); private final Paint mStatusBarPaint = new Paint(); private final Paint mNavigationBarPaint = new Paint(); private final int mStatusBarColor; @@ -551,6 +550,7 @@ class TaskSnapshotSurface implements StartingSurface { private final int mSysUiVis; private final float mScale; private final InsetsState mInsetsState; + private final Rect mSystemBarInsets = new Rect(); SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int sysUiVis, TaskDescription taskDescription, float scale, InsetsState insetsState) { @@ -576,9 +576,8 @@ class TaskSnapshotSurface implements StartingSurface { mInsetsState = insetsState; } - void setInsets(Rect contentInsets, Rect stableInsets) { - mContentInsets.set(contentInsets); - mStableInsets.set(stableInsets); + void setInsets(Rect systemBarInsets) { + mSystemBarInsets.set(systemBarInsets); } int getStatusBarColorViewHeight() { @@ -589,7 +588,7 @@ class TaskSnapshotSurface implements StartingSurface { mSysUiVis, mStatusBarColor, mWindowFlags, forceBarBackground) : STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) { - return (int) (getColorViewTopInset(mStableInsets.top, mContentInsets.top) * mScale); + return (int) (mSystemBarInsets.top * mScale); } else { return 0; } @@ -615,8 +614,7 @@ class TaskSnapshotSurface implements StartingSurface { int statusBarHeight) { if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { - final int rightInset = (int) (DecorView.getColorViewRightInset(mStableInsets.right, - mContentInsets.right) * mScale); + final int rightInset = (int) (mSystemBarInsets.right * mScale); final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint); } @@ -625,8 +623,8 @@ class TaskSnapshotSurface implements StartingSurface { @VisibleForTesting void drawNavigationBarBackground(Canvas c) { final Rect navigationBarRect = new Rect(); - getNavigationBarRect(c.getWidth(), c.getHeight(), mStableInsets, mContentInsets, - navigationBarRect, mScale); + getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, + mScale); final boolean visible = isNavigationBarColorViewVisible(); if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { c.drawRect(navigationBarRect, mNavigationBarPaint); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 6406f0ae51a6..dd08f4208eca 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1032,7 +1032,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @return Whether this child is on top of the window hierarchy. */ boolean isOnTop() { - return getParent().getTopChild() == this && getParent().isOnTop(); + final WindowContainer parent = getParent(); + return parent != null && parent.getTopChild() == this && parent.isOnTop(); } /** Returns the top child container. */ diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 8fb98624899e..5fc519c86f11 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2339,6 +2339,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } + if (inPinnedWindowingMode()) { + return false; + } + final boolean windowsAreFocusable = mActivityRecord == null || mActivityRecord.windowsAreFocusable(); if (!windowsAreFocusable) { // This window can't be an IME target if the app's windows should not be focusable. @@ -3412,6 +3416,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void setTouchableRegionCropIfNeeded(InputWindowHandle handle) { final Task task = getTask(); if (task == null || !task.cropWindowsToStackBounds()) { + handle.setTouchableRegionCrop(null); return; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java index b6ea063ccc14..f609306e44b0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java @@ -51,4 +51,9 @@ public class NotificationChannelLoggerFake implements NotificationChannelLogger NotificationChannelGroup channelGroup, int uid, String pkg, boolean wasBlocked) { mCalls.add(new CallRecord(event)); } + + @Override + public void logAppEvent(NotificationChannelEvent event, int uid, String pkg) { + mCalls.add(new CallRecord(event)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 622a203c5242..2e49929ec032 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -2266,6 +2266,14 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testAppBlockedLogging() { + mHelper.setEnabled(PKG_N_MR1, 1020, false); + assertEquals(1, mLogger.getCalls().size()); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.APP_NOTIFICATIONS_BLOCKED, + mLogger.get(0).event); + } + @Test public void testXml_statusBarIcons_default() throws Exception { String preQXml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 5a952b3e238f..8cf850736cb2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -79,6 +80,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; import android.annotation.SuppressLint; import android.app.ActivityTaskManager; @@ -1231,11 +1233,29 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testRecentsNotRotatingWithFixedRotation() { + final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); + doCallRealMethod().when(displayRotation).updateRotationUnchecked(anyBoolean()); + doCallRealMethod().when(displayRotation).updateOrientation(anyInt(), anyBoolean()); + + final ActivityRecord recentsActivity = createActivityRecord(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS); + recentsActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + + mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity); + displayRotation.setRotation((displayRotation.getRotation() + 1) % 4); + assertFalse(displayRotation.updateRotationUnchecked(false)); + + mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(false); + assertTrue(displayRotation.updateRotationUnchecked(false)); + } + + @Test public void testRemoteRotation() { DisplayContent dc = createNewDisplay(); final DisplayRotation dr = dc.getDisplayRotation(); - Mockito.doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean()); + doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean()); Mockito.doReturn(ROTATION_90).when(dr).rotationForOrientation(anyInt(), anyInt()); final boolean[] continued = new boolean[1]; // TODO(display-merge): Remove cast diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index 4907bdc5e1f0..d6ec78837f7d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -190,7 +190,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 0, 10); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); assertEquals(new Rect(0, 0, 100, 80), mSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); } @@ -199,7 +199,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame_navBarLeft() { setupSurface(100, 100); final Rect insets = new Rect(10, 10, 0, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); assertEquals(new Rect(10, 0, 100, 90), mSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); } @@ -208,7 +208,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame_waterfall() { setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100)); final Rect insets = new Rect(0, 10, 0, 10); - mSurface.setFrames(new Rect(5, 0, 95, 100), insets, insets); + mSurface.setFrames(new Rect(5, 0, 95, 100), insets); assertEquals(new Rect(0, 0, 90, 90), mSurface.calculateSnapshotFrame(new Rect(5, 0, 95, 90))); } @@ -217,7 +217,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -230,7 +230,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground_nullFrame() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -243,7 +243,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground_nope() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -257,7 +257,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(0, 10, 0, 10); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -270,7 +270,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(10, 10, 0, 0); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -283,7 +283,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(0, 10, 10, 0); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 0d26e4417ed9..4a0f48cf2ccb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -244,6 +245,12 @@ public class WindowStateTests extends WindowTestsBase { appWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE; assertTrue(appWindow.canBeImeTarget()); + // Verify PINNED windows can't be IME target. + int initialMode = appWindow.mActivityRecord.getWindowingMode(); + appWindow.mActivityRecord.setWindowingMode(WINDOWING_MODE_PINNED); + assertFalse(appWindow.canBeImeTarget()); + appWindow.mActivityRecord.setWindowingMode(initialMode); + // Make windows invisible appWindow.hideLw(false /* doAnimation */); imeWindow.hideLw(false /* doAnimation */); diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 0fc9be32f4cf..6eba62e63740 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -16,6 +16,8 @@ package android.net; +import static android.net.RouteInfo.RTN_THROW; +import static android.net.RouteInfo.RTN_UNICAST; import static android.net.RouteInfo.RTN_UNREACHABLE; import static com.android.testutils.ParcelUtilsKt.assertParcelSane; @@ -1282,4 +1284,20 @@ public class LinkPropertiesTest { assertTrue(lp.hasIpv6UnreachableDefaultRoute()); assertFalse(lp.hasIpv4UnreachableDefaultRoute()); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testRouteAddWithSameKey() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan0"); + final IpPrefix v6 = new IpPrefix("64:ff9b::/96"); + lp.addRoute(new RouteInfo(v6, address("fe80::1"), "wlan0", RTN_UNICAST, 1280)); + assertEquals(1, lp.getRoutes().size()); + lp.addRoute(new RouteInfo(v6, address("fe80::1"), "wlan0", RTN_UNICAST, 1500)); + assertEquals(1, lp.getRoutes().size()); + final IpPrefix v4 = new IpPrefix("192.0.2.128/25"); + lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_UNICAST, 1460)); + assertEquals(2, lp.getRoutes().size()); + lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_THROW, 1460)); + assertEquals(2, lp.getRoutes().size()); + } } diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java index 8204b494bbb8..60cac0b6b0f5 100644 --- a/tests/net/common/java/android/net/RouteInfoTest.java +++ b/tests/net/common/java/android/net/RouteInfoTest.java @@ -25,6 +25,7 @@ import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -56,7 +57,7 @@ public class RouteInfoTest { private static final int INVALID_ROUTE_TYPE = -1; private InetAddress Address(String addr) { - return InetAddress.parseNumericAddress(addr); + return InetAddresses.parseNumericAddress(addr); } private IpPrefix Prefix(String prefix) { @@ -391,4 +392,43 @@ public class RouteInfoTest { r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0"); assertEquals(0, r.getMtu()); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testRouteKey() { + RouteInfo.RouteKey k1, k2; + // Only prefix, null gateway and null interface + k1 = new RouteInfo(Prefix("2001:db8::/128"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("2001:db8::/128"), null).getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // With prefix, gateway and interface. Type and MTU does not affect RouteKey equality + k1 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", + RTN_UNREACHABLE, 1450).getRouteKey(); + k2 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", + RouteInfo.RTN_UNICAST, 1400).getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // Different scope IDs are ignored by the kernel, so we consider them equal here too. + k1 = new RouteInfo(Prefix("2001:db8::/64"), Address("fe80::1%1"), "wlan0").getRouteKey(); + k2 = new RouteInfo(Prefix("2001:db8::/64"), Address("fe80::1%2"), "wlan0").getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // Different prefix + k1 = new RouteInfo(Prefix("192.0.2.0/24"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("192.0.3.0/24"), null).getRouteKey(); + assertNotEquals(k1, k2); + + // Different gateway + k1 = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::1"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::2"), null).getRouteKey(); + assertNotEquals(k1, k2); + + // Different interface + k1 = new RouteInfo(Prefix("ff02::1/128"), null, "tun0").getRouteKey(); + k2 = new RouteInfo(Prefix("ff02::1/128"), null, "tun1").getRouteKey(); + assertNotEquals(k1, k2); + } } |