diff options
99 files changed, 3772 insertions, 1029 deletions
diff --git a/apex/media/OWNERS b/apex/media/OWNERS index bed38954a70c..2c5965c300e3 100644 --- a/apex/media/OWNERS +++ b/apex/media/OWNERS @@ -1,6 +1,5 @@ # Bug component: 1344 hdmoon@google.com -hkuang@google.com jinpark@google.com klhyun@google.com lnilsson@google.com diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java index e48f234c5569..7d47e250f99d 100644 --- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java +++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java @@ -79,7 +79,7 @@ public class MediaCommunicationService extends SystemService { final Executor mRecordExecutor = Executors.newSingleThreadExecutor(); @GuardedBy("mLock") - final List<CallbackRecord> mCallbackRecords = new ArrayList<>(); + final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<>(); final NotificationManager mNotificationManager; MediaSessionManager mSessionManager; @@ -150,8 +150,8 @@ public class MediaCommunicationService extends SystemService { return null; } - List<Session2Token> getSession2TokensLocked(int userId) { - List<Session2Token> list = new ArrayList<>(); + ArrayList<Session2Token> getSession2TokensLocked(int userId) { + ArrayList<Session2Token> list = new ArrayList<>(); if (userId == ALL.getIdentifier()) { int size = mUserRecords.size(); for (int i = 0; i < size; i++) { @@ -237,28 +237,29 @@ public class MediaCommunicationService extends SystemService { } void dispatchSession2Changed(int userId) { - MediaParceledListSlice<Session2Token> allSession2Tokens; - MediaParceledListSlice<Session2Token> userSession2Tokens; + ArrayList<Session2Token> allSession2Tokens; + ArrayList<Session2Token> userSession2Tokens; synchronized (mLock) { - allSession2Tokens = - new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier())); - userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId)); - } - allSession2Tokens.setInlineCountLimit(1); - userSession2Tokens.setInlineCountLimit(1); + allSession2Tokens = getSession2TokensLocked(ALL.getIdentifier()); + userSession2Tokens = getSession2TokensLocked(userId); - synchronized (mLock) { for (CallbackRecord record : mCallbackRecords) { if (record.mUserId == ALL.getIdentifier()) { try { - record.mCallback.onSession2Changed(allSession2Tokens); + MediaParceledListSlice<Session2Token> toSend = + new MediaParceledListSlice<>(allSession2Tokens); + toSend.setInlineCountLimit(0); + record.mCallback.onSession2Changed(toSend); } catch (RemoteException e) { Log.w(TAG, "Failed to notify session2 tokens changed " + record); } } else if (record.mUserId == userId) { try { - record.mCallback.onSession2Changed(userSession2Tokens); + MediaParceledListSlice<Session2Token> toSend = + new MediaParceledListSlice<>(userSession2Tokens); + toSend.setInlineCountLimit(0); + record.mCallback.onSession2Changed(toSend); } catch (RemoteException e) { Log.w(TAG, "Failed to notify session2 tokens changed " + record); } @@ -382,7 +383,7 @@ public class MediaCommunicationService extends SystemService { try { // Check that they can make calls on behalf of the user and get the final user id int resolvedUserId = handleIncomingUser(pid, uid, userId, null); - List<Session2Token> result; + ArrayList<Session2Token> result; synchronized (mLock) { result = getSession2TokensLocked(resolvedUserId); } diff --git a/core/api/current.txt b/core/api/current.txt index 407855b107cd..ab51690df88a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -27362,11 +27362,13 @@ package android.net { method @NonNull public android.net.VpnService.Builder addDnsServer(@NonNull java.net.InetAddress); method @NonNull public android.net.VpnService.Builder addDnsServer(@NonNull String); method @NonNull public android.net.VpnService.Builder addRoute(@NonNull java.net.InetAddress, int); + method @NonNull public android.net.VpnService.Builder addRoute(@NonNull android.net.IpPrefix); method @NonNull public android.net.VpnService.Builder addRoute(@NonNull String, int); method @NonNull public android.net.VpnService.Builder addSearchDomain(@NonNull String); method @NonNull public android.net.VpnService.Builder allowBypass(); method @NonNull public android.net.VpnService.Builder allowFamily(int); method @Nullable public android.os.ParcelFileDescriptor establish(); + method @NonNull public android.net.VpnService.Builder excludeRoute(@NonNull android.net.IpPrefix); method @NonNull public android.net.VpnService.Builder setBlocking(boolean); method @NonNull public android.net.VpnService.Builder setConfigureIntent(@NonNull android.app.PendingIntent); method @NonNull public android.net.VpnService.Builder setHttpProxy(@NonNull android.net.ProxyInfo); @@ -27684,6 +27686,23 @@ package android.net.sip { package android.net.vcn { + public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate { + method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds(); + method public int getOpportunistic(); + method public int getRoaming(); + method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds(); + } + + public static final class VcnCellUnderlyingNetworkTemplate.Builder { + ctor public VcnCellUnderlyingNetworkTemplate.Builder(); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build(); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int); + method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>); + } + public final class VcnConfig implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs(); @@ -27702,6 +27721,7 @@ package android.net.vcn { method @NonNull public String getGatewayConnectionName(); method @IntRange(from=0x500) public int getMaxMtu(); method @NonNull public long[] getRetryIntervalsMillis(); + method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities(); } public static final class VcnGatewayConnectionConfig.Builder { @@ -27711,6 +27731,7 @@ package android.net.vcn { method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>); } public class VcnManager { @@ -27734,6 +27755,24 @@ package android.net.vcn { method public abstract void onStatusChanged(int); } + public abstract class VcnUnderlyingNetworkTemplate { + method public int getMetered(); + field public static final int MATCH_ANY = 0; // 0x0 + field public static final int MATCH_FORBIDDEN = 2; // 0x2 + field public static final int MATCH_REQUIRED = 1; // 0x1 + } + + public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate { + method @NonNull public java.util.Set<java.lang.String> getSsids(); + } + + public static final class VcnWifiUnderlyingNetworkTemplate.Builder { + ctor public VcnWifiUnderlyingNetworkTemplate.Builder(); + method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build(); + method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int); + method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>); + } + } package android.nfc { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 3a3813f29d89..ac56488b7abf 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1047,6 +1047,7 @@ package android.app.admin { field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL"; field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME"; field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE"; + field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT = "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT"; field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER"; field public static final String EXTRA_PROVISIONING_SUPPORTED_MODES = "android.app.extra.PROVISIONING_SUPPORTED_MODES"; field public static final String EXTRA_PROVISIONING_SUPPORT_URL = "android.app.extra.PROVISIONING_SUPPORT_URL"; @@ -2430,6 +2431,25 @@ package android.bluetooth { package android.bluetooth.le { + public final class AdvertiseSettings implements android.os.Parcelable { + method public int getOwnAddressType(); + } + + public static final class AdvertiseSettings.Builder { + method @NonNull public android.bluetooth.le.AdvertiseSettings.Builder setOwnAddressType(int); + } + + public final class AdvertisingSetParameters implements android.os.Parcelable { + method public int getOwnAddressType(); + field public static final int ADDRESS_TYPE_DEFAULT = -1; // 0xffffffff + field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0 + field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1 + } + + public static final class AdvertisingSetParameters.Builder { + method @NonNull public android.bluetooth.le.AdvertisingSetParameters.Builder setOwnAddressType(int); + } + public final class BluetoothLeScanner { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback); @@ -10799,7 +10819,7 @@ package android.service.games { public class GameService extends android.app.Service { ctor public GameService(); - method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); + method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); method public void onConnected(); method public void onDisconnected(); field public static final String ACTION_GAME_SERVICE = "android.service.games.action.GAME_SERVICE"; @@ -10814,7 +10834,7 @@ package android.service.games { public abstract class GameSessionService extends android.app.Service { ctor public GameSessionService(); - method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); + method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); method @NonNull public abstract android.service.games.GameSession onNewSession(@NonNull android.service.games.CreateGameSessionRequest); field public static final String ACTION_GAME_SESSION_SERVICE = "android.service.games.action.GAME_SESSION_SERVICE"; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 7969cda7b84d..73ebdf6a4545 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2885,6 +2885,38 @@ public class DevicePolicyManager { public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2; /** + * An {@link Intent} extra which resolves to a custom user consent screen. + * + * <p>If this extra is provided to the device management role holder via either {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE}, the device management role holder must + * launch this intent which shows the custom user consent screen, replacing its own standard + * consent screen. + * + * <p>If this extra is provided, it is the responsibility of the intent handler to show the + * list of disclaimers which are normally shown by the standard consent screen: + * <ul> + * <li>Disclaimers set by the IT admin via the {@link #EXTRA_PROVISIONING_DISCLAIMERS} + * provisioning extra</li> + * <li>For fully-managed device provisioning, disclaimers defined in system apps via the + * {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER} and {@link + * #EXTRA_PROVISIONING_DISCLAIMER_CONTENT} metadata in their manifests</li> + * <li>General disclaimer relevant to the provisioning mode</li> + * </ul> + * + * <p>If the custom consent screens are granted by the user {@link Activity#RESULT_OK} is + * returned, otherwise {@link Activity#RESULT_CANCELED} is returned. The device management + * role holder should ensure that the provisioning flow terminates immediately if consent + * is not granted by the user. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @SystemApi + public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT = + "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT"; + + /** * Maximum supported password length. Kind-of arbitrary. * @hide */ diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 65b2775fd66b..177de835554b 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -156,7 +156,7 @@ public class TrustManager { @Override public void onTrustError(CharSequence message) { - Message m = mHandler.obtainMessage(MSG_TRUST_ERROR); + Message m = mHandler.obtainMessage(MSG_TRUST_ERROR, trustListener); m.getData().putCharSequence(DATA_MESSAGE, message); m.sendToTarget(); } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 856a8e1f8199..7e5e96d802d9 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -3064,6 +3064,9 @@ public final class BluetoothAdapter { BluetoothCsipSetCoordinator csipSetCoordinator = new BluetoothCsipSetCoordinator(context, listener, this); return true; + } else if (profile == BluetoothProfile.LE_CALL_CONTROL) { + BluetoothLeCallControl tbs = new BluetoothLeCallControl(context, listener); + return true; } else { return false; } @@ -3166,6 +3169,10 @@ public final class BluetoothAdapter { (BluetoothCsipSetCoordinator) proxy; csipSetCoordinator.close(); break; + case BluetoothProfile.LE_CALL_CONTROL: + BluetoothLeCallControl tbs = (BluetoothLeCallControl) proxy; + tbs.close(); + break; } } diff --git a/core/java/android/bluetooth/BluetoothLeCall.java b/core/java/android/bluetooth/BluetoothLeCall.java new file mode 100644 index 000000000000..fb7789db25c7 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeCall.java @@ -0,0 +1,285 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelUuid; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.UUID; + +/** + * Representation of Call + * + * @hide + */ +public final class BluetoothLeCall implements Parcelable { + + /** @hide */ + @IntDef(prefix = "STATE_", value = { + STATE_INCOMING, + STATE_DIALING, + STATE_ALERTING, + STATE_ACTIVE, + STATE_LOCALLY_HELD, + STATE_REMOTELY_HELD, + STATE_LOCALLY_AND_REMOTELY_HELD + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State { + } + + /** + * A remote party is calling (incoming call). + * + * @hide + */ + public static final int STATE_INCOMING = 0x00; + + /** + * The process to call the remote party has started but the remote party is not + * being alerted (outgoing call). + * + * @hide + */ + public static final int STATE_DIALING = 0x01; + + /** + * A remote party is being alerted (outgoing call). + * + * @hide + */ + public static final int STATE_ALERTING = 0x02; + + /** + * The call is in an active conversation. + * + * @hide + */ + public static final int STATE_ACTIVE = 0x03; + + /** + * The call is connected but held locally. “Locally Held” implies that either + * the server or the client can affect the state. + * + * @hide + */ + public static final int STATE_LOCALLY_HELD = 0x04; + + /** + * The call is connected but held remotely. “Remotely Held” means that the state + * is controlled by the remote party of a call. + * + * @hide + */ + public static final int STATE_REMOTELY_HELD = 0x05; + + /** + * The call is connected but held both locally and remotely. + * + * @hide + */ + public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06; + + /** + * Whether the call direction is outgoing. + * + * @hide + */ + public static final int FLAG_OUTGOING_CALL = 0x00000001; + + /** + * Whether the call URI and Friendly Name are withheld by server. + * + * @hide + */ + public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002; + + /** + * Whether the call URI and Friendly Name are withheld by network. + * + * @hide + */ + public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004; + + /** Unique UUID that identifies this call */ + private UUID mUuid; + + /** Remote Caller URI */ + private String mUri; + + /** Caller friendly name */ + private String mFriendlyName; + + /** Call state */ + private @State int mState; + + /** Call flags */ + private int mCallFlags; + + /** @hide */ + public BluetoothLeCall(@NonNull BluetoothLeCall that) { + mUuid = new UUID(that.getUuid().getMostSignificantBits(), + that.getUuid().getLeastSignificantBits()); + mUri = that.mUri; + mFriendlyName = that.mFriendlyName; + mState = that.mState; + mCallFlags = that.mCallFlags; + } + + /** @hide */ + public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName, + @State int state, int callFlags) { + mUuid = uuid; + mUri = uri; + mFriendlyName = friendlyName; + mState = state; + mCallFlags = callFlags; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + BluetoothLeCall that = (BluetoothLeCall) o; + return mUuid.equals(that.mUuid) && mUri.equals(that.mUri) + && mFriendlyName.equals(that.mFriendlyName) && mState == that.mState + && mCallFlags == that.mCallFlags; + } + + @Override + public int hashCode() { + return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags); + } + + /** + * Returns a string representation of this BluetoothLeCall. + * + * <p> + * Currently this is the UUID. + * + * @return string representation of this BluetoothLeCall + */ + @Override + public String toString() { + return mUuid.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeParcelable(new ParcelUuid(mUuid), 0); + out.writeString(mUri); + out.writeString(mFriendlyName); + out.writeInt(mState); + out.writeInt(mCallFlags); + } + + public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR = + new Parcelable.Creator<BluetoothLeCall>() { + public BluetoothLeCall createFromParcel(Parcel in) { + return new BluetoothLeCall(in); + } + + public BluetoothLeCall[] newArray(int size) { + return new BluetoothLeCall[size]; + } + }; + + private BluetoothLeCall(Parcel in) { + mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); + mUri = in.readString(); + mFriendlyName = in.readString(); + mState = in.readInt(); + mCallFlags = in.readInt(); + } + + /** + * Returns an UUID of this BluetoothLeCall. + * + * <p> + * An UUID is unique identifier of a BluetoothLeCall. + * + * @return UUID of this BluetoothLeCall + * @hide + */ + public @NonNull UUID getUuid() { + return mUuid; + } + + /** + * Returns a URI of the remote party of this BluetoothLeCall. + * + * @return string representation of this BluetoothLeCall + * @hide + */ + public @NonNull String getUri() { + return mUri; + } + + /** + * Returns a friendly name of the call. + * + * @return friendly name representation of this BluetoothLeCall + * @hide + */ + public @NonNull String getFriendlyName() { + return mFriendlyName; + } + + /** + * Returns the call state. + * + * @return the state of this BluetoothLeCall + * @hide + */ + public @State int getState() { + return mState; + } + + /** + * Returns the call flags. + * + * @return call flags + * @hide + */ + public int getCallFlags() { + return mCallFlags; + } + + /** + * Whether the call direction is incoming. + * + * @return true if incoming call, false otherwise + * @hide + */ + public boolean isIncomingCall() { + return (mCallFlags & FLAG_OUTGOING_CALL) == 0; + } +} diff --git a/core/java/android/bluetooth/BluetoothLeCallControl.java b/core/java/android/bluetooth/BluetoothLeCallControl.java new file mode 100644 index 000000000000..fb080c9ec3e3 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeCallControl.java @@ -0,0 +1,899 @@ +/* + * Copyright 2019 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.content.ComponentName; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; +import android.annotation.SuppressLint; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executor; + +/** + * This class provides the APIs to control the Call Control profile. + * + * <p> + * This class provides Bluetooth Telephone Bearer Service functionality, + * allowing applications to expose a GATT Service based interface to control the + * state of the calls by remote devices such as LE audio devices. + * + * <p> + * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the + * BluetoothLeCallControl proxy object. + * + * @hide + */ +public final class BluetoothLeCallControl implements BluetoothProfile { + private static final String TAG = "BluetoothLeCallControl"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** @hide */ + @IntDef(prefix = "RESULT_", value = { + RESULT_SUCCESS, + RESULT_ERROR_UNKNOWN_CALL_ID, + RESULT_ERROR_INVALID_URI, + RESULT_ERROR_APPLICATION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Result { + } + + /** + * Opcode write was successful. + * + * @hide + */ + public static final int RESULT_SUCCESS = 0; + + /** + * Unknown call Id has been used in the operation. + * + * @hide + */ + public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1; + + /** + * The URI provided in {@link Callback#onPlaceCallRequest} is invalid. + * + * @hide + */ + public static final int RESULT_ERROR_INVALID_URI = 2; + + /** + * Application internal error. + * + * @hide + */ + public static final int RESULT_ERROR_APPLICATION = 3; + + /** @hide */ + @IntDef(prefix = "TERMINATION_REASON_", value = { + TERMINATION_REASON_INVALID_URI, + TERMINATION_REASON_FAIL, + TERMINATION_REASON_REMOTE_HANGUP, + TERMINATION_REASON_SERVER_HANGUP, + TERMINATION_REASON_LINE_BUSY, + TERMINATION_REASON_NETWORK_CONGESTION, + TERMINATION_REASON_CLIENT_HANGUP, + TERMINATION_REASON_NO_SERVICE, + TERMINATION_REASON_NO_ANSWER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TerminationReason { + } + + /** + * Remote Caller ID value used to place a call was formed improperly. + * + * @hide + */ + public static final int TERMINATION_REASON_INVALID_URI = 0x00; + + /** + * Call fail. + * + * @hide + */ + public static final int TERMINATION_REASON_FAIL = 0x01; + + /** + * Remote party ended call. + * + * @hide + */ + public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02; + + /** + * Call ended from the server. + * + * @hide + */ + public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03; + + /** + * Line busy. + * + * @hide + */ + public static final int TERMINATION_REASON_LINE_BUSY = 0x04; + + /** + * Network congestion. + * + * @hide + */ + public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05; + + /** + * Client terminated. + * + * @hide + */ + public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06; + + /** + * No service. + * + * @hide + */ + public static final int TERMINATION_REASON_NO_SERVICE = 0x07; + + /** + * No answer. + * + * @hide + */ + public static final int TERMINATION_REASON_NO_ANSWER = 0x08; + + /* + * Flag indicating support for hold/unhold call feature. + * + * @hide + */ + public static final int CAPABILITY_HOLD_CALL = 0x00000001; + + /** + * Flag indicating support for joining calls feature. + * + * @hide + */ + public static final int CAPABILITY_JOIN_CALLS = 0x00000002; + + private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102; + private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103; + + private static final int REG_TIMEOUT = 10000; + + /** + * The template class is used to call callback functions on events from the TBS + * server. Callback functions are wrapped in this class and registered to the + * Android system during app registration. + * + * @hide + */ + public abstract static class Callback { + + private static final String TAG = "BluetoothLeCallControl.Callback"; + + /** + * Called when a remote client requested to accept the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to be accepted + * @hide + */ + public abstract void onAcceptCall(int requestId, @NonNull UUID callId); + + /** + * A remote client has requested to terminate the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to terminate + * @hide + */ + public abstract void onTerminateCall(int requestId, @NonNull UUID callId); + + /** + * A remote client has requested to hold the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to be put on hold + * @hide + */ + public void onHoldCall(int requestId, @NonNull UUID callId) { + Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); + } + + /** + * A remote client has requested to unhold the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to unhold + * @hide + */ + public void onUnholdCall(int requestId, @NonNull UUID callId) { + Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); + } + + /** + * A remote client has requested to place a call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The Id to be assigned for the new call + * @param uri The caller URI requested + * @hide + */ + public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri); + + /** + * A remote client has requested to join the calls. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callIds The call Id list requested to join + * @hide + */ + public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { + Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!"); + } + } + + private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub { + + private final Executor mExecutor; + private final Callback mCallback; + + CallbackWrapper(Executor executor, Callback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onBearerRegistered(int ccid) { + if (mCallback != null) { + mCcid = ccid; + } else { + // registration timeout + Log.e(TAG, "onBearerRegistered: mCallback is null"); + } + } + + @Override + public void onAcceptCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onTerminateCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onHoldCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onUnholdCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri)); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) { + List<UUID> uuids = new ArrayList<>(); + for (ParcelUuid parcelUuid : parcelUuids) { + uuids.add(parcelUuid.getUuid()); + } + + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids)); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + }; + + private Context mContext; + private ServiceListener mServiceListener; + private volatile IBluetoothLeCallControl mService; + private BluetoothAdapter mAdapter; + private int mCcid = 0; + private String mToken; + private Callback mCallback = null; + + private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) + Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + doUnbind(); + } else { + doBind(); + } + } + }; + + /** + * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth + * telephone bearer service. + */ + /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) { + mContext = context; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + mServiceListener = listener; + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + doBind(); + } + + private boolean doBind() { + synchronized (mConnection) { + if (mService == null) { + if (VDBG) + Log.d(TAG, "Binding service..."); + try { + return mAdapter.getBluetoothManager(). + bindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, + mConnection); + } catch (RemoteException e) { + Log.e(TAG, "Unable to bind TelephoneBearerService", e); + } + } + } + return false; + } + + private void doUnbind() { + synchronized (mConnection) { + if (mService != null) { + if (VDBG) + Log.d(TAG, "Unbinding service..."); + try { + mAdapter.getBluetoothManager(). + unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, + mConnection); + } catch (RemoteException e) { + Log.e(TAG, "Unable to unbind TelephoneBearerService", e); + } finally { + mService = null; + } + } + } + } + + /* package */ void close() { + if (VDBG) + log("close()"); + unregisterBearer(); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException re) { + Log.e(TAG, "", re); + } + } + mServiceListener = null; + doUnbind(); + } + + private IBluetoothLeCallControl getService() { + return mService; + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public int getConnectionState(@Nullable BluetoothDevice device) { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public @NonNull List<BluetoothDevice> getConnectedDevices() { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( + @NonNull int[] states) { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Register Telephone Bearer exposing the interface that allows remote devices + * to track and control the call states. + * + * <p> + * This is an asynchronous call. The callback is used to notify success or + * failure if the function returns true. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * <!-- The UCI is a String identifier of the telephone bearer as defined at + * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers + * (login required). --> + * + * <!-- The examples of common URI schemes can be found in + * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> + * + * <!-- The Technology is an integer value. The possible values are defined at + * https://www.bluetooth.com/specifications/assigned-numbers (login required). + * --> + * + * @param uci Bearer Unique Client Identifier + * @param uriSchemes URI Schemes supported list + * @param capabilities bearer capabilities + * @param provider Network provider name + * @param technology Network technology + * @param executor {@link Executor} object on which callback will be + * executed. The Executor object is required. + * @param callback {@link Callback} object to which callback messages will + * be sent. The Callback object is required. + * @return true on success, false otherwise + * @hide + */ + @SuppressLint("ExecutorRegistration") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean registerBearer(@Nullable String uci, + @NonNull List<String> uriSchemes, int capabilities, + @NonNull String provider, int technology, + @NonNull Executor executor, @NonNull Callback callback) { + if (DBG) { + Log.d(TAG, "registerBearer"); + } + if (callback == null) { + throw new IllegalArgumentException("null parameter: " + callback); + } + if (mCcid != 0) { + return false; + } + + mToken = uci; + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + if (mCallback != null) { + Log.e(TAG, "Bearer can be opened only once"); + return false; + } + + mCallback = callback; + try { + CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); + service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities, + provider, technology); + } catch (RemoteException e) { + Log.e(TAG, "", e); + mCallback = null; + return false; + } + + if (mCcid == 0) { + mCallback = null; + return false; + } + + return true; + } + + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + + return false; + } + + /** + * Unregister Telephone Bearer Service and destroy all the associated data. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void unregisterBearer() { + if (DBG) { + Log.d(TAG, "unregisterBearer"); + } + if (mCcid == 0) { + return; + } + + int ccid = mCcid; + mCcid = 0; + mCallback = null; + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.unregisterBearer(mToken); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Get the Content Control ID (CCID) value. + * + * @return ccid Content Control ID value + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public int getContentControlId() { + return mCcid; + } + + /** + * Notify about the newly added call. + * + * <p> + * This shall be called as early as possible after the call has been added. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param call Newly added call + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallAdded(@NonNull BluetoothLeCall call) { + if (DBG) { + Log.d(TAG, "onCallAdded: call=" + call); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callAdded(mCcid, call); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Notify about the removed call. + * + * <p> + * This shall be called as early as possible after the call has been removed. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param callId The Id of a call that has been removed + * @param reason Call termination reason + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) { + if (DBG) { + Log.d(TAG, "callRemoved: callId=" + callId); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callRemoved(mCcid, new ParcelUuid(callId), reason); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Notify the call state change + * + * <p> + * This shall be called as early as possible after the state of the call has + * changed. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param callId The call Id that state has been changed + * @param state Call state + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) { + if (DBG) { + Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callStateChanged(mCcid, new ParcelUuid(callId), state); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Provide the current calls list + * + * <p> + * This function must be invoked after registration if application has any + * calls. + * + * @param calls current calls list + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.currentCallsList(mCcid, calls); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + } + + /** + * Provide the network current status + * + * <p> + * This function must be invoked on change of network state. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * <!-- The Technology is an integer value. The possible values are defined at + * https://www.bluetooth.com/specifications/assigned-numbers (login required). + * --> + * + * @param provider Network provider name + * @param technology Network technology + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void networkStateChanged(@NonNull String provider, int technology) { + if (DBG) { + Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.networkStateChanged(mCcid, provider, technology); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Send a response to a call control request to a remote device. + * + * <p> + * This function must be invoked in when a request is received by one of these + * callback methods: + * + * <ul> + * <li>{@link Callback#onAcceptCall} + * <li>{@link Callback#onTerminateCall} + * <li>{@link Callback#onHoldCall} + * <li>{@link Callback#onUnholdCall} + * <li>{@link Callback#onPlaceCall} + * <li>{@link Callback#onJoinCalls} + * </ul> + * + * @param requestId The ID of the request that was received with the callback + * @param result The result of the request to be sent to the remote devices + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void requestResult(int requestId, @Result int result) { + if (DBG) { + Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.requestResult(mCcid, requestId, result); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + } + + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + private static boolean isValidDevice(@Nullable BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); + } + + private static void log(String msg) { + Log.d(TAG, msg); + } + + private final IBluetoothProfileServiceConnection mConnection = + new IBluetoothProfileServiceConnection.Stub() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) { + Log.d(TAG, "Proxy object connected"); + } + mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service)); + mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED)); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + if (DBG) { + Log.d(TAG, "Proxy object disconnected"); + } + doUnbind(); + mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED)); + } + }; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_TBS_SERVICE_CONNECTED: { + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL, + BluetoothLeCallControl.this); + } + break; + } + case MESSAGE_TBS_SERVICE_DISCONNECTED: { + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL); + } + break; + } + } + } + }; +} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index e047e5d81a9d..d0f74e985729 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -240,12 +240,19 @@ public interface BluetoothProfile { int LE_AUDIO_BROADCAST = 26; /** + * @hide + * Telephone Bearer Service from Call Control Profile + * + */ + int LE_CALL_CONTROL = 27; + + /** * Max profile ID. This value should be updated whenever a new profile is added to match * the largest value assigned to a profile. * * @hide */ - int MAX_PROFILE_ID = 26; + int MAX_PROFILE_ID = 27; /** * Default priority for devices that we try to auto-connect to and diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java index 7129d762cd95..c52a6ee33989 100644 --- a/core/java/android/bluetooth/le/AdvertiseSettings.java +++ b/core/java/android/bluetooth/le/AdvertiseSettings.java @@ -16,6 +16,9 @@ package android.bluetooth.le; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.bluetooth.le.AdvertisingSetParameters.AddressTypeStatus; import android.os.Parcel; import android.os.Parcelable; @@ -70,17 +73,21 @@ public final class AdvertiseSettings implements Parcelable { */ private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000; + private final int mAdvertiseMode; private final int mAdvertiseTxPowerLevel; private final int mAdvertiseTimeoutMillis; private final boolean mAdvertiseConnectable; + private final int mOwnAddressType; private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel, - boolean advertiseConnectable, int advertiseTimeout) { + boolean advertiseConnectable, int advertiseTimeout, + @AddressTypeStatus int ownAddressType) { mAdvertiseMode = advertiseMode; mAdvertiseTxPowerLevel = advertiseTxPowerLevel; mAdvertiseConnectable = advertiseConnectable; mAdvertiseTimeoutMillis = advertiseTimeout; + mOwnAddressType = ownAddressType; } private AdvertiseSettings(Parcel in) { @@ -88,6 +95,7 @@ public final class AdvertiseSettings implements Parcelable { mAdvertiseTxPowerLevel = in.readInt(); mAdvertiseConnectable = in.readInt() != 0; mAdvertiseTimeoutMillis = in.readInt(); + mOwnAddressType = in.readInt(); } /** @@ -118,12 +126,23 @@ public final class AdvertiseSettings implements Parcelable { return mAdvertiseTimeoutMillis; } + /** + * @return the own address type for advertising + * + * @hide + */ + @SystemApi + public @AddressTypeStatus int getOwnAddressType() { + return mOwnAddressType; + } + @Override public String toString() { return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel + ", mAdvertiseConnectable=" + mAdvertiseConnectable - + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis + "]"; + + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis + + ", mOwnAddressType=" + mOwnAddressType + "]"; } @Override @@ -137,6 +156,7 @@ public final class AdvertiseSettings implements Parcelable { dest.writeInt(mAdvertiseTxPowerLevel); dest.writeInt(mAdvertiseConnectable ? 1 : 0); dest.writeInt(mAdvertiseTimeoutMillis); + dest.writeInt(mOwnAddressType); } public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseSettings> CREATOR = @@ -160,6 +180,7 @@ public final class AdvertiseSettings implements Parcelable { private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM; private int mTimeoutMillis = 0; private boolean mConnectable = true; + private int mOwnAddressType = AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT; /** * Set advertise mode to control the advertising power and latency. @@ -226,10 +247,31 @@ public final class AdvertiseSettings implements Parcelable { } /** + * Set own address type for advertising to control public or privacy mode. If used to set + * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT}, + * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the + * time of starting advertising. + * + * @throws IllegalArgumentException If the {@code ownAddressType} is invalid + * + * @hide + */ + @SystemApi + public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) { + if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT + || ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) { + throw new IllegalArgumentException("unknown address type " + ownAddressType); + } + mOwnAddressType = ownAddressType; + return this; + } + + /** * Build the {@link AdvertiseSettings} object. */ public AdvertiseSettings build() { - return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis); + return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis, + mOwnAddressType); } } } diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java index e39b198ae384..5c8fae65193d 100644 --- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java +++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java @@ -16,11 +16,17 @@ package android.bluetooth.le; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * The {@link AdvertisingSetParameters} provide a way to adjust advertising * preferences for each @@ -97,6 +103,39 @@ public final class AdvertisingSetParameters implements Parcelable { */ private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000; + /** @hide */ + @IntDef(prefix = "ADDRESS_TYPE_", value = { + ADDRESS_TYPE_DEFAULT, + ADDRESS_TYPE_PUBLIC, + ADDRESS_TYPE_RANDOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AddressTypeStatus {} + + /** + * Advertise own address type that corresponds privacy settings of the device. + * + * @hide + */ + @SystemApi + public static final int ADDRESS_TYPE_DEFAULT = -1; + + /** + * Advertise own public address type. + * + * @hide + */ + @SystemApi + public static final int ADDRESS_TYPE_PUBLIC = 0; + + /** + * Generate and adverise own resolvable private address. + * + * @hide + */ + @SystemApi + public static final int ADDRESS_TYPE_RANDOM = 1; + private final boolean mIsLegacy; private final boolean mIsAnonymous; private final boolean mIncludeTxPower; @@ -106,11 +145,12 @@ public final class AdvertisingSetParameters implements Parcelable { private final boolean mScannable; private final int mInterval; private final int mTxPowerLevel; + private final int mOwnAddressType; private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy, boolean isAnonymous, boolean includeTxPower, int primaryPhy, int secondaryPhy, - int interval, int txPowerLevel) { + int interval, int txPowerLevel, @AddressTypeStatus int ownAddressType) { mConnectable = connectable; mScannable = scannable; mIsLegacy = isLegacy; @@ -120,6 +160,7 @@ public final class AdvertisingSetParameters implements Parcelable { mSecondaryPhy = secondaryPhy; mInterval = interval; mTxPowerLevel = txPowerLevel; + mOwnAddressType = ownAddressType; } private AdvertisingSetParameters(Parcel in) { @@ -132,6 +173,7 @@ public final class AdvertisingSetParameters implements Parcelable { mSecondaryPhy = in.readInt(); mInterval = in.readInt(); mTxPowerLevel = in.readInt(); + mOwnAddressType = in.readInt(); } /** @@ -197,6 +239,16 @@ public final class AdvertisingSetParameters implements Parcelable { return mTxPowerLevel; } + /** + * @return the own address type for advertising + * + * @hide + */ + @SystemApi + public @AddressTypeStatus int getOwnAddressType() { + return mOwnAddressType; + } + @Override public String toString() { return "AdvertisingSetParameters [connectable=" + mConnectable @@ -206,7 +258,8 @@ public final class AdvertisingSetParameters implements Parcelable { + ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy + ", interval=" + mInterval - + ", txPowerLevel=" + mTxPowerLevel + "]"; + + ", txPowerLevel=" + mTxPowerLevel + + ", ownAddressType=" + mOwnAddressType + "]"; } @Override @@ -225,6 +278,7 @@ public final class AdvertisingSetParameters implements Parcelable { dest.writeInt(mSecondaryPhy); dest.writeInt(mInterval); dest.writeInt(mTxPowerLevel); + dest.writeInt(mOwnAddressType); } public static final @android.annotation.NonNull Parcelable.Creator<AdvertisingSetParameters> CREATOR = @@ -253,6 +307,7 @@ public final class AdvertisingSetParameters implements Parcelable { private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M; private int mInterval = INTERVAL_LOW; private int mTxPowerLevel = TX_POWER_MEDIUM; + private int mOwnAddressType = ADDRESS_TYPE_DEFAULT; /** * Set whether the advertisement type should be connectable or @@ -399,6 +454,26 @@ public final class AdvertisingSetParameters implements Parcelable { } /** + * Set own address type for advertising to control public or privacy mode. If used to set + * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT}, + * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the + * time of starting advertising. + * + * @throws IllegalArgumentException If the {@code ownAddressType} is invalid + * + * @hide + */ + @SystemApi + public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) { + if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT + || ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) { + throw new IllegalArgumentException("unknown address type " + ownAddressType); + } + mOwnAddressType = ownAddressType; + return this; + } + + /** * Build the {@link AdvertisingSetParameters} object. * * @throws IllegalStateException if invalid combination of parameters is used. @@ -431,7 +506,8 @@ public final class AdvertisingSetParameters implements Parcelable { } return new AdvertisingSetParameters(mConnectable, mScannable, mIsLegacy, mIsAnonymous, - mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel); + mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel, + mOwnAddressType); } } } diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java index b9f8a57a75ea..879dceedaaec 100644 --- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -138,6 +138,7 @@ public final class BluetoothLeAdvertiser { parameters.setLegacyMode(true); parameters.setConnectable(isConnectable); parameters.setScannable(true); // legacy advertisements we support are always scannable + parameters.setOwnAddressType(settings.getOwnAddressType()); if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) { parameters.setInterval(1600); // 1s } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) { diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 32827ae11e0b..b3435b1180c2 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -1182,7 +1182,7 @@ public class IntentFilter implements Parcelable { public int match(Uri data, boolean wildcardSupported) { String host = data.getHost(); if (host == null) { - if (wildcardSupported && mWild) { + if (wildcardSupported && mWild && mHost.isEmpty()) { // special case, if no host is provided, but the Authority is wildcard, match return MATCH_CATEGORY_HOST; } else { diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 77b8be351bd1..d04c97c1e915 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -37,6 +37,9 @@ "name": "CarrierAppIntegrationTestCases" }, { + "name": "ApkVerityTest" + }, + { "name": "CtsIncrementalInstallHostTestCases", "options": [ { @@ -135,6 +138,9 @@ "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert" } ] + }, + { + "name": "CtsInstallHostTestCases" } ] } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index afaa085c7cbd..93573d15bae0 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -523,6 +523,7 @@ public class InputMethodService extends AbstractInputMethodService { private Handler mHandler; private boolean mImeSurfaceScheduledForRemoval; private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker(); + private boolean mDestroyed; /** * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput} @@ -599,6 +600,11 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void initializeInternal(@NonNull IBinder token, IInputMethodPrivilegedOperations privilegedOperations, int configChanges) { + if (mDestroyed) { + Log.i(TAG, "The InputMethodService has already onDestroyed()." + + "Ignore the initialization."); + return; + } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal"); mConfigTracker.onInitialize(configChanges); mPrivOps.set(privilegedOperations); @@ -1435,6 +1441,7 @@ public class InputMethodService extends AbstractInputMethodService { } @Override public void onDestroy() { + mDestroyed = true; super.onDestroy(); mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); @@ -1811,15 +1818,19 @@ public class InputMethodService extends AbstractInputMethodService { void updateExtractFrameVisibility() { final int vis; + updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE); + if (isFullscreenMode()) { vis = mExtractViewHidden ? View.INVISIBLE : View.VISIBLE; // "vis" should be applied for the extract frame as well in the fullscreen mode. mExtractFrame.setVisibility(vis); } else { - vis = View.VISIBLE; + // mFullscreenArea visibility will according the candidate frame visibility once the + // extract frame is gone. + vis = mCandidatesVisibility; mExtractFrame.setVisibility(View.GONE); } - updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE); + if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) { int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java index 1b5ab051610a..55541377a0bf 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -916,9 +916,23 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { } /** - * Sets whether the local traffic is exempted from the VPN. + * Sets whether the local traffic is exempted from the VPN. * - * @hide TODO(184750836): unhide once the implementation is completed + * When this is set, the system will not use the VPN network when an app + * tries to send traffic for an IP address that is on a local network. + * + * Note that there are important security implications. In particular, the + * networks that the device connects to typically decides what IP addresses + * are part of the local network. This means that for VPNs setting this + * flag, it is possible for anybody to set up a public network in such a + * way that traffic to arbitrary IP addresses will bypass the VPN, including + * traffic to services like DNS. When using this API, please consider the + * security implications for your particular case. + * + * Note that because the local traffic will always bypass the VPN, + * it is not possible to set this flag on a non-bypassable VPN. + * + * @hide TODO(184750836): unhide once the implementation is completed */ @NonNull @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/core/java/android/net/InternalNetworkUpdateRequest.java index 6f093835fb08..f42c4b7c420d 100644 --- a/core/java/android/net/InternalNetworkUpdateRequest.java +++ b/core/java/android/net/InternalNetworkUpdateRequest.java @@ -17,7 +17,6 @@ package android.net; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -27,7 +26,7 @@ import java.util.Objects; public final class InternalNetworkUpdateRequest implements Parcelable { @NonNull private final StaticIpConfiguration mIpConfig; - @Nullable + @NonNull private final NetworkCapabilities mNetworkCapabilities; @NonNull @@ -37,20 +36,16 @@ public final class InternalNetworkUpdateRequest implements Parcelable { @NonNull public NetworkCapabilities getNetworkCapabilities() { - return mNetworkCapabilities == null - ? null : new NetworkCapabilities(mNetworkCapabilities); + return new NetworkCapabilities(mNetworkCapabilities); } /** @hide */ public InternalNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig, - @Nullable final NetworkCapabilities networkCapabilities) { + @NonNull final NetworkCapabilities networkCapabilities) { Objects.requireNonNull(ipConfig); + Objects.requireNonNull(networkCapabilities); mIpConfig = new StaticIpConfiguration(ipConfig); - if (null == networkCapabilities) { - mNetworkCapabilities = null; - } else { - mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); - } + mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); } private InternalNetworkUpdateRequest(@NonNull final Parcel source) { diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 2ced05693755..1ae1b050d32f 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -41,6 +41,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.NetworkUtilsInternal; import com.android.internal.net.VpnConfig; @@ -50,6 +51,7 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.Socket; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -471,6 +473,13 @@ public class VpnService extends Service { } } + private static void checkNonPrefixBytes(@NonNull InetAddress address, int prefixLength) { + final IpPrefix prefix = new IpPrefix(address, prefixLength); + if (!prefix.getAddress().equals(address)) { + throw new IllegalArgumentException("Bad address"); + } + } + /** * Helper class to create a VPN interface. This class should be always * used within the scope of the outer {@link VpnService}. @@ -481,9 +490,9 @@ public class VpnService extends Service { private final VpnConfig mConfig = new VpnConfig(); @UnsupportedAppUsage - private final List<LinkAddress> mAddresses = new ArrayList<LinkAddress>(); + private final List<LinkAddress> mAddresses = new ArrayList<>(); @UnsupportedAppUsage - private final List<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); + private final List<RouteInfo> mRoutes = new ArrayList<>(); public Builder() { mConfig.user = VpnService.this.getClass().getName(); @@ -555,7 +564,6 @@ public class VpnService extends Service { throw new IllegalArgumentException("Bad address"); } mAddresses.add(new LinkAddress(address, prefixLength)); - mConfig.updateAllowedFamilies(address); return this; } @@ -579,28 +587,68 @@ public class VpnService extends Service { * Add a network route to the VPN interface. Both IPv4 and IPv6 * routes are supported. * + * If a route with the same destination is already present, its type will be updated. + * + * @throws IllegalArgumentException if the route is invalid. + */ + @NonNull + private Builder addRoute(@NonNull IpPrefix prefix, int type) { + check(prefix.getAddress(), prefix.getPrefixLength()); + + final RouteInfo newRoute = new RouteInfo(prefix, /* gateway */ + null, /* interface */ null, type); + + final int index = findRouteIndexByDestination(newRoute); + + if (index == -1) { + mRoutes.add(newRoute); + } else { + mRoutes.set(index, newRoute); + } + + return this; + } + + /** + * Add a network route to the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * * Adding a route implicitly allows traffic from that address family * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily * + * Calling this method overrides previous calls to {@link #excludeRoute} for the same + * destination. + * + * If multiple routes match the packet destination, route with the longest prefix takes + * precedence. + * * @throws IllegalArgumentException if the route is invalid. */ @NonNull public Builder addRoute(@NonNull InetAddress address, int prefixLength) { - check(address, prefixLength); + checkNonPrefixBytes(address, prefixLength); - int offset = prefixLength / 8; - byte[] bytes = address.getAddress(); - if (offset < bytes.length) { - for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) { - if (bytes[offset] != 0) { - throw new IllegalArgumentException("Bad address"); - } - } - } - mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null, null, - RouteInfo.RTN_UNICAST)); - mConfig.updateAllowedFamilies(address); - return this; + return addRoute(new IpPrefix(address, prefixLength), RouteInfo.RTN_UNICAST); + } + + /** + * Add a network route to the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * Calling this method overrides previous calls to {@link #excludeRoute} for the same + * destination. + * + * If multiple routes match the packet destination, route with the longest prefix takes + * precedence. + * + * @throws IllegalArgumentException if the route is invalid. + */ + @NonNull + public Builder addRoute(@NonNull IpPrefix prefix) { + return addRoute(prefix, RouteInfo.RTN_UNICAST); } /** @@ -611,6 +659,12 @@ public class VpnService extends Service { * Adding a route implicitly allows traffic from that address family * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily * + * Calling this method overrides previous calls to {@link #excludeRoute} for the same + * destination. + * + * If multiple routes match the packet destination, route with the longest prefix takes + * precedence. + * * @throws IllegalArgumentException if the route is invalid. * @see #addRoute(InetAddress, int) */ @@ -620,6 +674,23 @@ public class VpnService extends Service { } /** + * Exclude a network route from the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * + * Calling this method overrides previous calls to {@link #addRoute} for the same + * destination. + * + * If multiple routes match the packet destination, route with the longest prefix takes + * precedence. + * + * @throws IllegalArgumentException if the route is invalid. + */ + @NonNull + public Builder excludeRoute(@NonNull IpPrefix prefix) { + return addRoute(prefix, RouteInfo.RTN_THROW); + } + + /** * Add a DNS server to the VPN connection. Both IPv4 and IPv6 * addresses are supported. If none is set, the DNS servers of * the default network will be used. @@ -900,5 +971,23 @@ public class VpnService extends Service { throw new IllegalStateException(e); } } + + private int findRouteIndexByDestination(RouteInfo route) { + for (int i = 0; i < mRoutes.size(); i++) { + if (mRoutes.get(i).getDestination().equals(route.getDestination())) { + return i; + } + } + return -1; + } + + /** + * Method for testing, to observe mRoutes while builder is being used. + * @hide + */ + @VisibleForTesting + public List<RouteInfo> routes() { + return Collections.unmodifiableList(mRoutes); + } } } diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java index 1ac3f0a6d7d1..125b5730b2ed 100644 --- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java @@ -15,6 +15,9 @@ */ package android.net.vcn; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString; + import static com.android.internal.annotations.VisibleForTesting.Visibility; import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER; import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER; @@ -23,8 +26,12 @@ import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZ import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.net.NetworkCapabilities; +import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria; import android.os.PersistableBundle; import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.ArraySet; @@ -37,32 +44,36 @@ import java.util.Collections; import java.util.Objects; import java.util.Set; -// TODO: Add documents -/** @hide */ +/** + * This class represents a configuration for a network template class of underlying cellular + * networks. + * + * <p>See {@link VcnUnderlyingNetworkTemplate} + */ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate { private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds"; @NonNull private final Set<String> mAllowedNetworkPlmnIds; private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds"; @NonNull private final Set<Integer> mAllowedSpecificCarrierIds; - private static final String ALLOW_ROAMING_KEY = "mAllowRoaming"; - private final boolean mAllowRoaming; + private static final String ROAMING_MATCH_KEY = "mRoamingMatchCriteria"; + private final int mRoamingMatchCriteria; - private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic"; - private final boolean mRequireOpportunistic; + private static final String OPPORTUNISTIC_MATCH_KEY = "mOpportunisticMatchCriteria"; + private final int mOpportunisticMatchCriteria; private VcnCellUnderlyingNetworkTemplate( int networkQuality, - boolean allowMetered, + int meteredMatchCriteria, Set<String> allowedNetworkPlmnIds, Set<Integer> allowedSpecificCarrierIds, - boolean allowRoaming, - boolean requireOpportunistic) { - super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, allowMetered); + int roamingMatchCriteria, + int opportunisticMatchCriteria) { + super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, meteredMatchCriteria); mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds); mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds); - mAllowRoaming = allowRoaming; - mRequireOpportunistic = requireOpportunistic; + mRoamingMatchCriteria = roamingMatchCriteria; + mOpportunisticMatchCriteria = opportunisticMatchCriteria; validate(); } @@ -72,15 +83,17 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork protected void validate() { super.validate(); validatePlmnIds(mAllowedNetworkPlmnIds); - Objects.requireNonNull(mAllowedSpecificCarrierIds, "allowedCarrierIds is null"); + Objects.requireNonNull(mAllowedSpecificCarrierIds, "matchingCarrierIds is null"); + validateMatchCriteria(mRoamingMatchCriteria, "mRoamingMatchCriteria"); + validateMatchCriteria(mOpportunisticMatchCriteria, "mOpportunisticMatchCriteria"); } - private static void validatePlmnIds(Set<String> allowedNetworkPlmnIds) { - Objects.requireNonNull(allowedNetworkPlmnIds, "allowedNetworkPlmnIds is null"); + private static void validatePlmnIds(Set<String> matchingOperatorPlmnIds) { + Objects.requireNonNull(matchingOperatorPlmnIds, "matchingOperatorPlmnIds is null"); // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal // digits. - for (String id : allowedNetworkPlmnIds) { + for (String id : matchingOperatorPlmnIds) { if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) { continue; } else { @@ -97,7 +110,7 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork Objects.requireNonNull(in, "PersistableBundle is null"); final int networkQuality = in.getInt(NETWORK_QUALITY_KEY); - final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY); + final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY); final PersistableBundle plmnIdsBundle = in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY); @@ -114,16 +127,16 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork PersistableBundleUtils.toList( specificCarrierIdsBundle, INTEGER_DESERIALIZER)); - final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY); - final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY); + final int roamingMatchCriteria = in.getInt(ROAMING_MATCH_KEY); + final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY); return new VcnCellUnderlyingNetworkTemplate( networkQuality, - allowMetered, + meteredMatchCriteria, allowedNetworkPlmnIds, allowedSpecificCarrierIds, - allowRoaming, - requireOpportunistic); + roamingMatchCriteria, + opportunisticMatchCriteria); } /** @hide */ @@ -143,35 +156,51 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER); result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle); - result.putBoolean(ALLOW_ROAMING_KEY, mAllowRoaming); - result.putBoolean(REQUIRE_OPPORTUNISTIC_KEY, mRequireOpportunistic); + result.putInt(ROAMING_MATCH_KEY, mRoamingMatchCriteria); + result.putInt(OPPORTUNISTIC_MATCH_KEY, mOpportunisticMatchCriteria); return result; } - /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */ + /** + * Retrieve the matching operator PLMN IDs, or an empty set if any PLMN ID is acceptable. + * + * @see Builder#setOperatorPlmnIds(Set) + */ @NonNull - public Set<String> getAllowedOperatorPlmnIds() { + public Set<String> getOperatorPlmnIds() { return Collections.unmodifiableSet(mAllowedNetworkPlmnIds); } /** - * Retrieve the allowed specific carrier IDs, or an empty set if any specific carrier ID is - * acceptable. + * Retrieve the matching sim specific carrier IDs, or an empty set if any sim specific carrier + * ID is acceptable. + * + * @see Builder#setSimSpecificCarrierIds(Set) */ @NonNull - public Set<Integer> getAllowedSpecificCarrierIds() { + public Set<Integer> getSimSpecificCarrierIds() { return Collections.unmodifiableSet(mAllowedSpecificCarrierIds); } - /** Return if roaming is allowed. */ - public boolean allowRoaming() { - return mAllowRoaming; + /** + * Return the matching criteria for roaming networks. + * + * @see Builder#setRoaming(int) + */ + @MatchCriteria + public int getRoaming() { + return mRoamingMatchCriteria; } - /** Return if requiring an opportunistic network. */ - public boolean requireOpportunistic() { - return mRequireOpportunistic; + /** + * Return the matching criteria for opportunistic cellular subscriptions. + * + * @see Builder#setOpportunistic(int) + */ + @MatchCriteria + public int getOpportunistic() { + return mOpportunisticMatchCriteria; } @Override @@ -180,8 +209,8 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork super.hashCode(), mAllowedNetworkPlmnIds, mAllowedSpecificCarrierIds, - mAllowRoaming, - mRequireOpportunistic); + mRoamingMatchCriteria, + mOpportunisticMatchCriteria); } @Override @@ -197,8 +226,8 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other; return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds) && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds) - && mAllowRoaming == rhs.mAllowRoaming - && mRequireOpportunistic == rhs.mRequireOpportunistic; + && mRoamingMatchCriteria == rhs.mRoamingMatchCriteria + && mOpportunisticMatchCriteria == rhs.mOpportunisticMatchCriteria; } /** @hide */ @@ -206,77 +235,137 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork void dumpTransportSpecificFields(IndentingPrintWriter pw) { pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString()); pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString()); - pw.println("mAllowRoaming: " + mAllowRoaming); - pw.println("mRequireOpportunistic: " + mRequireOpportunistic); + pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria)); + pw.println( + "mOpportunisticMatchCriteria: " + + getMatchCriteriaString(mOpportunisticMatchCriteria)); } - /** This class is used to incrementally build WifiNetworkPriority objects. */ - public static final class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> { + /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */ + public static final class Builder { + private int mNetworkQuality = NETWORK_QUALITY_ANY; + private int mMeteredMatchCriteria = MATCH_ANY; + @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>(); @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>(); - private boolean mAllowRoaming = false; - private boolean mRequireOpportunistic = false; + private int mRoamingMatchCriteria = MATCH_ANY; + private int mOpportunisticMatchCriteria = MATCH_ANY; /** Construct a Builder object. */ public Builder() {} /** - * Set allowed operator PLMN IDs. + * Set the required network quality to match this template. + * + * <p>Network quality is a aggregation of multiple signals that reflect the network link + * metrics. For example, the network validation bit (see {@link + * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth + * and signal strength. + * + * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY + * @hide + */ + @NonNull + public Builder setNetworkQuality(@NetworkQuality int networkQuality) { + validateNetworkQuality(networkQuality); + + mNetworkQuality = networkQuality; + return this; + } + + /** + * Set the matching criteria for metered networks. + * + * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one + * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will + * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED). + * + * @param matchCriteria the matching criteria for metered networks. Defaults to {@link + * #MATCH_ANY}. + * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED + */ + // The matching getter is defined in the super class. Please see {@link + // VcnUnderlyingNetworkTemplate#getMetered()} + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setMetered(@MatchCriteria int matchCriteria) { + validateMatchCriteria(matchCriteria, "setMetered"); + + mMeteredMatchCriteria = matchCriteria; + return this; + } + + /** + * Set operator PLMN IDs with which a network can match this template. * * <p>This is used to distinguish cases where roaming agreements may dictate a different * priority from a partner's networks. * - * @param allowedNetworkPlmnIds the allowed operator PLMN IDs in String. Defaults to an - * empty set, allowing ANY PLMN ID. A valid PLMN is a concatenation of MNC and MCC, and - * thus consists of 5 or 6 decimal digits. See {@link SubscriptionInfo#getMccString()} - * and {@link SubscriptionInfo#getMncString()}. + * @param operatorPlmnIds the matching operator PLMN IDs in String. Network with one of the + * matching PLMN IDs can match this template. If the set is empty, any PLMN ID will + * match. The default is an empty set. A valid PLMN is a concatenation of MNC and MCC, + * and thus consists of 5 or 6 decimal digits. + * @see SubscriptionInfo#getMccString() + * @see SubscriptionInfo#getMncString() */ @NonNull - public Builder setAllowedOperatorPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) { - validatePlmnIds(allowedNetworkPlmnIds); + public Builder setOperatorPlmnIds(@NonNull Set<String> operatorPlmnIds) { + validatePlmnIds(operatorPlmnIds); mAllowedNetworkPlmnIds.clear(); - mAllowedNetworkPlmnIds.addAll(allowedNetworkPlmnIds); + mAllowedNetworkPlmnIds.addAll(operatorPlmnIds); return this; } /** - * Set allowed specific carrier IDs. + * Set sim specific carrier IDs with which a network can match this template. * - * @param allowedSpecificCarrierIds the allowed specific carrier IDs. Defaults to an empty - * set, allowing ANY carrier ID. See {@link TelephonyManager#getSimSpecificCarrierId()}. + * @param simSpecificCarrierIds the matching sim specific carrier IDs. Network with one of + * the sim specific carrier IDs can match this template. If the set is empty, any + * carrier ID will match. The default is an empty set. + * @see TelephonyManager#getSimSpecificCarrierId() */ @NonNull - public Builder setAllowedSpecificCarrierIds( - @NonNull Set<Integer> allowedSpecificCarrierIds) { - Objects.requireNonNull(allowedSpecificCarrierIds, "allowedCarrierIds is null"); + public Builder setSimSpecificCarrierIds(@NonNull Set<Integer> simSpecificCarrierIds) { + Objects.requireNonNull(simSpecificCarrierIds, "simSpecificCarrierIds is null"); + mAllowedSpecificCarrierIds.clear(); - mAllowedSpecificCarrierIds.addAll(allowedSpecificCarrierIds); + mAllowedSpecificCarrierIds.addAll(simSpecificCarrierIds); return this; } /** - * Set if roaming is allowed. + * Set the matching criteria for roaming networks. * - * @param allowRoaming the flag to indicate if roaming is allowed. Defaults to {@code - * false}. + * <p>A template where setRoaming(MATCH_REQUIRED) will only match roaming networks (one + * without NET_CAPABILITY_NOT_ROAMING). A template where setRoaming(MATCH_FORBIDDEN) will + * only match a network that is not roaming (one with NET_CAPABILITY_NOT_ROAMING). + * + * @param matchCriteria the matching criteria for roaming networks. Defaults to {@link + * #MATCH_ANY}. + * @see NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING */ @NonNull - public Builder setAllowRoaming(boolean allowRoaming) { - mAllowRoaming = allowRoaming; + public Builder setRoaming(@MatchCriteria int matchCriteria) { + validateMatchCriteria(matchCriteria, "setRoaming"); + + mRoamingMatchCriteria = matchCriteria; return this; } /** - * Set if requiring an opportunistic network. + * Set the matching criteria for opportunistic cellular subscriptions. * - * @param requireOpportunistic the flag to indicate if caller requires an opportunistic - * network. Defaults to {@code false}. + * @param matchCriteria the matching criteria for opportunistic cellular subscriptions. + * Defaults to {@link #MATCH_ANY}. + * @see SubscriptionManager#setOpportunistic(boolean, int) */ @NonNull - public Builder setRequireOpportunistic(boolean requireOpportunistic) { - mRequireOpportunistic = requireOpportunistic; + public Builder setOpportunistic(@MatchCriteria int matchCriteria) { + validateMatchCriteria(matchCriteria, "setOpportunistic"); + + mOpportunisticMatchCriteria = matchCriteria; return this; } @@ -285,17 +374,11 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork public VcnCellUnderlyingNetworkTemplate build() { return new VcnCellUnderlyingNetworkTemplate( mNetworkQuality, - mAllowMetered, + mMeteredMatchCriteria, mAllowedNetworkPlmnIds, mAllowedSpecificCarrierIds, - mAllowRoaming, - mRequireOpportunistic); - } - - /** @hide */ - @Override - Builder self() { - return this; + mRoamingMatchCriteria, + mOpportunisticMatchCriteria); } } } diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index d07c24a6529c..92956e859fc7 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -16,6 +16,7 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static com.android.internal.annotations.VisibleForTesting.Visibility; @@ -42,7 +43,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.SortedSet; @@ -162,30 +163,24 @@ public final class VcnGatewayConnectionConfig { /** @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) - public static final LinkedHashSet<VcnUnderlyingNetworkTemplate> - DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>(); + public static final List<VcnUnderlyingNetworkTemplate> DEFAULT_UNDERLYING_NETWORK_TEMPLATES = + new ArrayList<>(); static { - DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( + DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add( new VcnCellUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setAllowRoaming(true /* allowRoaming */) - .setRequireOpportunistic(true /* requireOpportunistic */) + .setOpportunistic(MATCH_REQUIRED) .build()); - DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( + DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add( new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) .build()); - DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( + DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add( new VcnCellUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setAllowRoaming(true /* allowRoaming */) - .setRequireOpportunistic(false /* requireOpportunistic */) .build()); } @@ -200,9 +195,9 @@ public final class VcnGatewayConnectionConfig { /** @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) - public static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities"; + public static final String UNDERLYING_NETWORK_TEMPLATES_KEY = "mUnderlyingNetworkTemplates"; - @NonNull private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities; + @NonNull private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates; private static final String MAX_MTU_KEY = "mMaxMtu"; private final int mMaxMtu; @@ -215,7 +210,7 @@ public final class VcnGatewayConnectionConfig { @NonNull String gatewayConnectionName, @NonNull IkeTunnelConnectionParams tunnelConnectionParams, @NonNull Set<Integer> exposedCapabilities, - @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, + @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull long[] retryIntervalsMs, @IntRange(from = MIN_MTU_V6) int maxMtu) { mGatewayConnectionName = gatewayConnectionName; @@ -224,9 +219,9 @@ public final class VcnGatewayConnectionConfig { mRetryIntervalsMs = retryIntervalsMs; mMaxMtu = maxMtu; - mUnderlyingNetworkPriorities = new LinkedHashSet<>(underlyingNetworkPriorities); - if (mUnderlyingNetworkPriorities.isEmpty()) { - mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); + mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates); + if (mUnderlyingNetworkTemplates.isEmpty()) { + mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES); } validate(); @@ -250,22 +245,19 @@ public final class VcnGatewayConnectionConfig { mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList( exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER)); - final PersistableBundle networkPrioritiesBundle = - in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY); + final PersistableBundle networkTemplatesBundle = + in.getPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY); - if (networkPrioritiesBundle == null) { - // UNDERLYING_NETWORK_PRIORITIES_KEY was added in Android T. Thus + if (networkTemplatesBundle == null) { + // UNDERLYING_NETWORK_TEMPLATES_KEY was added in Android T. Thus // VcnGatewayConnectionConfig created on old platforms will not have this data and will // be assigned with the default value - mUnderlyingNetworkPriorities = - new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); - + mUnderlyingNetworkTemplates = new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES); } else { - mUnderlyingNetworkPriorities = - new LinkedHashSet<>( - PersistableBundleUtils.toList( - networkPrioritiesBundle, - VcnUnderlyingNetworkTemplate::fromPersistableBundle)); + mUnderlyingNetworkTemplates = + PersistableBundleUtils.toList( + networkTemplatesBundle, + VcnUnderlyingNetworkTemplate::fromPersistableBundle); } mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY); @@ -285,7 +277,7 @@ public final class VcnGatewayConnectionConfig { checkValidCapability(cap); } - Objects.requireNonNull(mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null"); + validateNetworkTemplateList(mUnderlyingNetworkTemplates); Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null"); validateRetryInterval(mRetryIntervalsMs); @@ -314,6 +306,19 @@ public final class VcnGatewayConnectionConfig { } } + private static void validateNetworkTemplateList( + List<VcnUnderlyingNetworkTemplate> networkPriorityRules) { + Objects.requireNonNull(networkPriorityRules, "networkPriorityRules is null"); + + Set<VcnUnderlyingNetworkTemplate> existingRules = new ArraySet<>(); + for (VcnUnderlyingNetworkTemplate rule : networkPriorityRules) { + Objects.requireNonNull(rule, "Found null value VcnUnderlyingNetworkTemplate"); + if (!existingRules.add(rule)) { + throw new IllegalArgumentException("Found duplicate VcnUnderlyingNetworkTemplate"); + } + } + } + /** * Returns the configured Gateway Connection name. * @@ -368,15 +373,13 @@ public final class VcnGatewayConnectionConfig { } /** - * Retrieve the configured VcnUnderlyingNetworkTemplate list, or a default list if it is not - * configured. + * Retrieve the VcnUnderlyingNetworkTemplate list, or a default list if it is not configured. * - * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkTemplate>) - * @hide + * @see Builder#setVcnUnderlyingNetworkPriorities(List) */ @NonNull - public LinkedHashSet<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() { - return new LinkedHashSet<>(mUnderlyingNetworkPriorities); + public List<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() { + return new ArrayList<>(mUnderlyingNetworkTemplates); } /** @@ -415,15 +418,15 @@ public final class VcnGatewayConnectionConfig { PersistableBundleUtils.fromList( new ArrayList<>(mExposedCapabilities), PersistableBundleUtils.INTEGER_SERIALIZER); - final PersistableBundle networkPrioritiesBundle = + final PersistableBundle networkTemplatesBundle = PersistableBundleUtils.fromList( - new ArrayList<>(mUnderlyingNetworkPriorities), + mUnderlyingNetworkTemplates, VcnUnderlyingNetworkTemplate::toPersistableBundle); result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName); result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle); result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle); - result.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, networkPrioritiesBundle); + result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle); result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs); result.putInt(MAX_MTU_KEY, mMaxMtu); @@ -436,7 +439,7 @@ public final class VcnGatewayConnectionConfig { mGatewayConnectionName, mTunnelConnectionParams, mExposedCapabilities, - mUnderlyingNetworkPriorities, + mUnderlyingNetworkTemplates, Arrays.hashCode(mRetryIntervalsMs), mMaxMtu); } @@ -451,7 +454,7 @@ public final class VcnGatewayConnectionConfig { return mGatewayConnectionName.equals(rhs.mGatewayConnectionName) && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams) && mExposedCapabilities.equals(rhs.mExposedCapabilities) - && mUnderlyingNetworkPriorities.equals(rhs.mUnderlyingNetworkPriorities) + && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates) && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs) && mMaxMtu == rhs.mMaxMtu; } @@ -465,8 +468,8 @@ public final class VcnGatewayConnectionConfig { @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet(); @NonNull - private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities = - new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); + private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates = + new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES); @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS; private int mMaxMtu = DEFAULT_MAX_MTU; @@ -539,27 +542,37 @@ public final class VcnGatewayConnectionConfig { } /** - * Set the VcnUnderlyingNetworkTemplate list. + * Set the list of templates to match underlying networks against, in high-to-low priority + * order. + * + * <p>To select the VCN underlying network, the VCN connection will go through all the + * network candidates and return a network matching the highest priority rule. + * + * <p>If multiple networks match the same rule, the VCN will prefer an already-selected + * network as opposed to a new/unselected network. However, if both are new/unselected + * networks, a network will be chosen arbitrarily amongst the networks matching the highest + * priority rule. * - * @param underlyingNetworkPriorities a list of unique VcnUnderlyingNetworkPriorities that - * are ordered from most to least preferred, or an empty list to use the default - * prioritization. The default network prioritization is Opportunistic cellular, Carrier - * WiFi and Macro cellular - * @return + * <p>If all networks fail to match the rules provided, an underlying network will still be + * selected (at random if necessary). + * + * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are + * ordered from most to least preferred, or an empty list to use the default + * prioritization. The default network prioritization order is Opportunistic cellular, + * Carrier WiFi and then Macro cellular. + * @return this {@link Builder} instance, for chaining */ - /** @hide */ @NonNull public Builder setVcnUnderlyingNetworkPriorities( - @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities) { - Objects.requireNonNull( - mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null"); + @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) { + validateNetworkTemplateList(underlyingNetworkTemplates); - mUnderlyingNetworkPriorities.clear(); + mUnderlyingNetworkTemplates.clear(); - if (underlyingNetworkPriorities.isEmpty()) { - mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); + if (underlyingNetworkTemplates.isEmpty()) { + mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES); } else { - mUnderlyingNetworkPriorities.addAll(underlyingNetworkPriorities); + mUnderlyingNetworkTemplates.addAll(underlyingNetworkTemplates); } return this; @@ -629,7 +642,7 @@ public final class VcnGatewayConnectionConfig { mGatewayConnectionName, mTunnelConnectionParams, mExposedCapabilities, - mUnderlyingNetworkPriorities, + mUnderlyingNetworkTemplates, mRetryIntervalsMs, mMaxMtu); } diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java index d306d5cb6826..60fc936072fb 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java @@ -15,6 +15,8 @@ */ package android.net.vcn; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; + import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.IntDef; @@ -31,17 +33,24 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; -// TODO: Add documents -/** @hide */ +/** + * This class represents a template containing set of underlying network requirements for doing + * route selection. + * + * <p>Apps provisioning a VCN can configure the underlying network priority for each Gateway + * Connection by setting a list (in priority order, most to least preferred) of the appropriate + * subclasses in the VcnGatewayConnectionConfig. See {@link + * VcnGatewayConnectionConfig.Builder#setVcnUnderlyingNetworkPriorities} + */ public abstract class VcnUnderlyingNetworkTemplate { /** @hide */ - protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1; + static final int NETWORK_PRIORITY_TYPE_WIFI = 1; /** @hide */ - protected static final int NETWORK_PRIORITY_TYPE_CELL = 2; + static final int NETWORK_PRIORITY_TYPE_CELL = 2; - /** Denotes that any network quality is acceptable */ + /** Denotes that any network quality is acceptable. @hide */ public static final int NETWORK_QUALITY_ANY = 0; - /** Denotes that network quality needs to be OK */ + /** Denotes that network quality needs to be OK. @hide */ public static final int NETWORK_QUALITY_OK = 100000; private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>(); @@ -56,34 +65,82 @@ public abstract class VcnUnderlyingNetworkTemplate { @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY}) public @interface NetworkQuality {} + /** + * Used to configure the matching criteria of a network characteristic. This may include network + * capabilities, or cellular subscription information. Denotes that networks with or without the + * characteristic are both acceptable to match this template. + */ + public static final int MATCH_ANY = 0; + + /** + * Used to configure the matching criteria of a network characteristic. This may include network + * capabilities, or cellular subscription information. Denotes that a network MUST have the + * capability in order to match this template. + */ + public static final int MATCH_REQUIRED = 1; + + /** + * Used to configure the matching criteria of a network characteristic. This may include network + * capabilities, or cellular subscription information. Denotes that a network MUST NOT have the + * capability in order to match this template. + */ + public static final int MATCH_FORBIDDEN = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({MATCH_ANY, MATCH_REQUIRED, MATCH_FORBIDDEN}) + public @interface MatchCriteria {} + + private static final SparseArray<String> MATCH_CRITERIA_TO_STRING_MAP = new SparseArray<>(); + + static { + MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_ANY, "MATCH_ANY"); + MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_REQUIRED, "MATCH_REQUIRED"); + MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_FORBIDDEN, "MATCH_FORBIDDEN"); + } + private static final String NETWORK_PRIORITY_TYPE_KEY = "mNetworkPriorityType"; private final int mNetworkPriorityType; /** @hide */ - protected static final String NETWORK_QUALITY_KEY = "mNetworkQuality"; + static final String NETWORK_QUALITY_KEY = "mNetworkQuality"; + private final int mNetworkQuality; /** @hide */ - protected static final String ALLOW_METERED_KEY = "mAllowMetered"; - private final boolean mAllowMetered; + static final String METERED_MATCH_KEY = "mMeteredMatchCriteria"; + + private final int mMeteredMatchCriteria; /** @hide */ - protected VcnUnderlyingNetworkTemplate( - int networkPriorityType, int networkQuality, boolean allowMetered) { + VcnUnderlyingNetworkTemplate( + int networkPriorityType, int networkQuality, int meteredMatchCriteria) { mNetworkPriorityType = networkPriorityType; mNetworkQuality = networkQuality; - mAllowMetered = allowMetered; + mMeteredMatchCriteria = meteredMatchCriteria; } - private static void validateNetworkQuality(int networkQuality) { + /** @hide */ + static void validateNetworkQuality(int networkQuality) { Preconditions.checkArgument( networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK, "Invalid networkQuality:" + networkQuality); } /** @hide */ + static void validateMatchCriteria(int meteredMatchCriteria, String matchingCapability) { + Preconditions.checkArgument( + MATCH_CRITERIA_TO_STRING_MAP.contains(meteredMatchCriteria), + "Invalid matching criteria: " + + meteredMatchCriteria + + " for " + + matchingCapability); + } + + /** @hide */ protected void validate() { validateNetworkQuality(mNetworkQuality); + validateMatchCriteria(mMeteredMatchCriteria, "mMeteredMatchCriteria"); } /** @hide */ @@ -112,14 +169,14 @@ public abstract class VcnUnderlyingNetworkTemplate { result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType); result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality); - result.putBoolean(ALLOW_METERED_KEY, mAllowMetered); + result.putInt(METERED_MATCH_KEY, mMeteredMatchCriteria); return result; } @Override public int hashCode() { - return Objects.hash(mNetworkPriorityType, mNetworkQuality, mAllowMetered); + return Objects.hash(mNetworkPriorityType, mNetworkQuality, mMeteredMatchCriteria); } @Override @@ -131,7 +188,17 @@ public abstract class VcnUnderlyingNetworkTemplate { final VcnUnderlyingNetworkTemplate rhs = (VcnUnderlyingNetworkTemplate) other; return mNetworkPriorityType == rhs.mNetworkPriorityType && mNetworkQuality == rhs.mNetworkQuality - && mAllowMetered == rhs.mAllowMetered; + && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria; + } + + /** @hide */ + static String getNameString(SparseArray<String> toStringMap, int key) { + return toStringMap.get(key, "Invalid value " + key); + } + + /** @hide */ + static String getMatchCriteriaString(int meteredMatchCriteria) { + return getNameString(MATCH_CRITERIA_TO_STRING_MAP, meteredMatchCriteria); } /** @hide */ @@ -148,65 +215,32 @@ public abstract class VcnUnderlyingNetworkTemplate { pw.println( "mNetworkQuality: " - + NETWORK_QUALITY_TO_STRING_MAP.get( - mNetworkQuality, "Invalid value " + mNetworkQuality)); - pw.println("mAllowMetered: " + mAllowMetered); + + getNameString(NETWORK_QUALITY_TO_STRING_MAP, mNetworkQuality)); + pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria)); dumpTransportSpecificFields(pw); pw.decreaseIndent(); } - /** Retrieve the required network quality. */ + /** + * Retrieve the required network quality to match this template. + * + * @see Builder#setNetworkQuality(int) + * @hide + */ @NetworkQuality public int getNetworkQuality() { return mNetworkQuality; } - /** Return if a metered network is allowed. */ - public boolean allowMetered() { - return mAllowMetered; - } - /** - * This class is used to incrementally build VcnUnderlyingNetworkTemplate objects. + * Return the matching criteria for metered networks. * - * @param <T> The subclass to be built. + * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMetered(int) + * @see VcnCellUnderlyingNetworkTemplate.Builder#setMetered(int) */ - public abstract static class Builder<T extends Builder<T>> { - /** @hide */ - protected int mNetworkQuality = NETWORK_QUALITY_ANY; - /** @hide */ - protected boolean mAllowMetered = false; - - /** @hide */ - protected Builder() {} - - /** - * Set the required network quality. - * - * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY - */ - @NonNull - public T setNetworkQuality(@NetworkQuality int networkQuality) { - validateNetworkQuality(networkQuality); - - mNetworkQuality = networkQuality; - return self(); - } - - /** - * Set if a metered network is allowed. - * - * @param allowMetered the flag to indicate if a metered network is allowed, defaults to - * {@code false} - */ - @NonNull - public T setAllowMetered(boolean allowMetered) { - mAllowMetered = allowMetered; - return self(); - } - - /** @hide */ - abstract T self(); + @MatchCriteria + public int getMetered() { + return mMeteredMatchCriteria; } } diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java index 6bbb2bfecda4..272ca9dd7583 100644 --- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java @@ -16,31 +16,59 @@ package android.net.vcn; import static com.android.internal.annotations.VisibleForTesting.Visibility; +import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; +import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.net.NetworkCapabilities; import android.os.PersistableBundle; +import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.vcn.util.PersistableBundleUtils; +import java.util.ArrayList; +import java.util.Collections; import java.util.Objects; +import java.util.Set; -// TODO: Add documents -/** @hide */ +/** + * This class represents a configuration for a network template class of underlying Carrier WiFi + * networks. + * + * <p>See {@link VcnUnderlyingNetworkTemplate} + */ public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate { - private static final String SSID_KEY = "mSsid"; - @Nullable private final String mSsid; + private static final String SSIDS_KEY = "mSsids"; + @Nullable private final Set<String> mSsids; private VcnWifiUnderlyingNetworkTemplate( - int networkQuality, boolean allowMetered, String ssid) { - super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered); - mSsid = ssid; + int networkQuality, int meteredMatchCriteria, Set<String> ssids) { + super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, meteredMatchCriteria); + mSsids = new ArraySet<>(ssids); validate(); } /** @hide */ + @Override + protected void validate() { + super.validate(); + validateSsids(mSsids); + } + + private static void validateSsids(Set<String> ssids) { + Objects.requireNonNull(ssids, "ssids is null"); + + for (String ssid : ssids) { + Objects.requireNonNull(ssid, "found null value ssid"); + } + } + + /** @hide */ @NonNull @VisibleForTesting(visibility = Visibility.PROTECTED) public static VcnWifiUnderlyingNetworkTemplate fromPersistableBundle( @@ -48,9 +76,14 @@ public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetwork Objects.requireNonNull(in, "PersistableBundle is null"); final int networkQuality = in.getInt(NETWORK_QUALITY_KEY); - final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY); - final String ssid = in.getString(SSID_KEY); - return new VcnWifiUnderlyingNetworkTemplate(networkQuality, allowMetered, ssid); + final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY); + + final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY); + Objects.requireNonNull(ssidsBundle, "ssidsBundle is null"); + final Set<String> ssids = + new ArraySet<String>( + PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER)); + return new VcnWifiUnderlyingNetworkTemplate(networkQuality, meteredMatchCriteria, ssids); } /** @hide */ @@ -59,13 +92,17 @@ public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetwork @VisibleForTesting(visibility = Visibility.PROTECTED) public PersistableBundle toPersistableBundle() { final PersistableBundle result = super.toPersistableBundle(); - result.putString(SSID_KEY, mSsid); + + final PersistableBundle ssidsBundle = + PersistableBundleUtils.fromList(new ArrayList<>(mSsids), STRING_SERIALIZER); + result.putPersistableBundle(SSIDS_KEY, ssidsBundle); + return result; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), mSsid); + return Objects.hash(super.hashCode(), mSsids); } @Override @@ -79,49 +116,95 @@ public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetwork } final VcnWifiUnderlyingNetworkTemplate rhs = (VcnWifiUnderlyingNetworkTemplate) other; - return mSsid.equals(rhs.mSsid); + return mSsids.equals(rhs.mSsids); } /** @hide */ @Override void dumpTransportSpecificFields(IndentingPrintWriter pw) { - pw.println("mSsid: " + mSsid); + pw.println("mSsids: " + mSsids); } - /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */ - @Nullable - public String getSsid() { - return mSsid; + /** + * Retrieve the matching SSIDs, or an empty set if any SSID is acceptable. + * + * @see Builder#setSsids(Set) + */ + @NonNull + public Set<String> getSsids() { + return Collections.unmodifiableSet(mSsids); } /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */ - public static class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> { - @Nullable private String mSsid; + public static final class Builder { + private int mNetworkQuality = NETWORK_QUALITY_ANY; + private int mMeteredMatchCriteria = MATCH_ANY; + @NonNull private final Set<String> mSsids = new ArraySet<>(); /** Construct a Builder object. */ public Builder() {} /** - * Set the required SSID. + * Set the required network quality to match this template. * - * @param ssid the required SSID, or {@code null} if any SSID is acceptable. + * <p>Network quality is a aggregation of multiple signals that reflect the network link + * metrics. For example, the network validation bit (see {@link + * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth + * and signal strength. + * + * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY + * @hide */ @NonNull - public Builder setSsid(@Nullable String ssid) { - mSsid = ssid; + public Builder setNetworkQuality(@NetworkQuality int networkQuality) { + validateNetworkQuality(networkQuality); + + mNetworkQuality = networkQuality; return this; } - /** Build the VcnWifiUnderlyingNetworkTemplate. */ + /** + * Set the matching criteria for metered networks. + * + * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one + * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will + * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED). + * + * @param matchCriteria the matching criteria for metered networks. Defaults to {@link + * #MATCH_ANY}. + * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED + */ + // The matching getter is defined in the super class. Please see {@link + // VcnUnderlyingNetworkTemplate#getMetered()} + @SuppressLint("MissingGetterMatchingBuilder") @NonNull - public VcnWifiUnderlyingNetworkTemplate build() { - return new VcnWifiUnderlyingNetworkTemplate(mNetworkQuality, mAllowMetered, mSsid); + public Builder setMetered(@MatchCriteria int matchCriteria) { + validateMatchCriteria(matchCriteria, "setMetered"); + + mMeteredMatchCriteria = matchCriteria; + return this; } - /** @hide */ - @Override - Builder self() { + /** + * Set the SSIDs with which a network can match this priority rule. + * + * @param ssids the matching SSIDs. Network with one of the matching SSIDs can match this + * priority rule. If the set is empty, any SSID will match. The default is an empty set. + */ + @NonNull + public Builder setSsids(@NonNull Set<String> ssids) { + validateSsids(ssids); + + mSsids.clear(); + mSsids.addAll(ssids); return this; } + + /** Build the VcnWifiUnderlyingNetworkTemplate. */ + @NonNull + public VcnWifiUnderlyingNetworkTemplate build() { + return new VcnWifiUnderlyingNetworkTemplate( + mNetworkQuality, mMeteredMatchCriteria, mSsids); + } } } diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java index b79c0553460f..105c2aa53374 100644 --- a/core/java/android/service/games/GameService.java +++ b/core/java/android/service/games/GameService.java @@ -38,10 +38,23 @@ import java.util.Objects; * when a game session should begin. It is always kept running by the system. * Because of this it should be kept as lightweight as possible. * - * Heavy weight operations (such as showing UI) should be implemented in the + * <p>Heavyweight operations (such as showing UI) should be implemented in the * associated {@link GameSessionService} when a game session is taking place. Its * implementation should run in a separate process from the {@link GameService}. * + * <p>To extend this class, you must declare the service in your manifest file with + * the {@link android.Manifest.permission#BIND_GAME_SERVICE} permission + * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example: + * <pre> + * <service android:name=".GameService" + * android:label="@string/service_name" + * android:permission="android.permission.BIND_GAME_SERVICE"> + * <intent-filter> + * <action android:name="android.service.games.GameService" /> + * </intent-filter> + * </service> + * </pre> + * * @hide */ @SystemApi @@ -90,7 +103,7 @@ public class GameService extends Service { @Override @Nullable - public IBinder onBind(@Nullable Intent intent) { + public final IBinder onBind(@Nullable Intent intent) { if (ACTION_GAME_SERVICE.equals(intent.getAction())) { return mInterface.asBinder(); } diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java index a2c88709b62d..c1a3eb5286c4 100644 --- a/core/java/android/service/games/GameSessionService.java +++ b/core/java/android/service/games/GameSessionService.java @@ -73,7 +73,7 @@ public abstract class GameSessionService extends Service { @Override @Nullable - public IBinder onBind(@Nullable Intent intent) { + public final IBinder onBind(@Nullable Intent intent) { if (intent == null) { return null; } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 07d5fc533a3a..25e0eca12bf3 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -1896,7 +1896,7 @@ public class ViewDebug { private Canvas mCanvas; private Bitmap mBitmap; - private boolean mEnabledHwBitmapsInSwMode; + private boolean mEnabledHwFeaturesInSwMode; @Override public Canvas getCanvas(View view, int width, int height) { @@ -1913,7 +1913,7 @@ public class ViewDebug { if (mCanvas == null) { mCanvas = new Canvas(); } - mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled(); + mEnabledHwFeaturesInSwMode = mCanvas.isHwFeaturesInSwModeEnabled(); mCanvas.setBitmap(mBitmap); return mCanvas; } @@ -1921,7 +1921,7 @@ public class ViewDebug { @Override public Bitmap createBitmap() { mCanvas.setBitmap(null); - mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode); + mCanvas.setHwFeaturesInSwModeEnabled(mEnabledHwFeaturesInSwMode); return mBitmap; } } diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index 974a1dd50cf5..88ece5c536f6 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -101,14 +101,6 @@ public class DisplayAreaOrganizer extends WindowOrganizer { public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7; /** - * Display area for one handed background layer, which preventing when user - * turning the Dark theme on, they can not clearly identify the screen has entered - * one handed mode. - * @hide - */ - public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8; - - /** * Display area hosting IME window tokens (@see ImeContainer). By default, IMEs are parented * to FEATURE_IME_PLACEHOLDER but can be reparented under other RootDisplayArea. * @@ -118,7 +110,7 @@ public class DisplayAreaOrganizer extends WindowOrganizer { * app on another screen). * @hide */ - public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 9; + public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 8; /** * The last boundary of display area for system features diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 2a203acebc72..b579be03acbd 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -34,8 +34,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; -import java.net.Inet4Address; -import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -93,8 +91,8 @@ public class VpnConfig implements Parcelable { public String interfaze; public String session; public int mtu = -1; - public List<LinkAddress> addresses = new ArrayList<LinkAddress>(); - public List<RouteInfo> routes = new ArrayList<RouteInfo>(); + public List<LinkAddress> addresses = new ArrayList<>(); + public List<RouteInfo> routes = new ArrayList<>(); public List<String> dnsServers; public List<String> searchDomains; public List<String> allowedApplications; @@ -114,12 +112,32 @@ public class VpnConfig implements Parcelable { public VpnConfig() { } - public void updateAllowedFamilies(InetAddress address) { - if (address instanceof Inet4Address) { - allowIPv4 = true; - } else { - allowIPv6 = true; - } + public VpnConfig(VpnConfig other) { + user = other.user; + interfaze = other.interfaze; + session = other.session; + mtu = other.mtu; + addresses = copyOf(other.addresses); + routes = copyOf(other.routes); + dnsServers = copyOf(other.dnsServers); + searchDomains = copyOf(other.searchDomains); + allowedApplications = copyOf(other.allowedApplications); + disallowedApplications = copyOf(other.disallowedApplications); + configureIntent = other.configureIntent; + startTime = other.startTime; + legacy = other.legacy; + blocking = other.blocking; + allowBypass = other.allowBypass; + allowIPv4 = other.allowIPv4; + allowIPv6 = other.allowIPv6; + isMetered = other.isMetered; + underlyingNetworks = other.underlyingNetworks != null ? Arrays.copyOf( + other.underlyingNetworks, other.underlyingNetworks.length) : null; + proxyInfo = other.proxyInfo; + } + + private static <T> List<T> copyOf(List<T> list) { + return list != null ? new ArrayList<>(list) : null; } public void addLegacyRoutes(String routesStr) { @@ -131,7 +149,6 @@ public class VpnConfig implements Parcelable { //each route is ip/prefix RouteInfo info = new RouteInfo(new IpPrefix(route), null, null, RouteInfo.RTN_UNICAST); this.routes.add(info); - updateAllowedFamilies(info.getDestination().getAddress()); } } @@ -144,7 +161,6 @@ public class VpnConfig implements Parcelable { //each address is ip/prefix LinkAddress addr = new LinkAddress(address); this.addresses.add(addr); - updateAllowedFamilies(addr.getAddress()); } } diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index 627381c620c1..09ff4e0aa076 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -37,6 +37,7 @@ import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; +import android.view.RoundedCorner; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -229,13 +230,29 @@ public class PointerLocationView extends View implements InputDeviceListener, @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { + int headerPaddingTop = 0; + Insets waterfallInsets = Insets.NONE; + + final RoundedCorner topLeftRounded = + insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT); + if (topLeftRounded != null) { + headerPaddingTop = topLeftRounded.getRadius(); + } + + final RoundedCorner topRightRounded = + insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT); + if (topRightRounded != null) { + headerPaddingTop = Math.max(headerPaddingTop, topRightRounded.getRadius()); + } + if (insets.getDisplayCutout() != null) { - mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop(); - mWaterfallInsets = insets.getDisplayCutout().getWaterfallInsets(); - } else { - mHeaderPaddingTop = 0; - mWaterfallInsets = Insets.NONE; + headerPaddingTop = + Math.max(headerPaddingTop, insets.getDisplayCutout().getSafeInsetTop()); + waterfallInsets = insets.getDisplayCutout().getWaterfallInsets(); } + + mHeaderPaddingTop = headerPaddingTop; + mWaterfallInsets = waterfallInsets; return super.onApplyWindowInsets(insets); } diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index a612265793a3..425a37891afb 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -67,7 +67,7 @@ public abstract class BaseCanvas { * @hide */ protected int mDensity = Bitmap.DENSITY_NONE; - private boolean mAllowHwBitmapsInSwMode = false; + private boolean mAllowHwFeaturesInSwMode = false; protected void throwIfCannotDraw(Bitmap bitmap) { if (bitmap.isRecycled()) { @@ -101,14 +101,14 @@ public abstract class BaseCanvas { public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle, useCenter, paint.getNativeInstance()); } public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter, paint); } @@ -119,14 +119,14 @@ public abstract class BaseCanvas { public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) { throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity, bitmap.mDensity); } public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(), paint != null ? paint.getNativeInstance() : 0); } @@ -137,7 +137,7 @@ public abstract class BaseCanvas { throw new NullPointerException(); } throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); int left, top, right, bottom; @@ -163,7 +163,7 @@ public abstract class BaseCanvas { throw new NullPointerException(); } throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); float left, top, right, bottom; @@ -202,7 +202,7 @@ public abstract class BaseCanvas { || (lastScanline + width > length)) { throw new ArrayIndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); // quick escape if there's nothing to draw if (width == 0 || height == 0) { return; @@ -226,7 +226,7 @@ public abstract class BaseCanvas { if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) { throw new ArrayIndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (meshWidth == 0 || meshHeight == 0) { return; } @@ -243,7 +243,7 @@ public abstract class BaseCanvas { } public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance()); } @@ -275,23 +275,23 @@ public abstract class BaseCanvas { public void drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance()); } public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance()); } public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawLines(pts, 0, pts.length, paint); } public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance()); } @@ -299,18 +299,19 @@ public abstract class BaseCanvas { if (oval == null) { throw new NullPointerException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawOval(oval.left, oval.top, oval.right, oval.bottom, paint); } public void drawPaint(@NonNull Paint paint) { + throwIfHasHwFeaturesInSwMode(paint); nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance()); } public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint, @@ -320,7 +321,7 @@ public abstract class BaseCanvas { public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint, @@ -328,7 +329,7 @@ public abstract class BaseCanvas { } public void drawPath(@NonNull Path path, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (path.isSimplePath && path.rects != null) { nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance()); } else { @@ -337,18 +338,18 @@ public abstract class BaseCanvas { } public void drawPoint(float x, float y, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance()); } public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance()); } public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawPoints(pts, 0, pts.length, paint); } @@ -359,7 +360,7 @@ public abstract class BaseCanvas { if (index < 0 || index + count > text.length || count * 2 > pos.length) { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); for (int i = 0; i < count; i++) { drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint); } @@ -368,22 +369,22 @@ public abstract class BaseCanvas { @Deprecated public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawPosText(text.toCharArray(), 0, text.length(), pos, paint); } public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance()); } public void drawRect(@NonNull Rect r, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawRect(r.left, r.top, r.right, r.bottom, paint); } public void drawRect(@NonNull RectF rect, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance()); } @@ -394,13 +395,13 @@ public abstract class BaseCanvas { public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, paint.getNativeInstance()); } public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint); } @@ -410,7 +411,7 @@ public abstract class BaseCanvas { */ public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy, @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); float outerLeft = outer.left; float outerTop = outer.top; float outerRight = outer.right; @@ -431,7 +432,7 @@ public abstract class BaseCanvas { */ public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii, @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (innerRadii == null || outerRadii == null || innerRadii.length != 8 || outerRadii.length != 8) { throw new IllegalArgumentException("Both inner and outer radii arrays must contain " @@ -509,7 +510,7 @@ public abstract class BaseCanvas { (text.length - index - count)) < 0) { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags, paint.getNativeInstance()); } @@ -519,7 +520,7 @@ public abstract class BaseCanvas { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y, @@ -537,7 +538,7 @@ public abstract class BaseCanvas { } public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags, paint.getNativeInstance()); } @@ -547,7 +548,7 @@ public abstract class BaseCanvas { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags, paint.getNativeInstance()); } @@ -557,7 +558,7 @@ public abstract class BaseCanvas { if (index < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawTextOnPath(mNativeCanvasWrapper, text, index, count, path.readOnlyNI(), hOffset, vOffset, paint.mBidiFlags, paint.getNativeInstance()); @@ -566,7 +567,7 @@ public abstract class BaseCanvas { public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset, float vOffset, @NonNull Paint paint) { if (text.length() > 0) { - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset, paint.mBidiFlags, paint.getNativeInstance()); } @@ -587,7 +588,7 @@ public abstract class BaseCanvas { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount, x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */); } @@ -606,7 +607,7 @@ public abstract class BaseCanvas { throw new IndexOutOfBoundsException(); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart, @@ -664,7 +665,7 @@ public abstract class BaseCanvas { if (indices != null) { checkRange(indices.length, indexOffset, indexCount); } - throwIfHasHwBitmapInSwMode(paint); + throwIfHasHwFeaturesInSwMode(paint); nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts, vertOffset, texs, texOffset, colors, colorOffset, indices, indexOffset, indexCount, paint.getNativeInstance()); @@ -680,50 +681,52 @@ public abstract class BaseCanvas { /** * @hide */ - public void setHwBitmapsInSwModeEnabled(boolean enabled) { - mAllowHwBitmapsInSwMode = enabled; + public void setHwFeaturesInSwModeEnabled(boolean enabled) { + mAllowHwFeaturesInSwMode = enabled; } /** * @hide */ - public boolean isHwBitmapsInSwModeEnabled() { - return mAllowHwBitmapsInSwMode; + public boolean isHwFeaturesInSwModeEnabled() { + return mAllowHwFeaturesInSwMode; } /** + * If true throw an exception * @hide */ - protected void onHwBitmapInSwMode() { - if (!mAllowHwBitmapsInSwMode) { - throw new IllegalArgumentException( - "Software rendering doesn't support hardware bitmaps"); - } + protected boolean onHwFeatureInSwMode() { + return !mAllowHwFeaturesInSwMode; } private void throwIfHwBitmapInSwMode(Bitmap bitmap) { - if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) { - onHwBitmapInSwMode(); + if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE + && onHwFeatureInSwMode()) { + throw new IllegalArgumentException( + "Software rendering doesn't support hardware bitmaps"); } } - private void throwIfHasHwBitmapInSwMode(Paint p) { + private void throwIfHasHwFeaturesInSwMode(Paint p) { if (isHardwareAccelerated() || p == null) { return; } - throwIfHasHwBitmapInSwMode(p.getShader()); + throwIfHasHwFeaturesInSwMode(p.getShader()); } - private void throwIfHasHwBitmapInSwMode(Shader shader) { + private void throwIfHasHwFeaturesInSwMode(Shader shader) { if (shader == null) { return; } if (shader instanceof BitmapShader) { throwIfHwBitmapInSwMode(((BitmapShader) shader).mBitmap); - } - if (shader instanceof ComposeShader) { - throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderA); - throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderB); + } else if (shader instanceof RuntimeShader && onHwFeatureInSwMode()) { + throw new IllegalArgumentException( + "Software rendering doesn't support RuntimeShader"); + } else if (shader instanceof ComposeShader) { + throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderA); + throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderB); } } diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index 390d3d414346..ee4165b8da05 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -124,7 +124,7 @@ public class Picture { public void endRecording() { verifyValid(); if (mRecordingCanvas != null) { - mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap; + mRequiresHwAcceleration = mRecordingCanvas.mUsesHwFeature; mRecordingCanvas = null; nativeEndRecording(mNativePicture); } @@ -182,8 +182,10 @@ public class Picture { if (mRecordingCanvas != null) { endRecording(); } - if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) { - canvas.onHwBitmapInSwMode(); + if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated() + && canvas.onHwFeatureInSwMode()) { + throw new IllegalArgumentException("Software rendering not supported for Pictures that" + + " require hardware acceleration"); } nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture); } @@ -242,7 +244,7 @@ public class Picture { private static class PictureCanvas extends Canvas { private final Picture mPicture; - boolean mHoldsHwBitmap; + boolean mUsesHwFeature; public PictureCanvas(Picture pict, long nativeCanvas) { super(nativeCanvas); @@ -265,8 +267,9 @@ public class Picture { } @Override - protected void onHwBitmapInSwMode() { - mHoldsHwBitmap = true; + protected boolean onHwFeatureInSwMode() { + mUsesHwFeature = true; + return false; } } } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index b843589376c4..ffaa4ea51452 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -868,7 +868,7 @@ public class RippleDrawable extends LayerDrawable { private void drawPatterned(@NonNull Canvas canvas) { final Rect bounds = mHotspotBounds; final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); - boolean useCanvasProps = shouldUseCanvasProps(canvas); + boolean useCanvasProps = !mForceSoftware; if (isBounded()) { canvas.clipRect(getDirtyBounds()); } @@ -914,7 +914,11 @@ public class RippleDrawable extends LayerDrawable { } for (int i = 0; i < mRunningAnimations.size(); i++) { RippleAnimationSession s = mRunningAnimations.get(i); - if (useCanvasProps) { + if (!canvas.isHardwareAccelerated()) { + Log.e(TAG, "The RippleDrawable.STYLE_PATTERNED animation is not supported for a " + + "non-hardware accelerated Canvas. Skipping animation."); + break; + } else if (useCanvasProps) { RippleAnimationSession.AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> p = s.getCanvasProperties(); @@ -1002,10 +1006,6 @@ public class RippleDrawable extends LayerDrawable { return color; } - private boolean shouldUseCanvasProps(Canvas c) { - return !mForceSoftware && c.isHardwareAccelerated(); - } - @Override public void invalidateSelf() { invalidateSelf(true); diff --git a/libs/WindowManager/Shell/res/layout/background_panel.xml b/libs/WindowManager/Shell/res/layout/background_panel.xml new file mode 100644 index 000000000000..c3569d80fa1e --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/background_panel.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2022 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 + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/background_panel_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal | center_vertical" + android:background="@android:color/transparent"> +</LinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java new file mode 100644 index 000000000000..c20b7d9b2747 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.onehanded; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; +import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; + +import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Binder; +import android.util.Slog; +import android.view.ContextThemeWrapper; +import android.view.IWindow; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.SurfaceSession; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; +import com.android.wm.shell.common.DisplayLayout; + +import java.io.PrintWriter; + +/** + * Holds view hierarchy of a root surface and helps inflate a themeable view for background. + */ +public final class BackgroundWindowManager extends WindowlessWindowManager { + private static final String TAG = BackgroundWindowManager.class.getSimpleName(); + private static final int THEME_COLOR_OFFSET = 10; + + private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory + mTransactionFactory; + + private Context mContext; + private Rect mDisplayBounds; + private SurfaceControlViewHost mViewHost; + private SurfaceControl mLeash; + private View mBackgroundView; + private @OneHandedState.State int mCurrentState; + + public BackgroundWindowManager(Context context) { + super(context.getResources().getConfiguration(), null /* rootSurface */, + null /* hostInputToken */); + mContext = context; + mTransactionFactory = SurfaceControl.Transaction::new; + } + + @Override + public SurfaceControl getSurfaceControl(IWindow window) { + return super.getSurfaceControl(window); + } + + @Override + public void setConfiguration(Configuration configuration) { + super.setConfiguration(configuration); + mContext = mContext.createConfigurationContext(configuration); + } + + /** + * onConfigurationChanged events for updating background theme color. + */ + public void onConfigurationChanged() { + if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { + updateThemeOnly(); + } + } + + /** + * One-handed mode state changed callback + * @param newState of One-handed mode representing by {@link OneHandedState} + */ + public void onStateChanged(int newState) { + mCurrentState = newState; + } + + @Override + protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) + .setColorLayer() + .setBufferSize(mDisplayBounds.width(), mDisplayBounds.height()) + .setFormat(PixelFormat.RGB_888) + .setOpaque(true) + .setName(TAG) + .setCallsite("BackgroundWindowManager#attachToParentSurface"); + mLeash = builder.build(); + b.setParent(mLeash); + } + + /** Inflates background view on to the root surface. */ + boolean initView() { + if (mBackgroundView != null || mViewHost != null) { + return false; + } + + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + mBackgroundView = (View) LayoutInflater.from(mContext) + .inflate(R.layout.background_panel, null /* root */); + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + mDisplayBounds.width(), mDisplayBounds.height(), 0 /* TYPE NONE */, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH + | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); + lp.token = new Binder(); + lp.setTitle("background-panel"); + lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + mBackgroundView.setBackgroundColor(getThemeColorForBackground()); + mViewHost.setView(mBackgroundView, lp); + return true; + } + + /** + * Called when onDisplayAdded() or onDisplayRemoved() callback. + * @param displayLayout The latest {@link DisplayLayout} for display bounds. + */ + public void onDisplayChanged(DisplayLayout displayLayout) { + // One-handed mode is only available on portrait. + if (displayLayout.height() > displayLayout.width()) { + mDisplayBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height()); + } else { + mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width()); + } + } + + private void updateThemeOnly() { + if (mBackgroundView == null || mViewHost == null || mLeash == null) { + Slog.w(TAG, "Background view or SurfaceControl does not exist when trying to " + + "update theme only!"); + return; + } + + WindowManager.LayoutParams lp = (WindowManager.LayoutParams) + mBackgroundView.getLayoutParams(); + mBackgroundView.setBackgroundColor(getThemeColorForBackground()); + mViewHost.setView(mBackgroundView, lp); + } + + /** + * Shows the background layer when One-handed mode triggered. + */ + public void showBackgroundLayer() { + if (!initView()) { + updateThemeOnly(); + return; + } + if (mLeash == null) { + Slog.w(TAG, "SurfaceControl mLeash is null, can't show One-handed mode " + + "background panel!"); + return; + } + + mTransactionFactory.getTransaction() + .setAlpha(mLeash, 1.0f) + .setLayer(mLeash, -1 /* at bottom-most layer */) + .show(mLeash) + .apply(); + } + + /** + * Remove the leash of background layer after stop One-handed mode. + */ + public void removeBackgroundLayer() { + if (mBackgroundView != null) { + mBackgroundView = null; + } + + if (mViewHost != null) { + mViewHost.release(); + mViewHost = null; + } + + if (mLeash != null) { + mTransactionFactory.getTransaction().remove(mLeash).apply(); + mLeash = null; + } + } + + /** + * Gets {@link SurfaceControl} of the background layer. + * @return {@code null} if not exist. + */ + @Nullable + SurfaceControl getSurfaceControl() { + return mLeash; + } + + private int getThemeColor() { + final Context themedContext = new ContextThemeWrapper(mContext, + com.android.internal.R.style.Theme_DeviceDefault_DayNight); + return themedContext.getColor(R.color.one_handed_tutorial_background_color); + } + + int getThemeColorForBackground() { + final int origThemeColor = getThemeColor(); + return android.graphics.Color.argb(Color.alpha(origThemeColor), + Color.red(origThemeColor) - THEME_COLOR_OFFSET, + Color.green(origThemeColor) - THEME_COLOR_OFFSET, + Color.blue(origThemeColor) - THEME_COLOR_OFFSET); + } + + private float adjustColor(int origColor) { + return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f; + } + + void dump(@NonNull PrintWriter pw) { + final String innerPrefix = " "; + pw.println(TAG); + pw.print(innerPrefix + "mDisplayBounds="); + pw.println(mDisplayBounds); + pw.print(innerPrefix + "mViewHost="); + pw.println(mViewHost); + pw.print(innerPrefix + "mLeash="); + pw.println(mLeash); + pw.print(innerPrefix + "mBackgroundView="); + pw.println(mBackgroundView); + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java deleted file mode 100644 index 9e1c61aac868..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.onehanded; - -import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.view.ContextThemeWrapper; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.view.animation.LinearInterpolator; -import android.window.DisplayAreaAppearedInfo; -import android.window.DisplayAreaInfo; -import android.window.DisplayAreaOrganizer; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayLayout; - -import java.io.PrintWriter; -import java.util.List; -import java.util.concurrent.Executor; - -/** - * Manages OneHanded color background layer areas. - * To avoid when turning the Dark theme on, users can not clearly identify - * the screen has entered one handed mode. - */ -public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer - implements OneHandedAnimationCallback, OneHandedState.OnStateChangedListener { - private static final String TAG = "OneHandedBackgroundPanelOrganizer"; - private static final int THEME_COLOR_OFFSET = 10; - private static final int ALPHA_ANIMATION_DURATION = 200; - - private final Context mContext; - private final SurfaceSession mSurfaceSession = new SurfaceSession(); - private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory - mTransactionFactory; - - private @OneHandedState.State int mCurrentState; - private ValueAnimator mAlphaAnimator; - - private float mTranslationFraction; - private float[] mThemeColor; - - /** - * The background to distinguish the boundary of translated windows and empty region when - * one handed mode triggered. - */ - private Rect mBkgBounds; - private Rect mStableInsets; - - @Nullable - @VisibleForTesting - SurfaceControl mBackgroundSurface; - @Nullable - private SurfaceControl mParentLeash; - - public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout, - OneHandedSettingsUtil settingsUtil, Executor executor) { - super(executor); - mContext = context; - mTranslationFraction = settingsUtil.getTranslationFraction(context); - mTransactionFactory = SurfaceControl.Transaction::new; - updateThemeColors(); - } - - @Override - public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo, - @NonNull SurfaceControl leash) { - mParentLeash = leash; - } - - @Override - public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) { - final List<DisplayAreaAppearedInfo> displayAreaInfos; - displayAreaInfos = super.registerOrganizer(displayAreaFeature); - for (int i = 0; i < displayAreaInfos.size(); i++) { - final DisplayAreaAppearedInfo info = displayAreaInfos.get(i); - onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash()); - } - return displayAreaInfos; - } - - @Override - public void unregisterOrganizer() { - super.unregisterOrganizer(); - removeBackgroundPanelLayer(); - mParentLeash = null; - } - - @Override - public void onAnimationUpdate(SurfaceControl.Transaction tx, float xPos, float yPos) { - final int yTopPos = (mStableInsets.top - mBkgBounds.height()) + Math.round(yPos); - tx.setPosition(mBackgroundSurface, 0, yTopPos); - } - - @Nullable - @VisibleForTesting - boolean isRegistered() { - return mParentLeash != null; - } - - void createBackgroundSurface() { - mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession) - .setBufferSize(mBkgBounds.width(), mBkgBounds.height()) - .setColorLayer() - .setFormat(PixelFormat.RGB_888) - .setOpaque(true) - .setName("one-handed-background-panel") - .setCallsite("OneHandedBackgroundPanelOrganizer") - .build(); - - // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash. - mAlphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f); - mAlphaAnimator.setInterpolator(new LinearInterpolator()); - mAlphaAnimator.setDuration(ALPHA_ANIMATION_DURATION); - mAlphaAnimator.addUpdateListener( - animator -> detachBackgroundFromParent(animator)); - } - - void detachBackgroundFromParent(ValueAnimator animator) { - if (mBackgroundSurface == null || mParentLeash == null) { - return; - } - // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash. - final float currentValue = (float) animator.getAnimatedValue(); - final SurfaceControl.Transaction tx = mTransactionFactory.getTransaction(); - if (currentValue == 0.0f) { - tx.reparent(mBackgroundSurface, null).apply(); - } else { - tx.setAlpha(mBackgroundSurface, (float) animator.getAnimatedValue()).apply(); - } - } - - /** - * Called when onDisplayAdded() or onDisplayRemoved() callback. - * - * @param displayLayout The latest {@link DisplayLayout} representing current displayId - */ - public void onDisplayChanged(DisplayLayout displayLayout) { - mStableInsets = displayLayout.stableInsets(); - // Ensure the mBkgBounds is portrait, due to OHM only support on portrait - if (displayLayout.height() > displayLayout.width()) { - mBkgBounds = new Rect(0, 0, displayLayout.width(), - Math.round(displayLayout.height() * mTranslationFraction) + mStableInsets.top); - } else { - mBkgBounds = new Rect(0, 0, displayLayout.height(), - Math.round(displayLayout.width() * mTranslationFraction) + mStableInsets.top); - } - } - - @VisibleForTesting - void onStart() { - if (mBackgroundSurface == null) { - createBackgroundSurface(); - } - showBackgroundPanelLayer(); - } - - /** - * Called when transition finished. - */ - public void onStopFinished() { - if (mAlphaAnimator == null) { - return; - } - mAlphaAnimator.start(); - } - - @VisibleForTesting - void showBackgroundPanelLayer() { - if (mParentLeash == null) { - return; - } - - if (mBackgroundSurface == null) { - createBackgroundSurface(); - } - - // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash. - if (mAlphaAnimator.isRunning()) { - mAlphaAnimator.end(); - } - - mTransactionFactory.getTransaction() - .reparent(mBackgroundSurface, mParentLeash) - .setAlpha(mBackgroundSurface, 1.0f) - .setLayer(mBackgroundSurface, -1 /* at bottom-most layer */) - .setColor(mBackgroundSurface, mThemeColor) - .show(mBackgroundSurface) - .apply(); - } - - @VisibleForTesting - void removeBackgroundPanelLayer() { - if (mBackgroundSurface == null) { - return; - } - - mTransactionFactory.getTransaction() - .remove(mBackgroundSurface) - .apply(); - mBackgroundSurface = null; - } - - /** - * onConfigurationChanged events for updating tutorial text. - */ - public void onConfigurationChanged() { - updateThemeColors(); - - if (mCurrentState != STATE_ACTIVE) { - return; - } - showBackgroundPanelLayer(); - } - - private void updateThemeColors() { - final Context themedContext = new ContextThemeWrapper(mContext, - com.android.internal.R.style.Theme_DeviceDefault_DayNight); - final int themeColor = themedContext.getColor( - R.color.one_handed_tutorial_background_color); - mThemeColor = new float[]{ - adjustColor(Color.red(themeColor)), - adjustColor(Color.green(themeColor)), - adjustColor(Color.blue(themeColor))}; - } - - private float adjustColor(int origColor) { - return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f; - } - - @Override - public void onStateChanged(int newState) { - mCurrentState = newState; - } - - void dump(@NonNull PrintWriter pw) { - final String innerPrefix = " "; - pw.println(TAG); - pw.print(innerPrefix + "mBackgroundSurface="); - pw.println(mBackgroundSurface); - pw.print(innerPrefix + "mBkgBounds="); - pw.println(mBkgBounds); - pw.print(innerPrefix + "mThemeColor="); - pw.println(mThemeColor); - pw.print(innerPrefix + "mTranslationFraction="); - pw.println(mTranslationFraction); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 96f82fa5ce36..48acfc1c76e7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -99,7 +99,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, private OneHandedEventCallback mEventCallback; private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer; - private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; private OneHandedUiEventLogger mOneHandedUiEventLogger; private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener = @@ -163,7 +162,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, public void onStopFinished(Rect bounds) { mState.setState(STATE_NONE); notifyShortcutStateChanged(STATE_NONE); - mBackgroundPanelOrganizer.onStopFinished(); } }; @@ -201,32 +199,28 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context); OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor); OneHandedState oneHandedState = new OneHandedState(); + BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context); OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context, - settingsUtil, windowManager); + settingsUtil, windowManager, backgroundWindowManager); OneHandedAnimationController animationController = new OneHandedAnimationController(context); OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler, mainExecutor); - OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer = - new OneHandedBackgroundPanelOrganizer(context, displayLayout, settingsUtil, - mainExecutor); OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer( context, displayLayout, settingsUtil, animationController, tutorialHandler, - oneHandedBackgroundPanelOrganizer, jankMonitor, mainExecutor); + jankMonitor, mainExecutor); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); IOverlayManager overlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); - return new OneHandedController(context, displayController, - oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler, - settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, jankMonitor, - oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor, - mainHandler); + return new OneHandedController(context, displayController, organizer, touchHandler, + tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, + jankMonitor, oneHandedUiEventsLogger, overlayManager, taskStackListener, + mainExecutor, mainHandler); } @VisibleForTesting OneHandedController(Context context, DisplayController displayController, - OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer, OneHandedDisplayAreaOrganizer displayAreaOrganizer, OneHandedTouchHandler touchHandler, OneHandedTutorialHandler tutorialHandler, @@ -243,7 +237,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mContext = context; mOneHandedSettingsUtil = settingsUtil; mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil; - mBackgroundPanelOrganizer = backgroundPanelOrganizer; mDisplayAreaOrganizer = displayAreaOrganizer; mDisplayController = displayController; mTouchHandler = touchHandler; @@ -286,7 +279,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); - mState.addSListeners(mBackgroundPanelOrganizer); mState.addSListeners(mTutorialHandler); } @@ -368,7 +360,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction); mOneHandedAccessibilityUtil.announcementForScreenReader( mOneHandedAccessibilityUtil.getOneHandedStartDescription()); - mBackgroundPanelOrganizer.onStart(); mDisplayAreaOrganizer.scheduleOffset(0, yOffSet); mTimeoutHandler.resetTimer(); mOneHandedUiEventLogger.writeEvent( @@ -461,7 +452,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, } mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout); mTutorialHandler.onDisplayChanged(newDisplayLayout); - mBackgroundPanelOrganizer.onDisplayChanged(newDisplayLayout); } private ContentObserver getObserver(Runnable onChangeRunnable) { @@ -585,7 +575,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, if (!mIsOneHandedEnabled) { mDisplayAreaOrganizer.unregisterOrganizer(); - mBackgroundPanelOrganizer.unregisterOrganizer(); // Do NOT register + unRegister DA in the same call return; } @@ -594,11 +583,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mDisplayAreaOrganizer.registerOrganizer( OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED); } - - if (!mBackgroundPanelOrganizer.isRegistered()) { - mBackgroundPanelOrganizer.registerOrganizer( - OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL); - } } @VisibleForTesting @@ -613,13 +597,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, } private void onConfigChanged(Configuration newConfig) { - if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) { + if (mTutorialHandler == null) { return; } if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { return; } - mBackgroundPanelOrganizer.onConfigurationChanged(); mTutorialHandler.onConfigurationChanged(); } @@ -650,10 +633,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, pw.print(innerPrefix + "mIsSwipeToNotificationEnabled="); pw.println(mIsSwipeToNotificationEnabled); - if (mBackgroundPanelOrganizer != null) { - mBackgroundPanelOrganizer.dump(pw); - } - if (mDisplayAreaOrganizer != null) { mDisplayAreaOrganizer.dump(pw); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index 87eb40cbde62..f61d1b95bd85 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -80,7 +80,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mSurfaceControlTransactionFactory; private OneHandedTutorialHandler mTutorialHandler; private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>(); - private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; @VisibleForTesting OneHandedAnimationCallback mOneHandedAnimationCallback = @@ -135,7 +134,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { OneHandedSettingsUtil oneHandedSettingsUtil, OneHandedAnimationController animationController, OneHandedTutorialHandler tutorialHandler, - OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer, InteractionJankMonitor jankMonitor, ShellExecutor mainExecutor) { super(mainExecutor); @@ -150,7 +148,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION, animationDurationConfig); mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; - mBackgroundPanelOrganizer = oneHandedBackgroundGradientOrganizer; mTutorialHandler = tutorialHandler; } @@ -258,7 +255,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { animator.setTransitionDirection(direction) .addOneHandedAnimationCallback(mOneHandedAnimationCallback) .addOneHandedAnimationCallback(mTutorialHandler) - .addOneHandedAnimationCallback(mBackgroundPanelOrganizer) .setDuration(durationMs) .start(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java index 88f33755fa2d..04e8cf9d2c44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java @@ -32,7 +32,6 @@ import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.os.SystemProperties; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; @@ -65,6 +64,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, private final float mTutorialHeightRatio; private final WindowManager mWindowManager; + private final BackgroundWindowManager mBackgroundWindowManager; private @OneHandedState.State int mCurrentState; private int mTutorialAreaHeight; @@ -79,9 +79,10 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, private int mAlphaAnimationDurationMs; public OneHandedTutorialHandler(Context context, OneHandedSettingsUtil settingsUtil, - WindowManager windowManager) { + WindowManager windowManager, BackgroundWindowManager backgroundWindowManager) { mContext = context; mWindowManager = windowManager; + mBackgroundWindowManager = backgroundWindowManager; mTutorialHeightRatio = settingsUtil.getTranslationFraction(context); mAlphaAnimationDurationMs = settingsUtil.getTransitionDuration(context); } @@ -110,8 +111,19 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, } @Override + public void onStartFinished(Rect bounds) { + fillBackgroundColor(); + } + + @Override + public void onStopFinished(Rect bounds) { + removeBackgroundSurface(); + } + + @Override public void onStateChanged(int newState) { mCurrentState = newState; + mBackgroundWindowManager.onStateChanged(newState); switch (newState) { case STATE_ENTERING: createViewAndAttachToWindow(mContext); @@ -126,7 +138,6 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, case STATE_NONE: checkTransitionEnd(); removeTutorialFromWindowManager(); - break; default: break; } @@ -146,6 +157,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, } mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio); mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION; + mBackgroundWindowManager.onDisplayChanged(displayLayout); } @VisibleForTesting @@ -169,6 +181,7 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, private void attachTargetToWindow() { try { mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams()); + mBackgroundWindowManager.showBackgroundLayer(); } catch (IllegalStateException e) { // This shouldn't happen, but if the target is already added, just update its // layout params. @@ -186,6 +199,11 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, mTargetViewContainer = null; } + @VisibleForTesting + void removeBackgroundSurface() { + mBackgroundWindowManager.removeBackgroundLayer(); + } + /** * Returns layout params for the dismiss target, using the latest display metrics. */ @@ -213,9 +231,12 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, * onConfigurationChanged events for updating tutorial text. */ public void onConfigurationChanged() { + mBackgroundWindowManager.onConfigurationChanged(); + removeTutorialFromWindowManager(); if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { createViewAndAttachToWindow(mContext); + fillBackgroundColor(); updateThemeColor(); checkTransitionEnd(); } @@ -247,6 +268,14 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, tutorialDesc.setTextColor(themedTextColorSecondary); } + private void fillBackgroundColor() { + if (mTargetViewContainer == null || mBackgroundWindowManager == null) { + return; + } + mTargetViewContainer.setBackgroundColor( + mBackgroundWindowManager.getThemeColorForBackground()); + } + private void setupAlphaTransition(boolean isEntering) { final float start = isEntering ? 0.0f : 1.0f; final float end = isEntering ? 1.0f : 0.0f; @@ -282,5 +311,9 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, pw.println(mAlphaTransitionStart); pw.print(innerPrefix + "mAlphaAnimationDurationMs="); pw.println(mAlphaAnimationDurationMs); + + if (mBackgroundWindowManager != null) { + mBackgroundWindowManager.dump(pw); + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java new file mode 100644 index 000000000000..f3f70673b332 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.onehanded; + +import static com.google.common.truth.Truth.assertThat; + +import android.testing.TestableLooper; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for {@link BackgroundWindowManager} */ +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidJUnit4.class) +public class BackgroundWindowManagerTest extends ShellTestCase { + private BackgroundWindowManager mBackgroundWindowManager; + @Mock + private DisplayLayout mMockDisplayLayout; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mBackgroundWindowManager = new BackgroundWindowManager(mContext); + mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout); + } + + @Test + @UiThreadTest + public void testInitRelease() { + mBackgroundWindowManager.initView(); + assertThat(mBackgroundWindowManager.getSurfaceControl()).isNotNull(); + + mBackgroundWindowManager.removeBackgroundLayer(); + assertThat(mBackgroundWindowManager.getSurfaceControl()).isNull(); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java deleted file mode 100644 index 7b9553c5ef9b..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.onehanded; - -import static android.view.Display.DEFAULT_DISPLAY; -import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; - -import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; -import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.Display; -import android.view.SurfaceControl; -import android.window.DisplayAreaInfo; -import android.window.IWindowContainerToken; -import android.window.WindowContainerToken; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.DisplayLayout; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase { - private DisplayAreaInfo mDisplayAreaInfo; - private Display mDisplay; - private DisplayLayout mDisplayLayout; - private OneHandedBackgroundPanelOrganizer mSpiedBackgroundPanelOrganizer; - private WindowContainerToken mToken; - private SurfaceControl mLeash; - - @Mock - IWindowContainerToken mMockRealToken; - @Mock - DisplayController mMockDisplayController; - @Mock - OneHandedSettingsUtil mMockSettingsUtil; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mToken = new WindowContainerToken(mMockRealToken); - mLeash = new SurfaceControl(); - mDisplay = mContext.getDisplay(); - mDisplayLayout = new DisplayLayout(mContext, mDisplay); - when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); - mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, - FEATURE_ONE_HANDED_BACKGROUND_PANEL); - - mSpiedBackgroundPanelOrganizer = spy( - new OneHandedBackgroundPanelOrganizer(mContext, mDisplayLayout, mMockSettingsUtil, - Runnable::run)); - mSpiedBackgroundPanelOrganizer.onDisplayChanged(mDisplayLayout); - } - - @Test - public void testOnDisplayAreaAppeared() { - mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); - - assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isTrue(); - verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer(); - } - - @Test - public void testShowBackgroundLayer() { - mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, null); - mSpiedBackgroundPanelOrganizer.onStart(); - - verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer(); - } - - @Test - public void testRemoveBackgroundLayer() { - mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); - - assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isNotNull(); - - reset(mSpiedBackgroundPanelOrganizer); - mSpiedBackgroundPanelOrganizer.removeBackgroundPanelLayer(); - - assertThat(mSpiedBackgroundPanelOrganizer.mBackgroundSurface).isNull(); - } - - @Test - public void testStateNone_onConfigurationChanged() { - mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_NONE); - mSpiedBackgroundPanelOrganizer.onConfigurationChanged(); - - verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer(); - } - - @Test - public void testStateActivate_onConfigurationChanged() { - mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_ACTIVE); - mSpiedBackgroundPanelOrganizer.onConfigurationChanged(); - - verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer(); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 636e875bed7e..2886b97a3020 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -73,8 +73,6 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock DisplayController mMockDisplayController; @Mock - OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; - @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock OneHandedEventCallback mMockEventCallback; @@ -115,7 +113,6 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(null); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true); - when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true); when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn( mDefaultEnabled); when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn( @@ -134,7 +131,6 @@ public class OneHandedControllerTest extends OneHandedTestCase { mSpiedOneHandedController = spy(new OneHandedController( mContext, mMockDisplayController, - mMockBackgroundOrganizer, mMockDisplayAreaOrganizer, mMockTouchHandler, mMockTutorialHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index df21163c68cd..9c7f7237871a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -95,8 +95,6 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { @Mock WindowContainerTransaction mMockWindowContainerTransaction; @Mock - OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; - @Mock ShellExecutor mMockShellMainExecutor; @Mock OneHandedSettingsUtil mMockSettingsUitl; @@ -143,7 +141,6 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockSettingsUitl, mMockAnimationController, mTutorialHandler, - mMockBackgroundOrganizer, mJankMonitor, mMockShellMainExecutor)); @@ -431,7 +428,6 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockSettingsUitl, mMockAnimationController, mTutorialHandler, - mMockBackgroundOrganizer, mJankMonitor, mMockShellMainExecutor)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java index 58399b6444fa..dba1b8b86261 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java @@ -67,8 +67,6 @@ public class OneHandedStateTest extends OneHandedTestCase { @Mock DisplayController mMockDisplayController; @Mock - OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer; - @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock OneHandedTouchHandler mMockTouchHandler; @@ -105,7 +103,6 @@ public class OneHandedStateTest extends OneHandedTestCase { when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); - when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true); when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn( mDefaultEnabled); when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn( @@ -123,7 +120,6 @@ public class OneHandedStateTest extends OneHandedTestCase { mSpiedOneHandedController = spy(new OneHandedController( mContext, mMockDisplayController, - mMockBackgroundOrganizer, mMockDisplayAreaOrganizer, mMockTouchHandler, mMockTutorialHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java index b1434ca325b7..63d8bfd1e7ef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java @@ -56,6 +56,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { OneHandedSettingsUtil mMockSettingsUtil; @Mock WindowManager mMockWindowManager; + @Mock + BackgroundWindowManager mMockBackgroundWindowManager; @Before public void setUp() { @@ -63,10 +65,11 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { when(mMockSettingsUtil.getTutorialShownCounts(any(), anyInt())).thenReturn(0); mDisplay = mContext.getDisplay(); - mDisplayLayout = new DisplayLayout(mContext, mDisplay); + mDisplayLayout = new DisplayLayout(getTestContext().getApplicationContext(), mDisplay); mSpiedTransitionState = spy(new OneHandedState()); mSpiedTutorialHandler = spy( - new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager)); + new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager, + mMockBackgroundWindowManager)); mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor); } diff --git a/media/OWNERS b/media/OWNERS index 0aff43e00671..5f501372666b 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -3,7 +3,6 @@ elaurent@google.com essick@google.com etalvala@google.com hdmoon@google.com -hkuang@google.com hunga@google.com insun@google.com jaewan@google.com diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index ad86002f862d..f3670e6002f5 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -764,6 +764,9 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onVideoAvailable(mSession); + if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) { + mSession.getIAppSession().notifyVideoAvailable(); + } } }); } @@ -773,6 +776,9 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onVideoUnavailable(mSession, reason); + if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) { + mSession.getIAppSession().notifyVideoUnavailable(reason); + } } }); } @@ -782,6 +788,9 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onContentAllowed(mSession); + if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) { + mSession.getIAppSession().notifyContentAllowed(); + } } }); } @@ -791,6 +800,9 @@ public final class TvInputManager { @Override public void run() { mSessionCallback.onContentBlocked(mSession, rating); + if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) { + mSession.getIAppSession().notifyContentBlocked(rating); + } } }); } diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl index 23201faa1c5a..2e0435927ae8 100644 --- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl @@ -51,6 +51,10 @@ interface ITvIAppManager { void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId); void notifyTrackSelected(in IBinder sessionToken, int type, in String trackId, int userId); void notifyTracksChanged(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId); + void notifyVideoAvailable(in IBinder sessionToken, int userId); + void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId); + void notifyContentAllowed(in IBinder sessionToken, int userId); + void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId); void setSurface(in IBinder sessionToken, in Surface surface, int userId); void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height, int userId); diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl index 52f9a874aca2..2788ff65e301 100644 --- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl @@ -42,6 +42,10 @@ oneway interface ITvIAppSession { void notifyTuned(in Uri channelUri); void notifyTrackSelected(int type, in String trackId); void notifyTracksChanged(in List<TvTrackInfo> tracks); + void notifyVideoAvailable(); + void notifyVideoUnavailable(int reason); + void notifyContentAllowed(); + void notifyContentBlocked(in String rating); void setSurface(in Surface surface); void dispatchSurfaceChanged(int format, int width, int height); void notifyBroadcastInfoResponse(in BroadcastInfoResponse response); diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java index d1fd1df74e1b..9685e3af8c53 100644 --- a/media/java/android/media/tv/interactive/TvIAppManager.java +++ b/media/java/android/media/tv/interactive/TvIAppManager.java @@ -26,6 +26,7 @@ import android.media.tv.AdRequest; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; +import android.media.tv.TvContentRating; import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; import android.net.Uri; @@ -1037,6 +1038,66 @@ public final class TvIAppManager { } } + /** + * Notifies IAPP session when video is available. + */ + public void notifyVideoAvailable() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyVideoAvailable(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notifies IAPP session when video is unavailable. + */ + public void notifyVideoUnavailable(int reason) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyVideoUnavailable(mToken, reason, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notifies IAPP session when content is allowed. + */ + public void notifyContentAllowed() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyContentAllowed(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Notifies IAPP session when content is blocked. + */ + public void notifyContentBlocked(TvContentRating rating) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyContentBlocked(mToken, rating.flattenToString(), mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void flushPendingEventsLocked() { mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java index 04560415ed37..4993bc31768c 100644 --- a/media/java/android/media/tv/interactive/TvIAppService.java +++ b/media/java/android/media/tv/interactive/TvIAppService.java @@ -31,6 +31,7 @@ import android.media.tv.AdRequest; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; +import android.media.tv.TvContentRating; import android.media.tv.TvTrackInfo; import android.net.Uri; import android.os.AsyncTask; @@ -442,6 +443,34 @@ public abstract class TvIAppService extends Service { } /** + * Called when video is available. + * @hide + */ + public void onVideoAvailable() { + } + + /** + * Called when video is unavailable. + * @hide + */ + public void onVideoUnavailable(int reason) { + } + + /** + * Called when content is allowed. + * @hide + */ + public void onContentAllowed() { + } + + /** + * Called when content is blocked. + * @hide + */ + public void onContentBlocked(TvContentRating rating) { + } + + /** * Called when a broadcast info response is received. * @hide */ @@ -816,6 +845,33 @@ public abstract class TvIAppService extends Service { onTracksChanged(tracks); } + void notifyVideoAvailable() { + if (DEBUG) { + Log.d(TAG, "notifyVideoAvailable"); + } + onVideoAvailable(); + } + + void notifyVideoUnavailable(int reason) { + if (DEBUG) { + Log.d(TAG, "notifyVideoAvailable (reason=" + reason + ")"); + } + onVideoUnavailable(reason); + } + + void notifyContentAllowed() { + if (DEBUG) { + Log.d(TAG, "notifyContentAllowed"); + } + notifyContentAllowed(); + } + + void notifyContentBlocked(TvContentRating rating) { + if (DEBUG) { + Log.d(TAG, "notifyContentBlocked (rating=" + rating.flattenToString() + ")"); + } + onContentBlocked(rating); + } /** * Calls {@link #onBroadcastInfoResponse}. @@ -1182,6 +1238,26 @@ public abstract class TvIAppService extends Service { } @Override + public void notifyVideoAvailable() { + mSessionImpl.notifyVideoAvailable(); + } + + @Override + public void notifyVideoUnavailable(int reason) { + mSessionImpl.notifyVideoUnavailable(reason); + } + + @Override + public void notifyContentAllowed() { + mSessionImpl.notifyContentAllowed(); + } + + @Override + public void notifyContentBlocked(String rating) { + mSessionImpl.notifyContentBlocked(TvContentRating.unflattenFromString(rating)); + } + + @Override public void setSurface(Surface surface) { mSessionImpl.setSurface(surface); } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 9572e1806643..d9feaf4013a7 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -461,6 +461,7 @@ MediaEvent::MediaEvent(sp<FilterClient> filterClient, native_handle_t *avHandle, } MediaEvent::~MediaEvent() { + android::Mutex::Autolock autoLock(mLock); JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mMediaEventObj); mMediaEventObj = nullptr; diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp index 8568383385ac..959e756b8a16 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -43,6 +43,7 @@ FilterClient::FilterClient(DemuxFilterType type, shared_ptr<ITunerFilter> tunerF } FilterClient::~FilterClient() { + Mutex::Autolock _l(mLock); mTunerFilter = nullptr; mAvSharedHandle = nullptr; mAvSharedMemSize = 0; @@ -74,6 +75,7 @@ Result FilterClient::configure(DemuxFilterSettings configure) { Result res; checkIsPassthroughFilter(configure); + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->configure(configure); res = ClientHelper::getServiceSpecificErrorCode(s); @@ -87,6 +89,7 @@ Result FilterClient::configure(DemuxFilterSettings configure) { } Result FilterClient::configureMonitorEvent(int32_t monitorEventType) { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->configureMonitorEvent(monitorEventType); return ClientHelper::getServiceSpecificErrorCode(s); @@ -96,6 +99,7 @@ Result FilterClient::configureMonitorEvent(int32_t monitorEventType) { } Result FilterClient::configureIpFilterContextId(int32_t cid) { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->configureIpFilterContextId(cid); return ClientHelper::getServiceSpecificErrorCode(s); @@ -105,6 +109,7 @@ Result FilterClient::configureIpFilterContextId(int32_t cid) { } Result FilterClient::configureAvStreamType(AvStreamType avStreamType) { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->configureAvStreamType(avStreamType); return ClientHelper::getServiceSpecificErrorCode(s); @@ -114,6 +119,7 @@ Result FilterClient::configureAvStreamType(AvStreamType avStreamType) { } Result FilterClient::start() { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->start(); return ClientHelper::getServiceSpecificErrorCode(s); @@ -123,6 +129,7 @@ Result FilterClient::start() { } Result FilterClient::stop() { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->stop(); return ClientHelper::getServiceSpecificErrorCode(s); @@ -132,6 +139,7 @@ Result FilterClient::stop() { } Result FilterClient::flush() { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->flush(); return ClientHelper::getServiceSpecificErrorCode(s); @@ -141,6 +149,7 @@ Result FilterClient::flush() { } Result FilterClient::getId(int32_t& id) { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->getId(&id); return ClientHelper::getServiceSpecificErrorCode(s); @@ -150,6 +159,7 @@ Result FilterClient::getId(int32_t& id) { } Result FilterClient::getId64Bit(int64_t& id) { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->getId64Bit(&id); return ClientHelper::getServiceSpecificErrorCode(s); @@ -159,6 +169,7 @@ Result FilterClient::getId64Bit(int64_t& id) { } Result FilterClient::releaseAvHandle(native_handle_t* handle, uint64_t avDataId) { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->releaseAvHandle(dupToAidl(handle), avDataId); return ClientHelper::getServiceSpecificErrorCode(s); @@ -168,6 +179,7 @@ Result FilterClient::releaseAvHandle(native_handle_t* handle, uint64_t avDataId) } Result FilterClient::setDataSource(sp<FilterClient> filterClient){ + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->setDataSource(filterClient->getAidlFilter()); return ClientHelper::getServiceSpecificErrorCode(s); @@ -177,6 +189,7 @@ Result FilterClient::setDataSource(sp<FilterClient> filterClient){ } Result FilterClient::close() { + Mutex::Autolock _l(mLock); if (mFilterMQEventFlag != nullptr) { EventFlag::deleteEventFlag(&mFilterMQEventFlag); mFilterMQEventFlag = nullptr; @@ -197,6 +210,7 @@ Result FilterClient::close() { } string FilterClient::acquireSharedFilterToken() { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { string filterToken; if (mTunerFilter->acquireSharedFilterToken(&filterToken).isOk()) { @@ -208,6 +222,7 @@ string FilterClient::acquireSharedFilterToken() { } Result FilterClient::freeSharedFilterToken(const string& filterToken) { + Mutex::Autolock _l(mLock); if (mTunerFilter != nullptr) { Status s = mTunerFilter->freeSharedFilterToken(filterToken); return ClientHelper::getServiceSpecificErrorCode(s); @@ -237,6 +252,7 @@ Status TunerFilterCallback::onFilterEvent(const vector<DemuxFilterEvent>& filter } Result FilterClient::getFilterMq() { + Mutex::Autolock _l(mLock); if (mFilterMQ != nullptr) { return Result::SUCCESS; } diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h index 20e56102a909..9e9b2332ac33 100644 --- a/media/jni/tuner/FilterClient.h +++ b/media/jni/tuner/FilterClient.h @@ -21,6 +21,7 @@ #include <aidl/android/media/tv/tuner/BnTunerFilterCallback.h> #include <aidl/android/media/tv/tuner/ITunerFilter.h> #include <fmq/AidlMessageQueue.h> +#include <utils/Mutex.h> #include "ClientHelper.h" #include "FilterClientCallback.h" @@ -37,6 +38,7 @@ using ::aidl::android::hardware::tv::tuner::FilterDelayHint; using ::aidl::android::media::tv::tuner::BnTunerFilterCallback; using ::aidl::android::media::tv::tuner::ITunerFilter; using ::android::hardware::EventFlag; +using ::android::Mutex; using namespace std; @@ -179,6 +181,7 @@ private: uint64_t mAvSharedMemSize; bool mIsMediaFilter; bool mIsPassthroughFilter; + Mutex mLock; }; } // namespace android diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml index c20690342c19..3f2b8ac7609d 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml @@ -43,4 +43,8 @@ <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color> <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color> + + <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_200</color> + + <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml index 04010985fe74..ec3c336eba46 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml @@ -65,4 +65,8 @@ <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color> <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color> + + <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color> + + <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml index 1c33f1a57ea5..11546c8ed3d9 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml @@ -20,4 +20,9 @@ <dimen name="app_icon_min_width">52dp</dimen> <dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen> <dimen name="settingslib_dialogCornerRadius">28dp</dimen> + + <!-- Left padding of the preference --> + <dimen name="settingslib_listPreferredItemPaddingStart">24dp</dimen> + <!-- Right padding of the preference --> + <dimen name="settingslib_listPreferredItemPaddingEnd">24dp</dimen> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml index 58006369988e..8e7226b0eb53 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml @@ -17,13 +17,19 @@ <resources> <style name="TextAppearance.PreferenceTitle.SettingsLib" parent="@android:style/TextAppearance.Material.Subhead"> + <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item> <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> <item name="android:textSize">20sp</item> </style> + <style name="TextAppearance.PreferenceSummary.SettingsLib" + parent="@android:style/TextAppearance.DeviceDefault.Small"> + <item name="android:textColor">@color/settingslib_text_color_secondary_device_default</item> + </style> + <style name="TextAppearance.CategoryTitle.SettingsLib" parent="@android:style/TextAppearance.DeviceDefault.Medium"> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">@color/settingslib_text_color_preference_category_title</item> <item name="android:textSize">14sp</item> </style> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml index 6bf288b74d5a..7bf75bcc8a53 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml @@ -19,8 +19,8 @@ <!-- Only using in Settings application --> <style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings" > <item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle.SettingsLib</item> - <item name="android:listPreferredItemPaddingStart">24dp</item> - <item name="android:listPreferredItemPaddingLeft">24dp</item> + <item name="android:listPreferredItemPaddingStart">@dimen/settingslib_listPreferredItemPaddingStart</item> + <item name="android:listPreferredItemPaddingLeft">@dimen/settingslib_listPreferredItemPaddingStart</item> <item name="android:listPreferredItemPaddingEnd">16dp</item> <item name="android:listPreferredItemPaddingRight">16dp</item> <item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item> diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml new file mode 100644 index 000000000000..e73dcc0cc559 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <bool name="settingslib_config_icon_space_reserved">true</bool> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml index 18af1f9c15d0..f7e01444f4d4 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml @@ -21,4 +21,11 @@ <dimen name="app_icon_min_width">56dp</dimen> <dimen name="two_target_min_width">72dp</dimen> <dimen name="settingslib_dialogCornerRadius">8dp</dimen> + + <!-- Left padding of the preference --> + <dimen name="settingslib_listPreferredItemPaddingStart">16dp</dimen> + <!-- Right padding of the preference --> + <dimen name="settingslib_listPreferredItemPaddingEnd">16dp</dimen> + <!-- Icon size of the preference --> + <dimen name="settingslib_preferenceIconSize">24dp</dimen> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml new file mode 100644 index 000000000000..aaab0f041fe3 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<resources> + <style name="TextAppearance.PreferenceTitle.SettingsLib" + parent="@android:style/TextAppearance.Material.Subhead"> + </style> + + <style name="TextAppearance.PreferenceSummary.SettingsLib" + parent="@style/PreferenceSummaryTextStyle"> + </style> + + <style name="TextAppearance.CategoryTitle.SettingsLib" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + </style> +</resources> diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java index de45ea536e27..d3fe4a7fcb9f 100644 --- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java +++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java @@ -47,7 +47,7 @@ import javax.tools.Diagnostic.Kind; * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources} * subclasses. */ -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_9) @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"}) public class IndexableProcessor extends AbstractProcessor { diff --git a/packages/SystemUI/res-keyguard/values/bools.xml b/packages/SystemUI/res-keyguard/values/bools.xml index 2b83787172d3..c5bf4ce48188 100644 --- a/packages/SystemUI/res-keyguard/values/bools.xml +++ b/packages/SystemUI/res-keyguard/values/bools.xml @@ -17,4 +17,5 @@ <resources> <bool name="kg_show_ime_at_screen_on">true</bool> <bool name="kg_use_all_caps">true</bool> + <bool name="flag_active_unlock">false</bool> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 7b8f349777a6..56517ccb873f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -294,6 +294,7 @@ <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> + <item>com.android.keyguard.KeyguardBiometricLockoutLogger</item> <item>com.android.systemui.recents.Recents</item> <item>com.android.systemui.volume.VolumeUI</item> <item>com.android.systemui.statusbar.phone.StatusBar</item> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2916c1c9b357..7600eb1ad1d0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1165,7 +1165,7 @@ <string name="wallet_lockscreen_settings_label">Lock screen settings</string> <!-- QR Code Scanner label, title [CHAR LIMIT=32] --> - <string name="qr_code_scanner_title">QR Code</string> + <string name="qr_code_scanner_title">QR code</string> <!-- QR Code Scanner description [CHAR LIMIT=NONE] --> <string name="qr_code_scanner_description">Tap to scan</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java index 98083742d707..1d2caf9ab545 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java @@ -28,6 +28,8 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; +import androidx.annotation.VisibleForTesting; + import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -60,6 +62,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, private final Rect mRegisteredSamplingBounds = new Rect(); private final SamplingCallback mCallback; private final Executor mBackgroundExecutor; + private final SysuiCompositionSamplingListener mCompositionSamplingListener; private boolean mSamplingEnabled = false; private boolean mSamplingListenerRegistered = false; @@ -91,9 +94,17 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback, Executor backgroundExecutor) { + this(sampledView, samplingCallback, sampledView.getContext().getMainExecutor(), + backgroundExecutor, new SysuiCompositionSamplingListener()); + } + + @VisibleForTesting + RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback, + Executor mainExecutor, Executor backgroundExecutor, + SysuiCompositionSamplingListener compositionSamplingListener) { mBackgroundExecutor = backgroundExecutor; - mSamplingListener = new CompositionSamplingListener( - sampledView.getContext().getMainExecutor()) { + mCompositionSamplingListener = compositionSamplingListener; + mSamplingListener = new CompositionSamplingListener(mainExecutor) { @Override public void onSampleCollected(float medianLuma) { if (mSamplingEnabled) { @@ -136,7 +147,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, public void stopAndDestroy() { stop(); - mSamplingListener.destroy(); + mBackgroundExecutor.execute(mSamplingListener::destroy); mIsDestroyed = true; } @@ -189,13 +200,12 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, // We only want to re-register if something actually changed unregisterSamplingListener(); mSamplingListenerRegistered = true; - SurfaceControl wrappedStopLayer = stopLayerControl == null - ? null : new SurfaceControl(stopLayerControl, "regionSampling"); + SurfaceControl wrappedStopLayer = wrap(stopLayerControl); mBackgroundExecutor.execute(() -> { if (wrappedStopLayer != null && !wrappedStopLayer.isValid()) { return; } - CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY, + mCompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY, wrappedStopLayer, mSamplingRequestBounds); }); mRegisteredSamplingBounds.set(mSamplingRequestBounds); @@ -208,14 +218,21 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, } } + @VisibleForTesting + protected SurfaceControl wrap(SurfaceControl stopLayerControl) { + return stopLayerControl == null ? null : new SurfaceControl(stopLayerControl, + "regionSampling"); + } + private void unregisterSamplingListener() { if (mSamplingListenerRegistered) { mSamplingListenerRegistered = false; SurfaceControl wrappedStopLayer = mWrappedStopLayer; mRegisteredStopLayer = null; + mWrappedStopLayer = null; mRegisteredSamplingBounds.setEmpty(); mBackgroundExecutor.execute(() -> { - CompositionSamplingListener.unregister(mSamplingListener); + mCompositionSamplingListener.unregister(mSamplingListener); if (wrappedStopLayer != null && wrappedStopLayer.isValid()) { wrappedStopLayer.release(); } @@ -299,4 +316,19 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, return true; } } + + @VisibleForTesting + public static class SysuiCompositionSamplingListener { + public void register(CompositionSamplingListener listener, + int displayId, SurfaceControl stopLayer, Rect samplingArea) { + CompositionSamplingListener.register(listener, displayId, stopLayer, samplingArea); + } + + /** + * Unregisters a sampling listener. + */ + public void unregister(CompositionSamplingListener listener) { + CompositionSamplingListener.unregister(listener); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt new file mode 100644 index 000000000000..214b284ac4b9 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard + +import android.content.Context +import android.hardware.biometrics.BiometricSourceType +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE +import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Logs events when primary authentication requirements change. Primary authentication is considered + * authentication using pin/pattern/password input. + * + * See [PrimaryAuthRequiredEvent] for all the events and their descriptions. + */ +@SysUISingleton +class KeyguardBiometricLockoutLogger @Inject constructor( + context: Context?, + private val uiEventLogger: UiEventLogger, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val dumpManager: DumpManager +) : CoreStartable(context) { + private var fingerprintLockedOut = false + private var faceLockedOut = false + private var encryptedOrLockdown = false + private var unattendedUpdate = false + private var timeout = false + + override fun start() { + dumpManager.registerDumpable(this) + mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged( + KeyguardUpdateMonitor.getCurrentUser()) + keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback) + } + + private val mKeyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback = + object : KeyguardUpdateMonitorCallback() { + override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT) { + val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut + if (lockedOut && !fingerprintLockedOut) { + uiEventLogger.log( + PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT) + } else if (!lockedOut && fingerprintLockedOut) { + uiEventLogger.log( + PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET) + } + fingerprintLockedOut = lockedOut + } else if (biometricSourceType == BiometricSourceType.FACE) { + val lockedOut = keyguardUpdateMonitor.isFaceLockedOut + if (lockedOut && !faceLockedOut) { + uiEventLogger.log( + PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT) + } else if (!lockedOut && faceLockedOut) { + uiEventLogger.log( + PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET) + } + faceLockedOut = lockedOut + } + } + + override fun onStrongAuthStateChanged(userId: Int) { + if (userId != KeyguardUpdateMonitor.getCurrentUser()) { + return + } + val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker + .getStrongAuthForUser(userId) + + val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId) + if (newEncryptedOrLockdown && !encryptedOrLockdown) { + uiEventLogger.log( + PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN) + } + encryptedOrLockdown = newEncryptedOrLockdown + + val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags) + if (newUnattendedUpdate && !unattendedUpdate) { + uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + } + unattendedUpdate = newUnattendedUpdate + + val newTimeout = isStrongAuthTimeout(strongAuthFlags) + if (newTimeout && !timeout) { + uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT) + } + timeout = newTimeout + } + } + + private fun isUnattendedUpdate( + @LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int + ) = containsFlag(flags, STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) + + private fun isStrongAuthTimeout( + @LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int + ) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) || + containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { + pw.println(" mFingerprintLockedOut=$fingerprintLockedOut") + pw.println(" mFaceLockedOut=$faceLockedOut") + pw.println(" mIsEncryptedOrLockdown=$encryptedOrLockdown") + pw.println(" mIsUnattendedUpdate=$unattendedUpdate") + pw.println(" mIsTimeout=$timeout") + } + + /** + * Events pertaining to whether primary authentication (pin/pattern/password input) is required + * for device entry. + */ + @VisibleForTesting + enum class PrimaryAuthRequiredEvent(private val mId: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "Fingerprint cannot be used to authenticate for device entry. This" + + "can persist until the next primary auth or may timeout.") + PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT(924), + + @UiEvent(doc = "Fingerprint can be used to authenticate for device entry.") + PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET(925), + + @UiEvent(doc = "Face cannot be used to authenticate for device entry.") + PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT(926), + + @UiEvent(doc = "Face can be used to authenticate for device entry.") + PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET(927), + + @UiEvent(doc = "Device is encrypted (ie: after reboot) or device is locked down by DPM " + + "or a manual user lockdown.") + PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN(928), + + @UiEvent(doc = "Primary authentication is required because it hasn't been used for a " + + "time required by a device admin or because primary auth hasn't been used for a " + + "time after a non-strong biometric (weak or convenience) is used to unlock the " + + "device.") + PRIMARY_AUTH_REQUIRED_TIMEOUT(929), + + @UiEvent(doc = "Strong authentication is required to prepare for unattended upgrade.") + PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE(931); + + override fun getId(): Int { + return mId + } + } + + companion object { + private fun containsFlag(strongAuthFlags: Int, flagCheck: Int): Boolean { + return strongAuthFlags and flagCheck != 0 + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index 03f04d3a2cde..36fe5ba1a851 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -64,3 +64,19 @@ data class KeyguardFaceListenModel( val secureCameraLaunched: Boolean, val switchingUser: Boolean ) : KeyguardListenModel() +/** + * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock]. + */ +data class KeyguardActiveUnlockModel( + @CurrentTimeMillisLong override val timeMillis: Long, + override val userId: Int, + override val listening: Boolean, + // keep sorted + val authInterruptActive: Boolean, + val encryptedOrTimedOut: Boolean, + val fpLockout: Boolean, + val lockDown: Boolean, + val switchingUser: Boolean, + val triggerActiveUnlockForAssistant: Boolean, + val userCanDismissLockScreen: Boolean +) : KeyguardListenModel() diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt index f13a59a84811..210f5e763911 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt @@ -32,15 +32,17 @@ class KeyguardListenQueue( ) { private val faceQueue = ArrayDeque<KeyguardFaceListenModel>() private val fingerprintQueue = ArrayDeque<KeyguardFingerprintListenModel>() + private val activeUnlockQueue = ArrayDeque<KeyguardActiveUnlockModel>() @get:VisibleForTesting val models: List<KeyguardListenModel> - get() = faceQueue + fingerprintQueue + get() = faceQueue + fingerprintQueue + activeUnlockQueue /** Push a [model] to the queue (will be logged until the queue exceeds [sizePerModality]). */ fun add(model: KeyguardListenModel) { val queue = when (model) { is KeyguardFaceListenModel -> faceQueue.apply { add(model) } is KeyguardFingerprintListenModel -> fingerprintQueue.apply { add(model) } + is KeyguardActiveUnlockModel -> activeUnlockQueue.apply { add(model) } } if (queue.size > sizePerModality) { @@ -63,5 +65,9 @@ class KeyguardListenQueue( for (model in fingerprintQueue) { writer.println(stringify(model)) } + writer.println(" Active unlock triggers (last ${activeUnlockQueue.size} calls):") + for (model in activeUnlockQueue) { + writer.println(stringify(model)) + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 98721fd78a93..5276679ea104 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -37,6 +37,8 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.UserSwitchObserver; import android.app.admin.DevicePolicyManager; @@ -102,6 +104,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -109,6 +113,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; +import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.RingerModeTracker; import com.google.android.collect.Lists; @@ -143,8 +148,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES; private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE; private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE; private static final boolean DEBUG_SPEW = false; private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600; + private int mNumActiveUnlockTriggers = 0; private static final String ACTION_FACE_UNLOCK_STARTED = "com.android.facelock.FACE_UNLOCK_STARTED"; @@ -183,7 +190,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_USER_STOPPED = 340; private static final int MSG_USER_REMOVED = 341; private static final int MSG_KEYGUARD_GOING_AWAY = 342; - private static final int MSG_LOCK_SCREEN_MODE = 343; private static final int MSG_TIME_FORMAT_UPDATE = 344; private static final int MSG_REQUIRE_NFC_UNLOCK = 345; @@ -221,7 +227,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1; public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2; - private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000; /** * If no cancel signal has been received after this amount of time, set the biometric running * state to stopped to allow Keyguard to retry authentication. @@ -231,7 +236,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName( "com.android.settings", "com.android.settings.FallbackHome"); - /** * If true, the system is in the half-boot-to-decryption-screen state. * Prudently disable lockscreen. @@ -334,6 +338,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final Executor mBackgroundExecutor; private SensorPrivacyManager mSensorPrivacyManager; + private FeatureFlags mFeatureFlags; private int mFaceAuthUserId; /** @@ -1250,7 +1255,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); } - private boolean isEncryptedOrLockdown(int userId) { + /** + * Returns true if primary authentication is required for the given user due to lockdown + * or encryption after reboot. + */ + public boolean isEncryptedOrLockdown(int userId) { final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId); final boolean isLockDown = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) @@ -1311,6 +1320,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab void setAssistantVisible(boolean assistantVisible) { mAssistantVisible = assistantVisible; updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); + if (mAssistantVisible) { + requestActiveUnlock(); + } } static class DisplayClientState { @@ -1650,6 +1662,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp"); Assert.isMainThread(); updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); + requestActiveUnlock(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1777,7 +1790,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab AuthController authController, TelephonyListenerManager telephonyListenerManager, InteractionJankMonitor interactionJankMonitor, - LatencyTracker latencyTracker) { + LatencyTracker latencyTracker, + FeatureFlags featureFlags) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); mTelephonyListenerManager = telephonyListenerManager; @@ -1795,6 +1809,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); + mFeatureFlags = featureFlags; mHandler = new Handler(mainLooper) { @Override @@ -2180,6 +2195,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mAuthInterruptActive = active; updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); + requestActiveUnlock(); } /** @@ -2228,6 +2244,97 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + /** + * Attempts to trigger active unlock. + */ + public void requestActiveUnlock() { + // If this message exists, FP has already authenticated, so wait until that is handled + if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) { + return; + } + + if (shouldTriggerActiveUnlock() && mFeatureFlags.isEnabled(Flags.ACTIVE_UNLOCK)) { + // TODO (b/192405661): call new TrustManager API + mNumActiveUnlockTriggers++; + Log.d("ActiveUnlock", "would have triggered times=" + mNumActiveUnlockTriggers); + showActiveUnlockNotification(mNumActiveUnlockTriggers); + } + } + + /** + * TODO (b/192405661): Only for testing. Remove before release. + */ + private void showActiveUnlockNotification(int times) { + final String message = "Active unlock triggered " + times + " times."; + final Notification.Builder nb = + new Notification.Builder(mContext, NotificationChannels.GENERAL) + .setSmallIcon(R.drawable.ic_volume_ringer) + .setContentTitle(message) + .setStyle(new Notification.BigTextStyle().bigText(message)); + mContext.getSystemService(NotificationManager.class).notifyAsUser( + "active_unlock", + 0, + nb.build(), + UserHandle.ALL); + } + + private boolean shouldTriggerActiveUnlock() { + // TODO: check if active unlock is ENABLED / AVAILABLE + + // Triggers: + final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant(); + final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep + && mStatusBarState != StatusBarState.SHADE_LOCKED; + + // Gates: + final int user = getCurrentUser(); + + // No need to trigger active unlock if we're already unlocked or don't have + // pin/pattern/password setup + final boolean userCanDismissLockScreen = getUserCanSkipBouncer(user) + || !mLockPatternUtils.isSecure(user); + + // Don't trigger active unlock if fp is locked out TODO: confirm this one + final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent; + + // Don't trigger active unlock if primary auth is required + final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); + final boolean isLockDown = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) + || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + final boolean isEncryptedOrTimedOut = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT) + || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); + + final boolean shouldTriggerActiveUnlock = + (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard) + && !mSwitchingUser + && !userCanDismissLockScreen + && !fpLockedout + && !isLockDown + && !isEncryptedOrTimedOut + && !mKeyguardGoingAway + && !mSecureCameraLaunched; + + // Aggregate relevant fields for debug logging. + if (DEBUG_ACTIVE_UNLOCK || DEBUG_SPEW) { + maybeLogListenerModelData( + new KeyguardActiveUnlockModel( + System.currentTimeMillis(), + user, + shouldTriggerActiveUnlock, + mAuthInterruptActive, + isEncryptedOrTimedOut, + fpLockedout, + isLockDown, + mSwitchingUser, + triggerActiveUnlockForAssistant, + userCanDismissLockScreen)); + } + + return shouldTriggerActiveUnlock; + } + private boolean shouldListenForFingerprintAssistant() { BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser()); return mAssistantVisible && mKeyguardOccluded @@ -2242,6 +2349,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mUserHasTrust.get(getCurrentUser(), false); } + private boolean shouldTriggerActiveUnlockForAssistant() { + return mAssistantVisible && mKeyguardOccluded + && !mUserHasTrust.get(getCurrentUser(), false); + } + @VisibleForTesting protected boolean shouldListenForFingerprint(boolean isUdfps) { final int user = getCurrentUser(); @@ -2406,6 +2518,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Log.v(TAG, model.toString()); } + if (DEBUG_ACTIVE_UNLOCK + && model instanceof KeyguardActiveUnlockModel + && model.getListening()) { + mListenModels.add(model); + return; + } + // Add model data to the historical buffer. final boolean notYetRunning = (DEBUG_FACE @@ -2514,6 +2633,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mFingerprintLockedOut || mFingerprintLockedOutPermanent; } + public boolean isFaceLockedOut() { + return mFaceLockedOutPermanent; + } + /** * If biometrics hardware is available, not disabled, and user has enrolled templates. * This does NOT check if the device is encrypted or in lockdown. diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 9dddbb1d67c6..c0da57f58043 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -16,6 +16,7 @@ package com.android.systemui.dagger; +import com.android.keyguard.KeyguardBiometricLockoutLogger; import com.android.systemui.CoreStartable; import com.android.systemui.LatencyTester; import com.android.systemui.ScreenDecorations; @@ -90,6 +91,13 @@ public abstract class SystemUIBinder { @ClassKey(KeyguardViewMediator.class) public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui); + /** Inject into KeyguardBiometricLockoutLogger. */ + @Binds + @IntoMap + @ClassKey(KeyguardBiometricLockoutLogger.class) + public abstract CoreStartable bindKeyguardBiometricLockoutLogger( + KeyguardBiometricLockoutLogger sysui); + /** Inject into LatencyTests. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 4be819a49772..5d6c2a247df3 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -74,6 +74,9 @@ public class Flags { public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER = new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher); + public static final ResourceBooleanFlag ACTIVE_UNLOCK = + new ResourceBooleanFlag(205, R.bool.flag_active_unlock); + /***************************************/ // 300 - power menu public static final BooleanFlag POWER_MENU_LITE = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index bfa4a245070c..dee1b334182a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.systemui.statusbar.phone; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index ec2d0364a3a6..571c10b3800f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -54,6 +54,7 @@ class KeyguardLiftController constructor( isListening = false updateListeningState() keyguardUpdateMonitor.requestFaceAuth(true) + keyguardUpdateMonitor.requestActiveUnlock() } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt new file mode 100644 index 000000000000..6bc65054d830 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard + +import android.hardware.biometrics.BiometricSourceType +import org.mockito.Mockito.verify +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { + @Mock + lateinit var uiEventLogger: UiEventLogger + @Mock + lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock + lateinit var dumpManager: DumpManager + @Mock + lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker + + @Captor + lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback> + lateinit var updateMonitorCallback: KeyguardUpdateMonitorCallback + + lateinit var keyguardBiometricLockoutLogger: KeyguardBiometricLockoutLogger + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker) + keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger( + mContext, + uiEventLogger, + keyguardUpdateMonitor, + dumpManager) + } + + @Test + fun test_logsOnStart() { + // GIVEN is encrypted / lockdown before start + whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(anyInt())) + .thenReturn(true) + + // WHEN start + keyguardBiometricLockoutLogger.start() + + // THEN encrypted / lockdown state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN) + } + + @Test + fun test_logTimeoutChange() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c timeout + whenever(strongAuthTracker.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) + + // WHEN primary auth requirement changes + updateMonitorCallback.onStrongAuthStateChanged(0) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_TIMEOUT) + } + + @Test + fun test_logUnattendedUpdate() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c unattended update + whenever(strongAuthTracker.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) + + // WHEN primary auth requirement changes + updateMonitorCallback.onStrongAuthStateChanged(0) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + } + + @Test + fun test_logMultipleChanges() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c timeout + whenever(strongAuthTracker.getStrongAuthForUser(anyInt())) + .thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT + or STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) + + // WHEN primary auth requirement changes + updateMonitorCallback.onStrongAuthStateChanged(0) + + // THEN primary auth required state is logged with all the reasons + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_TIMEOUT) + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + + // WHEN onStrongAuthStateChanged is called again + updateMonitorCallback.onStrongAuthStateChanged(0) + + // THEN no more events are sent since there haven't been any changes + verifyNoMoreInteractions(uiEventLogger) + } + + @Test + fun test_logFaceLockout() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c face lock + whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(true) + + // WHEN lockout state changes + updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT) + + // WHEN face lockout is reset + whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false) + updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET) + } + + @Test + fun test_logFingerprintLockout() { + keyguardBiometricLockoutLogger.start() + captureUpdateMonitorCallback() + + // GIVEN primary auth required b/c fingerprint lock + whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true) + + // WHEN lockout state changes + updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT) + + // WHEN fingerprint lockout is reset + whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) + updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT) + + // THEN primary auth required state is logged + verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent + .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET) + } + + fun captureUpdateMonitorCallback() { + verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture()) + updateMonitorCallback = updateMonitorCallbackCaptor.value + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 70792cfee301..7266e41ad7ca 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -88,6 +88,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -173,6 +174,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private InteractionJankMonitor mInteractionJankMonitor; @Mock private LatencyTracker mLatencyTracker; + @Mock + private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; // Direct executor @@ -1105,7 +1108,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mRingerModeTracker, mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, - mInteractionJankMonitor, mLatencyTracker); + mInteractionJankMonitor, mLatencyTracker, mFeatureFlags); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt new file mode 100644 index 000000000000..8bc438bce5cc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.navigationbar +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.SurfaceControl +import android.view.View +import android.view.ViewRootImpl +import androidx.concurrent.futures.DirectExecutor +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@RunWithLooper +class RegionSamplingHelperTest : SysuiTestCase() { + + @Mock + lateinit var sampledView: View + @Mock + lateinit var samplingCallback: RegionSamplingHelper.SamplingCallback + @Mock + lateinit var compositionListener: RegionSamplingHelper.SysuiCompositionSamplingListener + @Mock + lateinit var viewRootImpl: ViewRootImpl + @Mock + lateinit var surfaceControl: SurfaceControl + @Mock + lateinit var wrappedSurfaceControl: SurfaceControl + @JvmField @Rule + var rule = MockitoJUnit.rule() + lateinit var regionSamplingHelper: RegionSamplingHelper + + @Before + fun setup() { + whenever(sampledView.isAttachedToWindow).thenReturn(true) + whenever(sampledView.viewRootImpl).thenReturn(viewRootImpl) + whenever(viewRootImpl.surfaceControl).thenReturn(surfaceControl) + whenever(surfaceControl.isValid).thenReturn(true) + whenever(wrappedSurfaceControl.isValid).thenReturn(true) + whenever(samplingCallback.isSamplingEnabled).thenReturn(true) + regionSamplingHelper = object : RegionSamplingHelper(sampledView, samplingCallback, + DirectExecutor.INSTANCE, DirectExecutor.INSTANCE, compositionListener) { + override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl { + return wrappedSurfaceControl + } + } + regionSamplingHelper.setWindowVisible(true) + } + + @Test + fun testStart_register() { + regionSamplingHelper.start(Rect(0, 0, 100, 100)) + verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl), any()) + } + + @Test + fun testStart_unregister() { + regionSamplingHelper.start(Rect(0, 0, 100, 100)) + regionSamplingHelper.setWindowVisible(false) + verify(compositionListener).unregister(any()) + } + + @Test + fun testStart_hasBlur_neverRegisters() { + regionSamplingHelper.setWindowHasBlurs(true) + regionSamplingHelper.start(Rect(0, 0, 100, 100)) + verify(compositionListener, never()) + .register(any(), anyInt(), eq(wrappedSurfaceControl), any()) + } + + @Test + fun testStart_stopAndDestroy() { + regionSamplingHelper.start(Rect(0, 0, 100, 100)) + regionSamplingHelper.stopAndDestroy() + verify(compositionListener).unregister(any()) + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index bc8da8443a7d..262933dea27f 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -45,6 +45,7 @@ import android.bluetooth.IBluetoothManager; import android.bluetooth.IBluetoothManagerCallback; import android.bluetooth.IBluetoothProfileServiceConnection; import android.bluetooth.IBluetoothStateChangeCallback; +import android.bluetooth.IBluetoothLeCallControl; import android.content.ActivityNotFoundException; import android.content.AttributionSource; import android.content.BroadcastReceiver; @@ -1323,11 +1324,15 @@ class BluetoothManagerService extends IBluetoothManager.Stub { + bluetoothProfile); } - if (bluetoothProfile != BluetoothProfile.HEADSET) { + Intent intent; + if (bluetoothProfile == BluetoothProfile.HEADSET) { + intent = new Intent(IBluetoothHeadset.class.getName()); + } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) { + intent = new Intent(IBluetoothLeCallControl.class.getName()); + } else { return false; } - Intent intent = new Intent(IBluetoothHeadset.class.getName()); psc = new ProfileServiceConnections(intent); if (!psc.bindService()) { return false; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 9b731d58153c..b3e46cd0b526 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -174,6 +174,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean mFgsNotificationWasDeferred; // FGS notification was shown before the FGS finishes, or it wasn't deferred in the first place. boolean mFgsNotificationShown; + // Whether FGS package has permissions to show notifications. + // TODO(b/194833441): Output this field to logs in ActiveServices#logFGSStateChangeLocked. + boolean mFgsHasNotificationPermission; // allow the service becomes foreground service? Service started from background may not be // allowed to become a foreground service. @@ -968,6 +971,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (nm == null) { return; } + // Record whether the package has permission to notify the user + mFgsHasNotificationPermission = nm.areNotificationsEnabledForPackage( + localPackageName, appUid); Notification localForegroundNoti = _foregroundNoti; try { if (localForegroundNoti.getSmallIcon() == null) { diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 6a716cbc2816..066c263fa83b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1223,8 +1223,11 @@ public class Vpn { for (RouteInfo route : mConfig.routes) { lp.addRoute(route); InetAddress address = route.getDestination().getAddress(); - allowIPv4 |= address instanceof Inet4Address; - allowIPv6 |= address instanceof Inet6Address; + + if (route.getType() == RouteInfo.RTN_UNICAST) { + allowIPv4 |= address instanceof Inet4Address; + allowIPv6 |= address instanceof Inet6Address; + } } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 4822d6a62ac7..96391ac62530 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -52,7 +52,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; -import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -643,22 +642,24 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } private void pushQueueUpdate() { - ParceledListSlice<QueueItem> parcelableQueue; + ArrayList<QueueItem> toSend; synchronized (mLock) { if (mDestroyed) { return; } - if (mQueue == null) { - parcelableQueue = null; - } else { - parcelableQueue = new ParceledListSlice<>(mQueue); - // Limit the size of initial Parcel to prevent binder buffer overflow - // as onQueueChanged is an async binder call. - parcelableQueue.setInlineCountLimit(1); + toSend = new ArrayList<>(); + if (mQueue != null) { + toSend.ensureCapacity(mQueue.size()); + toSend.addAll(mQueue); } } Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { + ParceledListSlice<QueueItem> parcelableQueue = new ParceledListSlice<>(toSend); + // Limit the size of initial Parcel to prevent binder buffer overflow + // as onQueueChanged is an async binder call. + parcelableQueue.setInlineCountLimit(1); + try { holder.mCallback.onQueueChanged(parcelableQueue); } catch (DeadObjectException e) { diff --git a/services/core/java/com/android/server/media/OWNERS b/services/core/java/com/android/server/media/OWNERS index 2e2d812c058e..8097f4e9b329 100644 --- a/services/core/java/com/android/server/media/OWNERS +++ b/services/core/java/com/android/server/media/OWNERS @@ -1,8 +1,6 @@ +# Bug component: 137631 elaurent@google.com -hdmoon@google.com -insun@google.com -jaewan@google.com -jinpark@google.com -klhyun@google.com lajos@google.com -sungsoo@google.com + +# go/android-fwk-media-solutions for info on areas of ownership. +include platform/frameworks/av:/media/janitors/media_solutions_OWNERS diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index 54557386d73c..c548e7edc3cf 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -39,4 +39,7 @@ public interface NotificationManagerInternal { /** Get the number of notification channels for a given package */ int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); + + /** Does the specified package/uid have permission to post notifications? */ + boolean areNotificationsEnabledForPackage(String pkg, int uid); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a0eeb6553088..399ae5347e6e 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6205,6 +6205,11 @@ public class NotificationManagerService extends SystemService { return NotificationManagerService.this .getNumNotificationChannelsForPackage(pkg, uid, includeDeleted); } + + @Override + public boolean areNotificationsEnabledForPackage(String pkg, int uid) { + return areNotificationsEnabledForPackageInt(pkg, uid); + } }; int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) { diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 05a51ccfaca7..31df0a53eaa9 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -896,10 +896,10 @@ public final class BackgroundDexOptService { synchronized (mLock) { if (!mFinishedPostBootUpdate) { mFinishedPostBootUpdate = true; - JobScheduler js = mInjector.getJobScheduler(); - js.cancel(JOB_POST_BOOT_UPDATE); } } + // Safe to do this outside lock. + mInjector.getJobScheduler().cancel(JOB_POST_BOOT_UPDATE); } private void notifyPinService(ArraySet<String> updatedPackages) { diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index ef0079e0c01f..ca675973b2fd 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -35,7 +35,6 @@ import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; - import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils; @@ -313,12 +312,12 @@ public final class PowerStatsLogger extends Handler { return mStartWallTime; } - public PowerStatsLogger(Context context, File dataStoragePath, + public PowerStatsLogger(Context context, Looper looper, File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - super(Looper.getMainLooper()); + super(looper); mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime(); if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime); mPowerStatsHALWrapper = powerStatsHALWrapper; diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index bb52c1dc1a29..9953ca8f9b65 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -28,6 +28,7 @@ import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.UserHandle; import android.power.PowerStatsInternal; import android.util.Slog; @@ -79,6 +80,9 @@ public class PowerStatsService extends SystemService { private StatsPullAtomCallbackImpl mPullAtomCallback; @Nullable private PowerStatsInternal mPowerStatsInternal; + @Nullable + @GuardedBy("this") + private Looper mLooper; @VisibleForTesting static class Injector { @@ -127,12 +131,12 @@ public class PowerStatsService extends SystemService { } } - PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String meterCacheFilename, + PowerStatsLogger createPowerStatsLogger(Context context, Looper looper, + File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - return new PowerStatsLogger(context, dataStoragePath, + return new PowerStatsLogger(context, looper, dataStoragePath, meterFilename, meterCacheFilename, modelFilename, modelCacheFilename, residencyFilename, residencyCacheFilename, @@ -229,11 +233,11 @@ public class PowerStatsService extends SystemService { mDataStoragePath = mInjector.createDataStoragePath(); // Only start logger and triggers if initialization is successful. - mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath, - mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(), - mInjector.createModelFilename(), mInjector.createModelCacheFilename(), - mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(), - getPowerStatsHal()); + mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, getLooper(), + mDataStoragePath, mInjector.createMeterFilename(), + mInjector.createMeterCacheFilename(), mInjector.createModelFilename(), + mInjector.createModelCacheFilename(), mInjector.createResidencyFilename(), + mInjector.createResidencyCacheFilename(), getPowerStatsHal()); mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger); mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger); } else { @@ -245,6 +249,17 @@ public class PowerStatsService extends SystemService { return mInjector.getPowerStatsHALWrapperImpl(); } + private Looper getLooper() { + synchronized (this) { + if (mLooper == null) { + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + return thread.getLooper(); + } + return mLooper; + } + } + public PowerStatsService(Context context) { this(context, new Injector()); } @@ -260,9 +275,7 @@ public class PowerStatsService extends SystemService { private final Handler mHandler; LocalService() { - HandlerThread thread = new HandlerThread(TAG); - thread.start(); - mHandler = new Handler(thread.getLooper()); + mHandler = new Handler(getLooper()); } diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java index a2bf2fe7df5e..a4732c1a0038 100644 --- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java @@ -906,6 +906,94 @@ public class TvIAppManagerService extends SystemService { } @Override + public void notifyVideoAvailable(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyVideoAvailable"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyVideoAvailable(); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyVideoAvailable", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void notifyVideoUnavailable(IBinder sessionToken, int reason, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyVideoUnavailable"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyVideoUnavailable(reason); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyVideoUnavailable", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void notifyContentAllowed(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyContentAllowed"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyContentAllowed(); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyContentAllowed", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void notifyContentBlocked(IBinder sessionToken, String rating, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyContentBlocked"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyContentBlocked(rating); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyContentBlocked", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void startIApp(IBinder sessionToken, int userId) { if (DEBUG) { Slogf.d(TAG, "BinderService#start(userId=%d)", userId); diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java index bd8d13b87125..6db25b7ed583 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -20,6 +20,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; @@ -44,7 +46,7 @@ import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.VcnContext; -import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; /** @hide */ @@ -76,7 +78,7 @@ class NetworkPriorityClassifier { public static int calculatePriorityClass( VcnContext vcnContext, UnderlyingNetworkRecord networkRecord, - LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, + List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -94,7 +96,7 @@ class NetworkPriorityClassifier { } int priorityIndex = 0; - for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkPriorities) { + for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) { if (checkMatchesPriorityRule( vcnContext, nwPriority, @@ -122,7 +124,11 @@ class NetworkPriorityClassifier { // TODO: Check Network Quality reported by metric monitors/probers. final NetworkCapabilities caps = networkRecord.networkCapabilities; - if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) { + + final int meteredMatch = networkPriority.getMetered(); + final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED); + if (meteredMatch == MATCH_REQUIRED && !isMetered + || meteredMatch == MATCH_FORBIDDEN && isMetered) { return false; } @@ -171,7 +177,8 @@ class NetworkPriorityClassifier { return false; } - if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) { + if (!networkPriority.getSsids().isEmpty() + && !networkPriority.getSsids().contains(caps.getSsid())) { return false; } @@ -226,26 +233,31 @@ class NetworkPriorityClassifier { .getSystemService(TelephonyManager.class) .createForSubscriptionId(subId); - if (!networkPriority.getAllowedOperatorPlmnIds().isEmpty()) { + if (!networkPriority.getOperatorPlmnIds().isEmpty()) { final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator(); - if (!networkPriority.getAllowedOperatorPlmnIds().contains(plmnId)) { + if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) { return false; } } - if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) { + if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) { final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId(); - if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) { + if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) { return false; } } - if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) { + final int roamingMatch = networkPriority.getRoaming(); + final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING); + if (roamingMatch == MATCH_REQUIRED && !isRoaming + || roamingMatch == MATCH_FORBIDDEN && isRoaming) { return false; } - if (networkPriority.requireOpportunistic()) { - if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) { + final int opportunisticMatch = networkPriority.getOpportunistic(); + final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds()); + if (opportunisticMatch == MATCH_REQUIRED) { + if (!isOpportunistic) { return false; } @@ -265,6 +277,8 @@ class NetworkPriorityClassifier { .contains(SubscriptionManager.getActiveDataSubscriptionId())) { return false; } + } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) { + return false; } return true; diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java index df2f0d58565e..c0488b18cb65 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java @@ -32,7 +32,7 @@ import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscription import com.android.server.vcn.VcnContext; import java.util.Comparator; -import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; /** @@ -77,7 +77,7 @@ public class UnderlyingNetworkRecord { static Comparator<UnderlyingNetworkRecord> getComparator( VcnContext vcnContext, - LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, + List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -87,7 +87,7 @@ public class UnderlyingNetworkRecord { NetworkPriorityClassifier.calculatePriorityClass( vcnContext, left, - underlyingNetworkPriorities, + underlyingNetworkTemplates, subscriptionGroup, snapshot, currentlySelected, @@ -96,7 +96,7 @@ public class UnderlyingNetworkRecord { NetworkPriorityClassifier.calculatePriorityClass( vcnContext, right, - underlyingNetworkPriorities, + underlyingNetworkTemplates, subscriptionGroup, snapshot, currentlySelected, @@ -133,7 +133,7 @@ public class UnderlyingNetworkRecord { void dump( VcnContext vcnContext, IndentingPrintWriter pw, - LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, + List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -145,7 +145,7 @@ public class UnderlyingNetworkRecord { NetworkPriorityClassifier.calculatePriorityClass( vcnContext, this, - underlyingNetworkPriorities, + underlyingNetworkTemplates, subscriptionGroup, snapshot, currentlySelected, diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 47622bc83417..9661e8d30b22 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -24,13 +24,11 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION; import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT; import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; -import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION; import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; @@ -134,11 +132,6 @@ public abstract class DisplayAreaPolicy { .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE) .build()) - .addFeature(new Feature.Builder(wmService.mPolicy, - "OneHandedBackgroundPanel", - FEATURE_ONE_HANDED_BACKGROUND_PANEL) - .upTo(TYPE_WALLPAPER) - .build()) .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded", FEATURE_ONE_HANDED) .all() diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 26b34fdd4e04..304fe5a1c9c3 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -30,6 +30,7 @@ import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.State; import android.hardware.power.stats.StateResidency; import android.hardware.power.stats.StateResidencyResult; +import android.os.Looper; import androidx.test.InstrumentationRegistry; @@ -145,12 +146,12 @@ public class PowerStatsServiceTest { } @Override - PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String meterCacheFilename, + PowerStatsLogger createPowerStatsLogger(Context context, Looper looper, + File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, + mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath, meterFilename, meterCacheFilename, modelFilename, modelCacheFilename, residencyFilename, residencyCacheFilename, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 11777ef7c1e0..d83190353a87 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -182,7 +182,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; -import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; @@ -7174,6 +7173,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testAreNotificationsEnabledForPackage_viaInternalService() throws Exception { + assertEquals(mInternalService.areNotificationsEnabledForPackage( + mContext.getPackageName(), mUid), + mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid)); + verify(mPermissionHelper, never()).hasPermission(anyInt()); + } + + @Test public void testAreBubblesAllowedForPackage_crossUser() throws Exception { try { mBinderService.getBubblePreferenceForPackage(mContext.getPackageName(), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index 0c8fe35484f7..1362628bde5e 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -536,6 +536,12 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { } @Test + public void testAreNotificationsEnabledForPackage_viaInternalService() { + mInternalService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid); + verify(mPermissionHelper).hasPermission(mUid); + } + + @Test public void testGetPackageImportance() throws Exception { when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); assertThat(mBinderService.getPackageImportance(mContext.getPackageName())) diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index 525888df7c78..cb9eb52bfc00 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -30,7 +30,6 @@ import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION; import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; -import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL; import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST; @@ -197,25 +196,6 @@ public class DisplayAreaPolicyBuilderTest { } @Test - public void testBuilder_defaultPolicy_hasOneHandedBackgroundFeature() { - final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( - resourcesWithProvider("")); - final DisplayAreaPolicyBuilder.Result defaultPolicy = - (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent, - mRoot, mImeContainer); - if (mDisplayContent.isDefaultDisplay) { - final List<Feature> features = defaultPolicy.getFeatures(); - boolean hasOneHandedBackgroundFeature = false; - for (Feature feature : features) { - hasOneHandedBackgroundFeature |= - feature.getId() == FEATURE_ONE_HANDED_BACKGROUND_PANEL; - } - - assertThat(hasOneHandedBackgroundFeature).isTrue(); - } - } - - @Test public void testBuilder_defaultPolicy_hasWindowedMagnificationFeature() { final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( resourcesWithProvider("")); diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java index d03aee282ee1..4a724b72f5f9 100644 --- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java +++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java @@ -15,12 +15,13 @@ */ package android.net.vcn; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -32,26 +33,26 @@ public class VcnCellUnderlyingNetworkTemplateTest { private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>(); // Package private for use in VcnGatewayConnectionConfigTest - static VcnCellUnderlyingNetworkTemplate getTestNetworkPriority() { + static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() { return new VcnCellUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setAllowedOperatorPlmnIds(ALLOWED_PLMN_IDS) - .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS) - .setAllowRoaming(true /* allowRoaming */) - .setRequireOpportunistic(true /* requireOpportunistic */) + .setMetered(MATCH_FORBIDDEN) + .setOperatorPlmnIds(ALLOWED_PLMN_IDS) + .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS) + .setRoaming(MATCH_FORBIDDEN) + .setOpportunistic(MATCH_REQUIRED) .build(); } @Test public void testBuilderAndGetters() { - final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); + final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality()); - assertTrue(networkPriority.allowMetered()); - assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedOperatorPlmnIds()); - assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds()); - assertTrue(networkPriority.allowRoaming()); - assertTrue(networkPriority.requireOpportunistic()); + assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered()); + assertEquals(ALLOWED_PLMN_IDS, networkPriority.getOperatorPlmnIds()); + assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds()); + assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming()); + assertEquals(MATCH_REQUIRED, networkPriority.getOpportunistic()); } @Test @@ -59,16 +60,16 @@ public class VcnCellUnderlyingNetworkTemplateTest { final VcnCellUnderlyingNetworkTemplate networkPriority = new VcnCellUnderlyingNetworkTemplate.Builder().build(); assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality()); - assertFalse(networkPriority.allowMetered()); - assertEquals(new HashSet<String>(), networkPriority.getAllowedOperatorPlmnIds()); - assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds()); - assertFalse(networkPriority.allowRoaming()); - assertFalse(networkPriority.requireOpportunistic()); + assertEquals(MATCH_ANY, networkPriority.getMetered()); + assertEquals(new HashSet<String>(), networkPriority.getOperatorPlmnIds()); + assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds()); + assertEquals(MATCH_ANY, networkPriority.getRoaming()); + assertEquals(MATCH_ANY, networkPriority.getOpportunistic()); } @Test public void testPersistableBundle() { - final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); + final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); assertEquals( networkPriority, VcnUnderlyingNetworkTemplate.fromPersistableBundle( diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 1f2905da08f4..2aef9ae7ca32 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -17,8 +17,8 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; -import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES; -import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_PRIORITIES_KEY; +import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES; +import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -40,8 +40,9 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedHashSet; +import java.util.List; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @@ -54,17 +55,17 @@ public class VcnGatewayConnectionConfigTest { }; public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN}; - private static final LinkedHashSet<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_PRIORITIES = - new LinkedHashSet(); + private static final List<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_TEMPLATES = + new ArrayList(); static { Arrays.sort(EXPOSED_CAPS); Arrays.sort(UNDERLYING_CAPS); - UNDERLYING_NETWORK_PRIORITIES.add( - VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority()); - UNDERLYING_NETWORK_PRIORITIES.add( - VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); + UNDERLYING_NETWORK_TEMPLATES.add( + VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); + UNDERLYING_NETWORK_TEMPLATES.add( + VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); } public static final long[] RETRY_INTERVALS_MS = @@ -95,7 +96,7 @@ public class VcnGatewayConnectionConfigTest { // Public for use in VcnGatewayConnectionTest public static VcnGatewayConnectionConfig buildTestConfig() { final VcnGatewayConnectionConfig.Builder builder = - newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES); + newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES); return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS); } @@ -174,10 +175,10 @@ public class VcnGatewayConnectionConfigTest { } @Test - public void testBuilderRequiresNonNullNetworkPriorities() { + public void testBuilderRequiresNonNullNetworkTemplates() { try { newBuilder().setVcnUnderlyingNetworkPriorities(null); - fail("Expected exception due to invalid underlyingNetworkPriorities"); + fail("Expected exception due to invalid underlyingNetworkTemplates"); } catch (NullPointerException e) { } } @@ -219,7 +220,7 @@ public class VcnGatewayConnectionConfigTest { Arrays.sort(exposedCaps); assertArrayEquals(EXPOSED_CAPS, exposedCaps); - assertEquals(UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities()); + assertEquals(UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities()); assertEquals(TUNNEL_CONNECTION_PARAMS, config.getTunnelConnectionParams()); assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis()); @@ -234,13 +235,13 @@ public class VcnGatewayConnectionConfigTest { } @Test - public void testParsePersistableBundleWithoutVcnUnderlyingNetworkPriorities() { + public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() { PersistableBundle configBundle = buildTestConfig().toPersistableBundle(); - configBundle.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, null); + configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null); final VcnGatewayConnectionConfig config = new VcnGatewayConnectionConfig(configBundle); assertEquals( - DEFAULT_UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities()); + DEFAULT_UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities()); } private static IkeTunnelConnectionParams buildTunnelConnectionParams(String ikePsk) { @@ -285,39 +286,36 @@ public class VcnGatewayConnectionConfigTest { assertNotEquals(config, anotherConfig); } - private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkPriorities( - LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPriorities) { + private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkTemplates( + List<VcnUnderlyingNetworkTemplate> networkTemplates) { return buildTestConfigWithExposedCaps( new VcnGatewayConnectionConfig.Builder( - "buildTestConfigWithVcnUnderlyingNetworkPriorities", + "buildTestConfigWithVcnUnderlyingNetworkTemplates", TUNNEL_CONNECTION_PARAMS) - .setVcnUnderlyingNetworkPriorities(networkPriorities), + .setVcnUnderlyingNetworkPriorities(networkTemplates), EXPOSED_CAPS); } @Test - public void testVcnUnderlyingNetworkPrioritiesEquality() throws Exception { + public void testVcnUnderlyingNetworkTemplatesEquality() throws Exception { final VcnGatewayConnectionConfig config = - buildTestConfigWithVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES); + buildTestConfigWithVcnUnderlyingNetworkTemplates(UNDERLYING_NETWORK_TEMPLATES); - final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesEqual = - new LinkedHashSet(); - networkPrioritiesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority()); - networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); + final List<VcnUnderlyingNetworkTemplate> networkTemplatesEqual = new ArrayList(); + networkTemplatesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); + networkTemplatesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); final VcnGatewayConnectionConfig configEqual = - buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesEqual); + buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesEqual); - final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesNotEqual = - new LinkedHashSet(); - networkPrioritiesNotEqual.add( - VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); + final List<VcnUnderlyingNetworkTemplate> networkTemplatesNotEqual = new ArrayList(); + networkTemplatesNotEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate()); final VcnGatewayConnectionConfig configNotEqual = - buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesNotEqual); + buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesNotEqual); - assertEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesEqual); + assertEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesEqual); assertEquals(config, configEqual); - assertNotEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesNotEqual); + assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual); assertNotEquals(config, configNotEqual); } } diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java index 652057fd48c7..cb5b47bbdc15 100644 --- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java +++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java @@ -15,36 +15,38 @@ */ package android.net.vcn; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; +import java.util.Set; + public class VcnWifiUnderlyingNetworkTemplateTest { private static final String SSID = "TestWifi"; private static final int INVALID_NETWORK_QUALITY = -1; // Package private for use in VcnGatewayConnectionConfigTest - static VcnWifiUnderlyingNetworkTemplate getTestNetworkPriority() { + static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() { return new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setSsid(SSID) + .setMetered(MATCH_FORBIDDEN) + .setSsids(Set.of(SSID)) .build(); } @Test public void testBuilderAndGetters() { - final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); + final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality()); - assertTrue(networkPriority.allowMetered()); - assertEquals(SSID, networkPriority.getSsid()); + assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered()); + assertEquals(Set.of(SSID), networkPriority.getSsids()); } @Test @@ -52,8 +54,8 @@ public class VcnWifiUnderlyingNetworkTemplateTest { final VcnWifiUnderlyingNetworkTemplate networkPriority = new VcnWifiUnderlyingNetworkTemplate.Builder().build(); assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality()); - assertFalse(networkPriority.allowMetered()); - assertNull(SSID, networkPriority.getSsid()); + assertEquals(MATCH_ANY, networkPriority.getMetered()); + assertTrue(networkPriority.getSsids().isEmpty()); } @Test @@ -68,7 +70,7 @@ public class VcnWifiUnderlyingNetworkTemplateTest { @Test public void testPersistableBundle() { - final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); + final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); assertEquals( networkPriority, VcnUnderlyingNetworkTemplate.fromPersistableBundle( diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java index f23d5bf67ebf..4bb7de8c1fef 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -16,6 +16,8 @@ package com.android.server.vcn.routeselection; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static com.android.server.vcn.VcnTestUtils.setupSystemService; @@ -145,7 +147,7 @@ public class NetworkPriorityClassifierTest { final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(false /* allowMetered */) + .setMetered(MATCH_FORBIDDEN) .build(); assertFalse( @@ -164,7 +166,6 @@ public class NetworkPriorityClassifierTest { final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) .build(); final UnderlyingNetworkRecord selectedNetworkRecord = isSelectedNetwork ? mWifiNetworkRecord : null; @@ -214,8 +215,7 @@ public class NetworkPriorityClassifierTest { final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setSsid(nwPrioritySsid) + .setSsids(Set.of(nwPrioritySsid)) .build(); assertEquals( @@ -238,10 +238,7 @@ public class NetworkPriorityClassifierTest { } private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() { - return new VcnCellUnderlyingNetworkTemplate.Builder() - .setNetworkQuality(NETWORK_QUALITY_OK) - .setAllowMetered(true /* allowMetered */) - .setAllowRoaming(true /* allowRoaming */); + return new VcnCellUnderlyingNetworkTemplate.Builder().setNetworkQuality(NETWORK_QUALITY_OK); } @Test @@ -258,9 +255,7 @@ public class NetworkPriorityClassifierTest { @Test public void testMatchOpportunisticCell() { final VcnCellUnderlyingNetworkTemplate opportunisticCellNetworkPriority = - getCellNetworkPriorityBuilder() - .setRequireOpportunistic(true /* requireOpportunistic */) - .build(); + getCellNetworkPriorityBuilder().setOpportunistic(MATCH_REQUIRED).build(); when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true); when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>()); @@ -279,7 +274,7 @@ public class NetworkPriorityClassifierTest { final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER; final VcnCellUnderlyingNetworkTemplate networkPriority = getCellNetworkPriorityBuilder() - .setAllowedOperatorPlmnIds(Set.of(networkPriorityPlmnId)) + .setOperatorPlmnIds(Set.of(networkPriorityPlmnId)) .build(); assertEquals( @@ -308,7 +303,7 @@ public class NetworkPriorityClassifierTest { final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER; final VcnCellUnderlyingNetworkTemplate networkPriority = getCellNetworkPriorityBuilder() - .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId)) + .setSimSpecificCarrierIds(Set.of(networkPriorityCarrierId)) .build(); assertEquals( @@ -336,7 +331,7 @@ public class NetworkPriorityClassifierTest { @Test public void testMatchWifiFailWithoutNotRoamingBit() { final VcnCellUnderlyingNetworkTemplate networkPriority = - getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build(); + getCellNetworkPriorityBuilder().setRoaming(MATCH_FORBIDDEN).build(); assertFalse( checkMatchesCellPriorityRule( @@ -353,7 +348,7 @@ public class NetworkPriorityClassifierTest { calculatePriorityClass( mVcnContext, networkRecord, - VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES, + VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES, SUB_GROUP, mSubscriptionSnapshot, null /* currentlySelected */, |