Merge "Add self revocation public API"
diff --git a/apex/media/OWNERS b/apex/media/OWNERS
index bed3895..2c5965c 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 e48f234..7d47e25 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 @@
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 @@
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 @@
}
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 @@
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 2dfae26..e702dad 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -27364,11 +27364,13 @@
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);
@@ -27686,6 +27688,23 @@
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();
@@ -27704,6 +27723,7 @@
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 {
@@ -27713,6 +27733,7 @@
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 {
@@ -27736,6 +27757,24 @@
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 18dae37..e1c05e2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1047,6 +1047,7 @@
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.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);
@@ -10800,7 +10820,7 @@
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";
@@ -10815,7 +10835,7 @@
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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index b052bc5..a9ec11e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -178,17 +178,16 @@
Point getAppTaskThumbnailSize();
/**
* Only callable from the system. This token grants a temporary permission to call
- * #startActivityAsCallerWithToken. The token will time out after
- * START_AS_CALLER_TOKEN_TIMEOUT if it is not used.
+ * #startActivityAsCaller. The token will time out after START_AS_CALLER_TOKEN_TIMEOUT
+ * if it is not used.
*
- * @param delegatorToken The Binder token referencing the system Activity that wants to delegate
- * the #startActivityAsCaller to another app. The "caller" will be the caller of this
- * activity's token, not the delegate's caller (which is probably the delegator itself).
+ * @param componentName The component name of the delegated component that is allowed to
+ * call #startActivityAsCaller with the returned token.
*
* @return Returns a token that can be given to a "delegate" app that may call
* #startActivityAsCaller
*/
- IBinder requestStartActivityPermissionToken(in IBinder delegatorToken);
+ IBinder requestStartActivityPermissionToken(in ComponentName componentName);
oneway void releaseSomeActivities(in IApplicationThread app);
Bitmap getTaskDescriptionIcon(in String filename, int userId);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7969cda..73ebdf6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2885,6 +2885,38 @@
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 65b2775..177de83 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -156,7 +156,7 @@
@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 856a8e1..7e5e96d 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -3064,6 +3064,9 @@
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 @@
(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 0000000..fb7789d
--- /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 0000000..fb080c9
--- /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 e047e5d..d0f74e9 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -240,12 +240,19 @@
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 7129d76..c52a6ee 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 @@
*/
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 @@
mAdvertiseTxPowerLevel = in.readInt();
mAdvertiseConnectable = in.readInt() != 0;
mAdvertiseTimeoutMillis = in.readInt();
+ mOwnAddressType = in.readInt();
}
/**
@@ -118,12 +126,23 @@
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 @@
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 @@
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 @@
}
/**
+ * 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 e39b198..5c8fae6 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 @@
*/
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 @@
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 @@
mSecondaryPhy = secondaryPhy;
mInterval = interval;
mTxPowerLevel = txPowerLevel;
+ mOwnAddressType = ownAddressType;
}
private AdvertisingSetParameters(Parcel in) {
@@ -132,6 +173,7 @@
mSecondaryPhy = in.readInt();
mInterval = in.readInt();
mTxPowerLevel = in.readInt();
+ mOwnAddressType = in.readInt();
}
/**
@@ -197,6 +239,16 @@
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 @@
+ ", primaryPhy=" + mPrimaryPhy
+ ", secondaryPhy=" + mSecondaryPhy
+ ", interval=" + mInterval
- + ", txPowerLevel=" + mTxPowerLevel + "]";
+ + ", txPowerLevel=" + mTxPowerLevel
+ + ", ownAddressType=" + mOwnAddressType + "]";
}
@Override
@@ -225,6 +278,7 @@
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 @@
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 @@
}
/**
+ * 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 @@
}
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 b9f8a57..879dcee 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -138,6 +138,7 @@
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 32827ae..b3435b1 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -1182,7 +1182,7 @@
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/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 7696cbe..78984bd 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -174,7 +174,7 @@
// Register for user-related events
IntentFilter userFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_USER_REMOVED);
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 77b8be3..d04c97c 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 afaa085..93573d1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -523,6 +523,7 @@
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 @@
@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 @@
}
@Override public void onDestroy() {
+ mDestroyed = true;
super.onDestroy();
mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
mInsetsComputer);
@@ -1811,15 +1818,19 @@
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 1b5ab05..5554137 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -916,9 +916,23 @@
}
/**
- * 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 6f09383..f42c4b7 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 @@
public final class InternalNetworkUpdateRequest implements Parcelable {
@NonNull
private final StaticIpConfiguration mIpConfig;
- @Nullable
+ @NonNull
private final NetworkCapabilities mNetworkCapabilities;
@NonNull
@@ -37,20 +36,16 @@
@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 2ced056..1ae1b05 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -41,6 +41,7 @@
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.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 @@
}
}
+ 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 @@
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 @@
throw new IllegalArgumentException("Bad address");
}
mAddresses.add(new LinkAddress(address, prefixLength));
- mConfig.updateAllowedFamilies(address);
return this;
}
@@ -579,28 +587,68 @@
* 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 @@
* 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 @@
}
/**
+ * 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 @@
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 1ac3f0a..125b573 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 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.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 @@
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 @@
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 @@
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 @@
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 @@
super.hashCode(),
mAllowedNetworkPlmnIds,
mAllowedSpecificCarrierIds,
- mAllowRoaming,
- mRequireOpportunistic);
+ mRoamingMatchCriteria,
+ mOpportunisticMatchCriteria);
}
@Override
@@ -197,8 +226,8 @@
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 @@
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 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 d07c24a..92956e8 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.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 @@
/** @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 @@
/** @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 @@
@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 @@
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 @@
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 @@
checkValidCapability(cap);
}
- Objects.requireNonNull(mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+ validateNetworkTemplateList(mUnderlyingNetworkTemplates);
Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
validateRetryInterval(mRetryIntervalsMs);
@@ -314,6 +306,19 @@
}
}
+ 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 @@
}
/**
- * 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 @@
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 @@
mGatewayConnectionName,
mTunnelConnectionParams,
mExposedCapabilities,
- mUnderlyingNetworkPriorities,
+ mUnderlyingNetworkTemplates,
Arrays.hashCode(mRetryIntervalsMs),
mMaxMtu);
}
@@ -451,7 +454,7 @@
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 @@
@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 @@
}
/**
- * Set the VcnUnderlyingNetworkTemplate list.
+ * Set the list of templates to match underlying networks against, in high-to-low priority
+ * order.
*
- * @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>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.
+ *
+ * <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 @@
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 d306d5c..60fc936 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.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 @@
@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 @@
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 @@
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 @@
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 6bbb2bf..272ca9d 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 @@
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 @@
@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 @@
}
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;
+ }
+
+ /**
+ * 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 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, mAllowMetered, mSsid);
- }
-
- /** @hide */
- @Override
- Builder self() {
- return this;
+ 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 b79c055..105c2aa 100644
--- a/core/java/android/service/games/GameService.java
+++ b/core/java/android/service/games/GameService.java
@@ -38,10 +38,23 @@
* 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 @@
@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 a2c8870..c1a3eb5 100644
--- a/core/java/android/service/games/GameSessionService.java
+++ b/core/java/android/service/games/GameSessionService.java
@@ -73,7 +73,7 @@
@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 07d5fc5..25e0eca 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1896,7 +1896,7 @@
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 @@
if (mCanvas == null) {
mCanvas = new Canvas();
}
- mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
+ mEnabledHwFeaturesInSwMode = mCanvas.isHwFeaturesInSwModeEnabled();
mCanvas.setBitmap(mBitmap);
return mCanvas;
}
@@ -1921,7 +1921,7 @@
@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 974a1dd..88ece5c 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -101,14 +101,6 @@
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 @@
* 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/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 025f711..be7388b 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -759,11 +759,11 @@
}
try {
- IBinder permissionToken = ActivityTaskManager.getService()
- .requestStartActivityPermissionToken(getActivityToken());
Intent delegationIntent = new Intent();
final ComponentName delegateActivity = ComponentName.unflattenFromString(
Resources.getSystem().getString(R.string.config_chooserActivity));
+ IBinder permissionToken = ActivityTaskManager.getService()
+ .requestStartActivityPermissionToken(delegateActivity);
delegationIntent.setComponent(delegateActivity);
delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index b273f6d..f9a8c7b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1437,11 +1437,11 @@
try {
// TODO: Once this is a small springboard activity, it can move off the UI process
// and we can move the request method to ActivityManagerInternal.
- IBinder permissionToken = ActivityTaskManager.getService()
- .requestStartActivityPermissionToken(getActivityToken());
final Intent chooserIntent = new Intent();
final ComponentName delegateActivity = ComponentName.unflattenFromString(
Resources.getSystem().getString(R.string.config_chooserActivity));
+ IBinder permissionToken = ActivityTaskManager.getService()
+ .requestStartActivityPermissionToken(delegateActivity);
chooserIntent.setClassName(delegateActivity.getPackageName(),
delegateActivity.getClassName());
chooserIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 2a203ac..b579be0 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.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 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 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 @@
//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 @@
//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 627381c..09ff4e0 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.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 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (insets.getDisplayCutout() != null) {
- mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop();
- mWaterfallInsets = insets.getDisplayCutout().getWaterfallInsets();
- } else {
- mHeaderPaddingTop = 0;
- mWaterfallInsets = Insets.NONE;
+ 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) {
+ 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 a612265..425a378 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -67,7 +67,7 @@
* @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 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 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 @@
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 @@
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 @@
|| (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 @@
if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
throw new ArrayIndexOutOfBoundsException();
}
- throwIfHasHwBitmapInSwMode(paint);
+ throwIfHasHwFeaturesInSwMode(paint);
if (meshWidth == 0 || meshHeight == 0) {
return;
}
@@ -243,7 +243,7 @@
}
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 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 @@
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 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 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 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 @@
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 @@
@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 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 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 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 @@
(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 @@
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 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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
/**
* @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) {
+ protected boolean onHwFeatureInSwMode() {
+ return !mAllowHwFeaturesInSwMode;
+ }
+
+ private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
+ if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE
+ && onHwFeatureInSwMode()) {
throw new IllegalArgumentException(
"Software rendering doesn't support hardware bitmaps");
}
}
- private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
- if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
- onHwBitmapInSwMode();
- }
- }
-
- 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 390d3d4..ee4165b 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -124,7 +124,7 @@
public void endRecording() {
verifyValid();
if (mRecordingCanvas != null) {
- mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
+ mRequiresHwAcceleration = mRecordingCanvas.mUsesHwFeature;
mRecordingCanvas = null;
nativeEndRecording(mNativePicture);
}
@@ -182,8 +182,10 @@
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 @@
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 @@
}
@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 b843589..ffaa4ea 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -868,7 +868,7 @@
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 @@
}
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 @@
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 0000000..c3569d8
--- /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/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index ad9ebb2..36e55ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -137,14 +137,16 @@
return;
}
- if (mIcon == null) {
- // TODO: add fade-in animation.
+ if (mBackgroundLeash == null) {
mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
.setLayer(mBackgroundLeash, SPLIT_DIVIDER_LAYER - 1)
.show(mBackgroundLeash);
+ }
+ if (mIcon == null && resizingTask.topActivityInfo != null) {
+ // TODO: add fade-in animation.
mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
mResizingIconView.setImageDrawable(mIcon);
mResizingIconView.setVisibility(View.VISIBLE);
@@ -168,12 +170,16 @@
return;
}
+ if (mBackgroundLeash != null) {
+ t.remove(mBackgroundLeash);
+ mBackgroundLeash = null;
+ }
+
if (mIcon != null) {
mResizingIconView.setVisibility(View.GONE);
mResizingIconView.setImageDrawable(null);
- t.remove(mBackgroundLeash).hide(mIconLeash);
+ t.hide(mIconLeash);
mIcon = null;
- mBackgroundLeash = null;
}
}
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 0000000..c20b7d9
--- /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 9e1c61a..0000000
--- 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 96f82fa..48acfc1 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 @@
private OneHandedEventCallback mEventCallback;
private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
- private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
private OneHandedUiEventLogger mOneHandedUiEventLogger;
private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
@@ -163,7 +162,6 @@
public void onStopFinished(Rect bounds) {
mState.setState(STATE_NONE);
notifyShortcutStateChanged(STATE_NONE);
- mBackgroundPanelOrganizer.onStopFinished();
}
};
@@ -201,32 +199,28 @@
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 @@
mContext = context;
mOneHandedSettingsUtil = settingsUtil;
mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
- mBackgroundPanelOrganizer = backgroundPanelOrganizer;
mDisplayAreaOrganizer = displayAreaOrganizer;
mDisplayController = displayController;
mTouchHandler = touchHandler;
@@ -286,7 +279,6 @@
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
- mState.addSListeners(mBackgroundPanelOrganizer);
mState.addSListeners(mTutorialHandler);
}
@@ -368,7 +360,6 @@
mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
mOneHandedAccessibilityUtil.announcementForScreenReader(
mOneHandedAccessibilityUtil.getOneHandedStartDescription());
- mBackgroundPanelOrganizer.onStart();
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
mTimeoutHandler.resetTimer();
mOneHandedUiEventLogger.writeEvent(
@@ -461,7 +452,6 @@
}
mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
mTutorialHandler.onDisplayChanged(newDisplayLayout);
- mBackgroundPanelOrganizer.onDisplayChanged(newDisplayLayout);
}
private ContentObserver getObserver(Runnable onChangeRunnable) {
@@ -585,7 +575,6 @@
if (!mIsOneHandedEnabled) {
mDisplayAreaOrganizer.unregisterOrganizer();
- mBackgroundPanelOrganizer.unregisterOrganizer();
// Do NOT register + unRegister DA in the same call
return;
}
@@ -594,11 +583,6 @@
mDisplayAreaOrganizer.registerOrganizer(
OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED);
}
-
- if (!mBackgroundPanelOrganizer.isRegistered()) {
- mBackgroundPanelOrganizer.registerOrganizer(
- OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL);
- }
}
@VisibleForTesting
@@ -613,13 +597,12 @@
}
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 @@
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 87eb40c..f61d1b9 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 @@
mSurfaceControlTransactionFactory;
private OneHandedTutorialHandler mTutorialHandler;
private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>();
- private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
@VisibleForTesting
OneHandedAnimationCallback mOneHandedAnimationCallback =
@@ -135,7 +134,6 @@
OneHandedSettingsUtil oneHandedSettingsUtil,
OneHandedAnimationController animationController,
OneHandedTutorialHandler tutorialHandler,
- OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer,
InteractionJankMonitor jankMonitor,
ShellExecutor mainExecutor) {
super(mainExecutor);
@@ -150,7 +148,6 @@
SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION,
animationDurationConfig);
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
- mBackgroundPanelOrganizer = oneHandedBackgroundGradientOrganizer;
mTutorialHandler = tutorialHandler;
}
@@ -258,7 +255,6 @@
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 88f3375..04e8cf9 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.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 @@
private final float mTutorialHeightRatio;
private final WindowManager mWindowManager;
+ private final BackgroundWindowManager mBackgroundWindowManager;
private @OneHandedState.State int mCurrentState;
private int mTutorialAreaHeight;
@@ -79,9 +79,10 @@
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 @@
}
@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 @@
case STATE_NONE:
checkTransitionEnd();
removeTutorialFromWindowManager();
- break;
default:
break;
}
@@ -146,6 +157,7 @@
}
mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio);
mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION;
+ mBackgroundWindowManager.onDisplayChanged(displayLayout);
}
@VisibleForTesting
@@ -169,6 +181,7 @@
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 @@
mTargetViewContainer = null;
}
+ @VisibleForTesting
+ void removeBackgroundSurface() {
+ mBackgroundWindowManager.removeBackgroundLayer();
+ }
+
/**
* Returns layout params for the dismiss target, using the latest display metrics.
*/
@@ -213,9 +231,12 @@
* 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 @@
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 @@
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 0000000..f3f7067
--- /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 7b9553c..0000000
--- 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 636e875..2886b97 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 @@
@Mock
DisplayController mMockDisplayController;
@Mock
- OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
- @Mock
OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
@Mock
OneHandedEventCallback mMockEventCallback;
@@ -115,7 +113,6 @@
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 @@
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 df21163..9c7f723 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 @@
@Mock
WindowContainerTransaction mMockWindowContainerTransaction;
@Mock
- OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
- @Mock
ShellExecutor mMockShellMainExecutor;
@Mock
OneHandedSettingsUtil mMockSettingsUitl;
@@ -143,7 +141,6 @@
mMockSettingsUitl,
mMockAnimationController,
mTutorialHandler,
- mMockBackgroundOrganizer,
mJankMonitor,
mMockShellMainExecutor));
@@ -431,7 +428,6 @@
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 58399b6..dba1b8b 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 @@
@Mock
DisplayController mMockDisplayController;
@Mock
- OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
- @Mock
OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
@Mock
OneHandedTouchHandler mMockTouchHandler;
@@ -105,7 +103,6 @@
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 @@
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 b1434ca..63d8bfd 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 @@
OneHandedSettingsUtil mMockSettingsUtil;
@Mock
WindowManager mMockWindowManager;
+ @Mock
+ BackgroundWindowManager mMockBackgroundWindowManager;
@Before
public void setUp() {
@@ -63,10 +65,11 @@
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 0aff43e..5f50137 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -3,7 +3,6 @@
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 ad86002..f3670e6 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -764,6 +764,9 @@
@Override
public void run() {
mSessionCallback.onVideoAvailable(mSession);
+ if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+ mSession.getIAppSession().notifyVideoAvailable();
+ }
}
});
}
@@ -773,6 +776,9 @@
@Override
public void run() {
mSessionCallback.onVideoUnavailable(mSession, reason);
+ if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+ mSession.getIAppSession().notifyVideoUnavailable(reason);
+ }
}
});
}
@@ -782,6 +788,9 @@
@Override
public void run() {
mSessionCallback.onContentAllowed(mSession);
+ if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+ mSession.getIAppSession().notifyContentAllowed();
+ }
}
});
}
@@ -791,6 +800,9 @@
@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 23201fa..2e04359 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -51,6 +51,10 @@
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 52f9a87..2788ff6 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
@@ -42,6 +42,10 @@
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 d1fd1df..9685e3a 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.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 @@
}
}
+ /**
+ * 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 0456041..4993bc3 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.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 @@
}
/**
+ * 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 @@
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 @@
}
@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 9572e180..d9feaf40 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -461,6 +461,7 @@
}
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 8568383..959e756 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
}
FilterClient::~FilterClient() {
+ Mutex::Autolock _l(mLock);
mTunerFilter = nullptr;
mAvSharedHandle = nullptr;
mAvSharedMemSize = 0;
@@ -74,6 +75,7 @@
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::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::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::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::start() {
+ Mutex::Autolock _l(mLock);
if (mTunerFilter != nullptr) {
Status s = mTunerFilter->start();
return ClientHelper::getServiceSpecificErrorCode(s);
@@ -123,6 +129,7 @@
}
Result FilterClient::stop() {
+ Mutex::Autolock _l(mLock);
if (mTunerFilter != nullptr) {
Status s = mTunerFilter->stop();
return ClientHelper::getServiceSpecificErrorCode(s);
@@ -132,6 +139,7 @@
}
Result FilterClient::flush() {
+ Mutex::Autolock _l(mLock);
if (mTunerFilter != nullptr) {
Status s = mTunerFilter->flush();
return ClientHelper::getServiceSpecificErrorCode(s);
@@ -141,6 +149,7 @@
}
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::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::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::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::close() {
+ Mutex::Autolock _l(mLock);
if (mFilterMQEventFlag != nullptr) {
EventFlag::deleteEventFlag(&mFilterMQEventFlag);
mFilterMQEventFlag = nullptr;
@@ -197,6 +210,7 @@
}
string FilterClient::acquireSharedFilterToken() {
+ Mutex::Autolock _l(mLock);
if (mTunerFilter != nullptr) {
string filterToken;
if (mTunerFilter->acquireSharedFilterToken(&filterToken).isOk()) {
@@ -208,6 +222,7 @@
}
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 @@
}
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 20e5610..9e9b233 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::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 @@
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 c206903..3f2b8ac 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 0401098..ec3c336 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 1c33f1a..11546c8 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 5800636..8e7226b 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 6bf288b..7bf75bc 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 0000000..e73dcc0
--- /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 18af1f9..f7e0144 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 0000000..aaab0f0
--- /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 de45ea5..d3fe4a7 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 @@
* 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/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
index 21a4ac6..fa4ae67 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
@@ -60,6 +60,22 @@
return true;
}
+ /**
+ * Confirm Wi-Fi Config is allowed to add according to whether user restriction is set
+ *
+ * @param context A context
+ * @return whether the device is permitted to add new Wi-Fi config
+ */
+ public static boolean isAddWifiConfigAllowed(Context context) {
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ final Bundle restrictions = userManager.getUserRestrictions();
+ if (isAtLeastT() && restrictions.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)) {
+ Log.i(TAG, "Wi-Fi Add network isn't available due to user restriction.");
+ return false;
+ }
+ return true;
+ }
+
@ChecksSdkIntAtLeast(api=Build.VERSION_CODES.TIRAMISU)
private static boolean isAtLeastT() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
index 3c339de..f6af09a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
@@ -103,4 +103,30 @@
assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isTrue();
}
+
+ @Test
+ public void isAddWifiConfigAllowed_setSDKForS_shouldReturnTrue() {
+ ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S);
+ when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true);
+
+ assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isTrue();
+ }
+
+ @Test
+ public void isAddWifiConfigAllowed_setSDKForTAndDisallowForRestriction_shouldReturnFalse() {
+ ReflectionHelpers.setStaticField(
+ Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU);
+ when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true);
+
+ assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAddWifiConfigAllowed_setSDKForTAndAllowForRestriction_shouldReturnTrue() {
+ ReflectionHelpers.setStaticField(
+ Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU);
+ when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(false);
+
+ assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isTrue();
+ }
}
diff --git a/packages/SystemUI/res-keyguard/values/bools.xml b/packages/SystemUI/res-keyguard/values/bools.xml
index 2b83787..c5bf4ce 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 7b8f349..56517cc 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 2916c1c..7600eb1 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 9808374..1d2caf9 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.ViewRootImpl;
import android.view.ViewTreeObserver;
+import androidx.annotation.VisibleForTesting;
+
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -60,6 +62,7 @@
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 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 void stopAndDestroy() {
stop();
- mSamplingListener.destroy();
+ mBackgroundExecutor.execute(mSamplingListener::destroy);
mIsDestroyed = true;
}
@@ -189,13 +200,12 @@
// 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 @@
}
}
+ @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 @@
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 0000000..214b284
--- /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 03f04d3..36fe5ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -64,3 +64,19 @@
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 f13a59a..210f5e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
@@ -32,15 +32,17 @@
) {
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 @@
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 98721fd..5276679 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.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.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.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 @@
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 @@
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 @@
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 @@
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 @@
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final Executor mBackgroundExecutor;
private SensorPrivacyManager mSensorPrivacyManager;
+ private FeatureFlags mFeatureFlags;
private int mFaceAuthUserId;
/**
@@ -1250,7 +1255,11 @@
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 @@
void setAssistantVisible(boolean assistantVisible) {
mAssistantVisible = assistantVisible;
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ if (mAssistantVisible) {
+ requestActiveUnlock();
+ }
}
static class DisplayClientState {
@@ -1650,6 +1662,7 @@
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 @@
AuthController authController,
TelephonyListenerManager telephonyListenerManager,
InteractionJankMonitor interactionJankMonitor,
- LatencyTracker latencyTracker) {
+ LatencyTracker latencyTracker,
+ FeatureFlags featureFlags) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -1795,6 +1809,7 @@
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mFeatureFlags = featureFlags;
mHandler = new Handler(mainLooper) {
@Override
@@ -2180,6 +2195,7 @@
}
mAuthInterruptActive = active;
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+ requestActiveUnlock();
}
/**
@@ -2228,6 +2244,97 @@
}
}
+ /**
+ * 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 @@
&& !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 @@
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 @@
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 9dddbb1..c0da57f 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 @@
@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 4be819a..5d6c2a2 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 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 bfa4a24..dee1b33 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 ec2d036..571c10b 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 @@
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 0000000..6bc6505
--- /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 70792cf..7266e41 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.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 @@
private InteractionJankMonitor mInteractionJankMonitor;
@Mock
private LatencyTracker mLatencyTracker;
+ @Mock
+ private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
// Direct executor
@@ -1105,7 +1108,7 @@
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 0000000..8bc438b
--- /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 bc8da84..262933d 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.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 @@
+ 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 9b731d5..b3e46cd 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 @@
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 @@
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 6a716cb..066c263 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 @@
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 4822d6a..96391ac 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.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 @@
}
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 2e2d812..8097f4e 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 5455738..c548e7e 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 @@
/** 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 a0eeb65..399ae53 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 @@
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 05a51cc..31df0a5 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 @@
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/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 4d6b3c2..416b3a4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -70,6 +70,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.ExceptionUtils;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -121,7 +122,7 @@
public class PackageInstallerService extends IPackageInstaller.Stub implements
PackageSessionProvider {
private static final String TAG = "PackageInstaller";
- private static final boolean LOGD = false;
+ private static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
@@ -510,6 +511,7 @@
valid = true;
}
if (!valid) {
+ Slog.w(TAG, "Remove old session: " + session.sessionId);
// Remove expired sessions as well as child sessions if any
mSessions.remove(session.sessionId);
// Since this is early during boot we don't send
@@ -850,6 +852,9 @@
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
mSettingsWriteRequest.schedule();
+ if (LOGD) {
+ Slog.d(TAG, "Created session id=" + sessionId + " staged=" + params.isStaged);
+ }
return sessionId;
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 10d9bbf..c9a8701 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -1139,13 +1139,11 @@
private boolean shouldForceShowNotificationPermissionRequest(@NonNull String pkgName,
@NonNull UserHandle user) {
AndroidPackage pkg = mPackageManagerInternal.getPackage(pkgName);
- // TODO(b/205888750): Remove platform key and permissionController lines after pregrants
- // are in place
- if (pkg == null || pkg.getPackageName() == null || pkg.isSignedWithPlatformKey()
- || pkg.getPackageName().contains("nexuslauncher")
+ if (pkg == null || pkg.getPackageName() == null
|| Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName())
|| pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
- // TODO(b/205888750) add warning logs when pregrants in place
+ Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
+ + pkgName + " or pkg is Permission Controller");
return false;
}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index ef0079e..ca67597 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.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 @@
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 bb52c1d..9953ca8 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.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 @@
private StatsPullAtomCallbackImpl mPullAtomCallback;
@Nullable
private PowerStatsInternal mPowerStatsInternal;
+ @Nullable
+ @GuardedBy("this")
+ private Looper mLooper;
@VisibleForTesting
static class Injector {
@@ -127,12 +131,12 @@
}
}
- 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 @@
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 @@
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 @@
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 a2bf2fe..a4732c1 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 @@
}
@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 bd8d13b..6db25b7 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.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.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 @@
public static int calculatePriorityClass(
VcnContext vcnContext,
UnderlyingNetworkRecord networkRecord,
- LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+ List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected,
@@ -94,7 +96,7 @@
}
int priorityIndex = 0;
- for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkPriorities) {
+ for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) {
if (checkMatchesPriorityRule(
vcnContext,
nwPriority,
@@ -122,7 +124,11 @@
// 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 @@
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 @@
.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 @@
.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 df2f0d5..c0488b1 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.VcnContext;
import java.util.Comparator;
-import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Objects;
/**
@@ -77,7 +77,7 @@
static Comparator<UnderlyingNetworkRecord> getComparator(
VcnContext vcnContext,
- LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+ List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected,
@@ -87,7 +87,7 @@
NetworkPriorityClassifier.calculatePriorityClass(
vcnContext,
left,
- underlyingNetworkPriorities,
+ underlyingNetworkTemplates,
subscriptionGroup,
snapshot,
currentlySelected,
@@ -96,7 +96,7 @@
NetworkPriorityClassifier.calculatePriorityClass(
vcnContext,
right,
- underlyingNetworkPriorities,
+ underlyingNetworkTemplates,
subscriptionGroup,
snapshot,
currentlySelected,
@@ -133,7 +133,7 @@
void dump(
VcnContext vcnContext,
IndentingPrintWriter pw,
- LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+ List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected,
@@ -145,7 +145,7 @@
NetworkPriorityClassifier.calculatePriorityClass(
vcnContext,
this,
- underlyingNetworkPriorities,
+ underlyingNetworkTemplates,
subscriptionGroup,
snapshot,
currentlySelected,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1bafd49..ddd624d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -437,9 +437,9 @@
private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT =
START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * MINUTE_IN_MILLIS;
- // Activity tokens of system activities that are delegating their call to
- // #startActivityByCaller, keyed by the permissionToken granted to the delegate.
- final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>();
+ // The component name of the delegated activities that are allowed to call
+ // #startActivityAsCaller with the one-time used permission token.
+ final HashMap<IBinder, ComponentName> mStartActivitySources = new HashMap<>();
// Permission tokens that have expired, but we remember for error reporting.
final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>();
@@ -1513,7 +1513,7 @@
}
@Override
- public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
+ public IBinder requestStartActivityPermissionToken(ComponentName componentName) {
int callingUid = Binder.getCallingUid();
if (UserHandle.getAppId(callingUid) != SYSTEM_UID) {
throw new SecurityException("Only the system process can request a permission token, "
@@ -1521,7 +1521,7 @@
}
IBinder permissionToken = new Binder();
synchronized (mGlobalLock) {
- mStartActivitySources.put(permissionToken, delegatorToken);
+ mStartActivitySources.put(permissionToken, componentName);
}
Message expireMsg = PooledLambda.obtainMessage(
@@ -1546,7 +1546,7 @@
// 1) The caller is an activity that is part of the core framework, and then only when it
// is running as the system.
// 2) The caller provides a valid permissionToken. Permission tokens are one-time use and
- // can only be requested by a system activity, which may then delegate this call to
+ // can only be requested from system uid, which may then delegate this call to
// another app.
final ActivityRecord sourceRecord;
final int targetUid;
@@ -1557,18 +1557,26 @@
if (resultTo == null) {
throw new SecurityException("Must be called from an activity");
}
- final IBinder sourceToken;
+
+ sourceRecord = ActivityRecord.isInAnyTask(resultTo);
+ if (sourceRecord == null) {
+ throw new SecurityException("Called with bad activity token: " + resultTo);
+ }
+ if (sourceRecord.app == null) {
+ throw new SecurityException("Called without a process attached to activity");
+ }
+
+ final ComponentName componentName;
if (permissionToken != null) {
// To even attempt to use a permissionToken, an app must also have this signature
// permission.
mAmInternal.enforceCallingPermission(
android.Manifest.permission.START_ACTIVITY_AS_CALLER,
"startActivityAsCaller");
- // If called with a permissionToken, we want the sourceRecord from the delegator
- // activity that requested this token.
- sourceToken = mStartActivitySources.remove(permissionToken);
- if (sourceToken == null) {
- // Invalid permissionToken, check if it recently expired.
+ // If called with a permissionToken, the caller must be the same component that
+ // was allowed to use the permissionToken.
+ componentName = mStartActivitySources.remove(permissionToken);
+ if (!sourceRecord.mActivityComponent.equals(componentName)) {
if (mExpiredStartAsCallerTokens.contains(permissionToken)) {
throw new SecurityException("Called with expired permission token: "
+ permissionToken);
@@ -1578,33 +1586,21 @@
}
}
} else {
- // This method was called directly by the source.
- sourceToken = resultTo;
- }
-
- sourceRecord = ActivityRecord.isInAnyTask(sourceToken);
- if (sourceRecord == null) {
- throw new SecurityException("Called with bad activity token: " + sourceToken);
- }
- if (sourceRecord.app == null) {
- throw new SecurityException("Called without a process attached to activity");
- }
-
- // Whether called directly or from a delegate, the source activity must be from the
- // android package.
- if (!sourceRecord.info.packageName.equals("android")) {
- throw new SecurityException("Must be called from an activity that is "
- + "declared in the android package");
- }
-
- if (UserHandle.getAppId(sourceRecord.app.mUid) != SYSTEM_UID) {
- // This is still okay, as long as this activity is running under the
- // uid of the original calling activity.
- if (sourceRecord.app.mUid != sourceRecord.launchedFromUid) {
- throw new SecurityException(
- "Calling activity in uid " + sourceRecord.app.mUid
- + " must be system uid or original calling uid "
- + sourceRecord.launchedFromUid);
+ // Whether called directly or from a delegate, the source activity must be from the
+ // android package.
+ if (!sourceRecord.info.packageName.equals("android")) {
+ throw new SecurityException("Must be called from an activity that is "
+ + "declared in the android package");
+ }
+ if (UserHandle.getAppId(sourceRecord.app.mUid) != SYSTEM_UID) {
+ // This is still okay, as long as this activity is running under the
+ // uid of the original calling activity.
+ if (sourceRecord.app.mUid != sourceRecord.launchedFromUid) {
+ throw new SecurityException(
+ "Calling activity in uid " + sourceRecord.app.mUid
+ + " must be system uid or original calling uid "
+ + sourceRecord.launchedFromUid);
+ }
}
}
if (ignoreTargetSecurity) {
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 47622bc..9661e8d 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_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 @@
.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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fe5c358..ac90ceb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -310,6 +310,8 @@
"com.android.clockwork.connectivity.WearConnectivityService";
private static final String WEAR_POWER_SERVICE_CLASS =
"com.android.clockwork.power.WearPowerService";
+ private static final String HEALTH_SERVICE_CLASS =
+ "com.google.android.clockwork.healthservices.HealthService";
private static final String WEAR_SIDEKICK_SERVICE_CLASS =
"com.google.android.clockwork.sidekick.SidekickService";
private static final String WEAR_DISPLAYOFFLOAD_SERVICE_CLASS =
@@ -2499,6 +2501,10 @@
mSystemServiceManager.startService(WEAR_POWER_SERVICE_CLASS);
t.traceEnd();
+ t.traceBegin("StartHealthService");
+ mSystemServiceManager.startService(HEALTH_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("StartWearConnectivityService");
mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
t.traceEnd();
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 26b34fd..304fe5a 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.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 @@
}
@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 11777ef..d831903 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.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 @@
}
@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 0c8fe35..1362628 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 @@
}
@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 525888d..cb9eb52 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_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 @@
}
@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/AppLaunchWear/Android.bp b/tests/AppLaunchWear/Android.bp
deleted file mode 100644
index e2fc473..0000000
--- a/tests/AppLaunchWear/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
- name: "AppLaunchWear",
- // Only compile source java files in this apk.
- srcs: ["src/**/*.java"],
- platform_apis: true,
- certificate: "platform",
- libs: [
- "android.test.base",
- "android.test.runner",
- ],
- static_libs: ["androidx.test.rules"],
- test_suites: ["device-tests"],
-}
diff --git a/tests/AppLaunchWear/AndroidManifest.xml b/tests/AppLaunchWear/AndroidManifest.xml
deleted file mode 100644
index 7dfd7ba..0000000
--- a/tests/AppLaunchWear/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.applaunch"
- android:sharedUserId="android.uid.system" >
-
- <uses-permission android:name="android.permission.REAL_GET_TASKS" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
- <uses-sdk
- android:minSdkVersion="22"
- android:targetSdkVersion="24" />
-
- <instrumentation android:label="Measure app start up time"
- android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.tests.applaunch" />
-
- <application android:label="App Launch Test">
- <uses-library android:name="android.test.runner" />
- </application>
-</manifest>
diff --git a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
deleted file mode 100644
index 97701c6..0000000
--- a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
+++ /dev/null
@@ -1,864 +0,0 @@
-/*
- * Copyright (C) 2013 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.tests.applaunch;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.ActivityManager;
-import android.app.ActivityManager.ProcessErrorStateInfo;
-import android.app.IActivityManager;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.test.InstrumentationTestCase;
-import android.test.InstrumentationTestRunner;
-import android.util.Log;
-
-import androidx.test.rule.logging.AtraceLogger;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * This test is intended to measure the time it takes for the apps to start.
- * Names of the applications are passed in command line, and the
- * test starts each application, and reports the start up time in milliseconds.
- * The instrumentation expects the following key to be passed on the command line:
- * apps - A list of applications to start and their corresponding result keys
- * in the following format:
- * -e apps <app name>^<result key>|<app name>^<result key>
- */
-public class AppLaunch extends InstrumentationTestCase {
-
- private static final int JOIN_TIMEOUT = 10000;
- private static final String TAG = AppLaunch.class.getSimpleName();
-
- // optional parameter: comma separated list of required account types before proceeding
- // with the app launch
- private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts";
- private static final String KEY_APPS = "apps";
- private static final String KEY_TRIAL_LAUNCH = "trial_launch";
- private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
- private static final String KEY_LAUNCH_ORDER = "launch_order";
- private static final String KEY_DROP_CACHE = "drop_cache";
- private static final String KEY_SIMPLEPERF_CMD = "simpleperf_cmd";
- private static final String KEY_SIMPLEPERF_APP = "simpleperf_app";
- private static final String KEY_TRACE_ITERATIONS = "trace_iterations";
- private static final String KEY_LAUNCH_DIRECTORY = "launch_directory";
- private static final String KEY_TRACE_DIRECTORY = "trace_directory";
- private static final String KEY_TRACE_CATEGORY = "trace_categories";
- private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize";
- private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval";
- private static final String KEY_COMPILER_FILTERS = "compiler_filters";
-
- private static final String SIMPLEPERF_APP_CMD =
- "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s";
- private static final String WEARABLE_ACTION_GOOGLE =
- "com.google.android.wearable.action.GOOGLE";
- private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 5000; // 5s to allow app to idle
- private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; // 750ms idle for non initial launches
- private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 5000; // 5s between launching apps
- private static final String LAUNCH_SUB_DIRECTORY = "launch_logs";
- private static final String LAUNCH_FILE = "applaunch.txt";
- private static final String TRACE_SUB_DIRECTORY = "atrace_logs";
- private static final String DEFAULT_TRACE_CATEGORIES =
- "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm";
- private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000";
- private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10";
- private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH";
- private static final String DELIMITER = ",";
- private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh";
- private static final String APP_LAUNCH_CMD = "am start -W -n";
- private static final String SUCCESS_MESSAGE = "Status: ok";
- private static final String WARNING_MESSAGE = "Warning: Activity not started";
- private static final String COMPILE_SUCCESS = "Success";
- private static final String THIS_TIME = "ThisTime:";
- private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d";
- private static final String TRACE_ITERATION = "TRACE_ITERATION-%d";
- private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION";
- private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION";
- private static final String LAUNCH_ORDER_CYCLIC = "cyclic";
- private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential";
- private static final String COMPILE_CMD = "cmd package compile -f -m %s %s";
- private static final String SPEED_PROFILE_FILTER = "speed-profile";
- private static final String VERIFY_FILTER = "verify";
- private static final String LAUNCH_SCRIPT_NAME = "appLaunch";
- private static final String WEARABLE_HOME_PACKAGE = "com.google.android.wearable.app";
-
- private Map<String, Intent> mNameToIntent;
- private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>();
- private Map<String, String> mNameToResultKey;
- private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime;
- private IActivityManager mAm;
- private String mSimplePerfCmd = null;
- private String mLaunchOrder = null;
- private boolean mDropCache = false;
- private int mLaunchIterations = 10;
- private int mTraceLaunchCount = 0;
- private String mTraceDirectoryStr = null;
- private Bundle mResult = new Bundle();
- private Set<String> mRequiredAccounts;
- private boolean mTrialLaunch = false;
- private BufferedWriter mBufferedWriter = null;
- private boolean mSimplePerfAppOnly = false;
- private String[] mCompilerFilters = null;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
- }
-
- @Override
- protected void tearDown() throws Exception {
- getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
- super.tearDown();
- }
-
- private void addLaunchResult(LaunchOrder launch, AppLaunchResult result) {
- mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()).add(result);
- }
-
- private boolean hasFailureOnFirstLaunch(LaunchOrder launch) {
- List<AppLaunchResult> results =
- mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter());
- return (results.size() > 0) && (results.get(0).mLaunchTime < 0);
- }
-
- public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException,
- IOException, InterruptedException {
- InstrumentationTestRunner instrumentation =
- (InstrumentationTestRunner)getInstrumentation();
- Bundle args = instrumentation.getArguments();
- mAm = ActivityManager.getService();
- String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY);
-
- createMappings();
- parseArgs(args);
- checkAccountSignIn();
-
- // Root directory for applaunch file to log the app launch output
- // Will be useful in case of simpleperf command is used
- File launchRootDir = null;
- if (null != launchDirectory && !launchDirectory.isEmpty()) {
- launchRootDir = new File(launchDirectory);
- if (!launchRootDir.exists() && !launchRootDir.mkdirs()) {
- throw new IOException("Unable to create the destination directory");
- }
- }
-
- try {
- File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY);
-
- if (!launchSubDir.exists() && !launchSubDir.mkdirs()) {
- throw new IOException("Unable to create the lauch file sub directory");
- }
- File file = new File(launchSubDir, LAUNCH_FILE);
- FileOutputStream outputStream = new FileOutputStream(file);
- mBufferedWriter = new BufferedWriter(new OutputStreamWriter(
- outputStream));
-
- // Root directory for trace file during the launches
- File rootTrace = null;
- File rootTraceSubDir = null;
- int traceBufferSize = 0;
- int traceDumpInterval = 0;
- Set<String> traceCategoriesSet = null;
- if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) {
- rootTrace = new File(mTraceDirectoryStr);
- if (!rootTrace.exists() && !rootTrace.mkdirs()) {
- throw new IOException("Unable to create the trace directory");
- }
- rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY);
- if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) {
- throw new IOException("Unable to create the trace sub directory");
- }
- assertNotNull("Trace iteration parameter is mandatory",
- args.getString(KEY_TRACE_ITERATIONS));
- mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS));
- String traceCategoriesStr = args
- .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES);
- traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE,
- DEFAULT_TRACE_BUFFER_SIZE));
- traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL,
- DEFAULT_TRACE_DUMP_INTERVAL));
- traceCategoriesSet = new HashSet<String>();
- if (!traceCategoriesStr.isEmpty()) {
- String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER);
- for (int i = 0; i < traceCategoriesSplit.length; i++) {
- traceCategoriesSet.add(traceCategoriesSplit[i]);
- }
- }
- }
-
- // Get the app launch order based on launch order, trial launch,
- // launch iterations and trace iterations
- setLaunchOrder();
-
- for (LaunchOrder launch : mLaunchOrderList) {
- if (mNameToIntent.get(launch.getApp()) == null) {
- continue;
- }
- dropCache();
- String appPkgName = mNameToIntent.get(launch.getApp())
- .getComponent().getPackageName();
- Log.v(TAG, String.format("\nApp name: %s", launch.getApp()));
- Log.v(TAG, String.format("Adding app package name: %s", appPkgName));
- // App launch times for trial launch will not be used for final
- // launch time calculations.
- if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) {
- // In the "applaunch.txt" file, trail launches is referenced using
- // "TRIAL_LAUNCH"
- Log.v(TAG, "Trial Launch");
- if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) {
- assertTrue(String.format("Not able to compile the app : %s", appPkgName),
- compileApp(VERIFY_FILTER, appPkgName));
- } else if (launch.getCompilerFilter() != null) {
- assertTrue(String.format("Not able to compile the app : %s", appPkgName),
- compileApp(launch.getCompilerFilter(), appPkgName));
- }
- // We only need to run a trial for the speed-profile filter, but we always
- // run one for "applaunch.txt" consistency.
- AppLaunchResult launchResult = null;
- if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
- Log.v(TAG, "Home package detected. Not killing app");
- launchResult = startApp(launch.getApp(), false, launch.getLaunchReason());
- } else {
- Log.v(TAG, "Will kill app before launch");
- launchResult = startApp(launch.getApp(), true, launch.getLaunchReason());
- }
- if (launchResult.mLaunchTime < 0) {
- addLaunchResult(launch, new AppLaunchResult());
- // simply pass the app if launch isn't successful
- // error should have already been logged by startApp
- continue;
- }
- sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
- if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) {
- // Send SIGUSR1 to force dumping a profile.
- String sendSignalCommand =
- String.format("killall -s SIGUSR1 %s", appPkgName);
- getInstrumentation().getUiAutomation().executeShellCommand(
- sendSignalCommand);
- assertTrue(String.format("Not able to compile the app : %s", appPkgName),
- compileApp(launch.getCompilerFilter(), appPkgName));
- }
- }
-
- // App launch times used for final calculation
- else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) {
- Log.v(TAG, "Launch iteration prefix.");
- AppLaunchResult launchResults = null;
- if (hasFailureOnFirstLaunch(launch)) {
- // skip if the app has failures while launched first
- continue;
- }
- // In the "applaunch.txt" file app launches are referenced using
- // "LAUNCH_ITERATION - ITERATION NUM"
- if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
- Log.v(TAG, "Home package detected. Not killing app");
- launchResults = startApp(launch.getApp(), false, launch.getLaunchReason());
- } else {
- Log.v(TAG, "Will kill app before launch");
- launchResults = startApp(launch.getApp(), true, launch.getLaunchReason());
- }
- if (launchResults.mLaunchTime < 0) {
- addLaunchResult(launch, new AppLaunchResult());
- // if it fails once, skip the rest of the launches
- continue;
- } else {
- addLaunchResult(launch, launchResults);
- }
- sleep(POST_LAUNCH_IDLE_TIMEOUT);
- }
-
- // App launch times for trace launch will not be used for final
- // launch time calculations.
- else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) {
- Log.v(TAG, "Trace iteration prefix");
- AtraceLogger atraceLogger = AtraceLogger
- .getAtraceLoggerInstance(getInstrumentation());
- // Start the trace
- try {
- atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize,
- traceDumpInterval, rootTraceSubDir,
- String.format("%s-%s", launch.getApp(), launch.getLaunchReason()));
- if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
- Log.v(TAG, "Home package detected. Not killing app");
- startApp(launch.getApp(), false, launch.getLaunchReason());
- } else {
- Log.v(TAG, "Will kill app before launch");
- startApp(launch.getApp(), true, launch.getLaunchReason());
- }
- sleep(POST_LAUNCH_IDLE_TIMEOUT);
- } finally {
- // Stop the trace
- atraceLogger.atraceStop();
- }
- }
- closeApp(launch.getApp(), true);
- sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
- }
- } finally {
- if (null != mBufferedWriter) {
- mBufferedWriter.close();
- }
- }
-
- for (String app : mNameToResultKey.keySet()) {
- for (String compilerFilter : mCompilerFilters) {
- StringBuilder launchTimes = new StringBuilder();
- StringBuilder cpuCycles = new StringBuilder();
- StringBuilder majorFaults = new StringBuilder();
- for (AppLaunchResult result : mNameToLaunchTime.get(app).get(compilerFilter)) {
- launchTimes.append(result.mLaunchTime);
- launchTimes.append(",");
- if (mSimplePerfAppOnly) {
- cpuCycles.append(result.mCpuCycles);
- cpuCycles.append(",");
- majorFaults.append(result.mMajorFaults);
- majorFaults.append(",");
- }
- }
- String filterName = (compilerFilter == null) ? "" : ("-" + compilerFilter);
- mResult.putString(mNameToResultKey.get(app) + filterName, launchTimes.toString());
- if (mSimplePerfAppOnly) {
- mResult.putString(mNameToResultKey.get(app) + filterName + "-cpuCycles",
- cpuCycles.toString());
- mResult.putString(mNameToResultKey.get(app) + filterName + "-majorFaults",
- majorFaults.toString());
- }
- }
- }
- instrumentation.sendStatus(0, mResult);
- }
-
- /**
- * Compile the app package using compilerFilter and return true or false
- * based on status of the compilation command.
- */
- private boolean compileApp(String compilerFilter, String appPkgName) throws IOException {
- try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation().
- executeShellCommand(String.format(COMPILE_CMD, compilerFilter, appPkgName));
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
- new FileInputStream(result.getFileDescriptor())))) {
- String line;
- while ((line = bufferedReader.readLine()) != null) {
- if (line.contains(COMPILE_SUCCESS)) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * If launch order is "cyclic" then apps will be launched one after the
- * other for each iteration count.
- * If launch order is "sequential" then each app will be launched for given number
- * iterations at once before launching the other apps.
- */
- private void setLaunchOrder() {
- if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) {
- for (String compilerFilter : mCompilerFilters) {
- if (mTrialLaunch) {
- for (String app : mNameToResultKey.keySet()) {
- mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH));
- }
- }
- for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) {
- for (String app : mNameToResultKey.keySet()) {
- mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
- String.format(LAUNCH_ITERATION, launchCount)));
- }
- }
- if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) {
- for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) {
- for (String app : mNameToResultKey.keySet()) {
- mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
- String.format(TRACE_ITERATION, traceCount)));
- }
- }
- }
- }
- } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) {
- for (String compilerFilter : mCompilerFilters) {
- for (String app : mNameToResultKey.keySet()) {
- if (mTrialLaunch) {
- mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH));
- }
- for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) {
- mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
- String.format(LAUNCH_ITERATION, launchCount)));
- }
- if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) {
- for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) {
- mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
- String.format(TRACE_ITERATION, traceCount)));
- }
- }
- }
- }
- } else {
- assertTrue("Launch order is not valid parameter", false);
- }
- }
-
- private void dropCache() {
- if (mDropCache) {
- assertNotNull("Issue in dropping the cache",
- getInstrumentation().getUiAutomation()
- .executeShellCommand(DROP_CACHE_SCRIPT));
- }
- }
-
- private void parseArgs(Bundle args) {
- mNameToResultKey = new LinkedHashMap<String, String>();
- mNameToLaunchTime = new HashMap<>();
- String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS);
- if (launchIterations != null) {
- mLaunchIterations = Integer.parseInt(launchIterations);
- }
- String appList = args.getString(KEY_APPS);
- if (appList == null)
- return;
-
- String appNames[] = appList.split("\\|");
- for (String pair : appNames) {
- String[] parts = pair.split("\\^");
- if (parts.length != 2) {
- Log.e(TAG, "The apps key is incorrectly formatted");
- fail();
- }
-
- mNameToResultKey.put(parts[0], parts[1]);
- mNameToLaunchTime.put(parts[0], null);
- }
- String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS);
- if (requiredAccounts != null) {
- mRequiredAccounts = new HashSet<String>();
- for (String accountType : requiredAccounts.split(",")) {
- mRequiredAccounts.add(accountType);
- }
- }
-
- String compilerFilterList = args.getString(KEY_COMPILER_FILTERS);
- if (compilerFilterList != null) {
- // If a compiler filter is passed, we make a trial launch to force compilation
- // of the apps.
- mTrialLaunch = true;
- mCompilerFilters = compilerFilterList.split("\\|");
- } else {
- // Just pass a null compiler filter to use the current state of the app.
- mCompilerFilters = new String[1];
- }
-
- // Pre-populate the results map to avoid null checks.
- for (String app : mNameToLaunchTime.keySet()) {
- HashMap<String, List<AppLaunchResult>> map = new HashMap<>();
- mNameToLaunchTime.put(app, map);
- for (String compilerFilter : mCompilerFilters) {
- map.put(compilerFilter, new ArrayList<>());
- }
- }
-
- mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY);
- mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE));
- mSimplePerfCmd = args.getString(KEY_SIMPLEPERF_CMD);
- mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC);
- mSimplePerfAppOnly = Boolean.parseBoolean(args.getString(KEY_SIMPLEPERF_APP));
- mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH));
-
- if (mSimplePerfCmd != null && mSimplePerfAppOnly) {
- Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s",
- KEY_SIMPLEPERF_CMD, KEY_SIMPLEPERF_APP));
- }
- }
-
- private boolean hasLeanback(Context context) {
- return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
- }
-
- private void createMappings() {
- mNameToIntent = new LinkedHashMap<String, Intent>();
-
- PackageManager pm = getInstrumentation().getContext()
- .getPackageManager();
- Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
- intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ?
- Intent.CATEGORY_LEANBACK_LAUNCHER :
- Intent.CATEGORY_LAUNCHER);
- List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
- resolveLoop(ris, intentToResolve, pm);
- // For Wear
- intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE);
- ris = pm.queryIntentActivities(intentToResolve, 0);
- resolveLoop(ris, intentToResolve, pm);
- }
-
- private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) {
- if (ris == null || ris.isEmpty()) {
- Log.i(TAG, "Could not find any apps");
- } else {
- for (ResolveInfo ri : ris) {
- Intent startIntent = new Intent(intentToResolve);
- startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- startIntent.setClassName(ri.activityInfo.packageName,
- ri.activityInfo.name);
- String appName = ri.loadLabel(pm).toString();
- if (appName != null) {
- // Support launching intent using package name or app name
- mNameToIntent.put(ri.activityInfo.packageName, startIntent);
- mNameToIntent.put(appName, startIntent);
- }
- }
- }
- }
-
- private AppLaunchResult startApp(String appName, boolean forceStopBeforeLaunch,
- String launchReason) throws NameNotFoundException, RemoteException {
- Log.i(TAG, "Starting " + appName);
-
- Intent startIntent = mNameToIntent.get(appName);
- if (startIntent == null) {
- Log.w(TAG, "App does not exist: " + appName);
- mResult.putString(mNameToResultKey.get(appName), "App does not exist");
- return new AppLaunchResult();
- }
- AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch,
- launchReason);
- Thread t = new Thread(runnable);
- t.start();
- try {
- t.join(JOIN_TIMEOUT);
- } catch (InterruptedException e) {
- // ignore
- }
- return runnable.getResult();
- }
-
- private void checkAccountSignIn() {
- // ensure that the device has the required account types before starting test
- // e.g. device must have a valid Google account sign in to measure a meaningful launch time
- // for Gmail
- if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) {
- return;
- }
- final AccountManager am =
- (AccountManager) getInstrumentation().getTargetContext().getSystemService(
- Context.ACCOUNT_SERVICE);
- Account[] accounts = am.getAccounts();
- // use set here in case device has multiple accounts of the same type
- Set<String> foundAccounts = new HashSet<String>();
- for (Account account : accounts) {
- if (mRequiredAccounts.contains(account.type)) {
- foundAccounts.add(account.type);
- }
- }
- // check if account type matches, if not, fail test with message on what account types
- // are missing
- if (mRequiredAccounts.size() != foundAccounts.size()) {
- mRequiredAccounts.removeAll(foundAccounts);
- StringBuilder sb = new StringBuilder("Device missing these accounts:");
- for (String account : mRequiredAccounts) {
- sb.append(' ');
- sb.append(account);
- }
- fail(sb.toString());
- }
- }
-
- private void closeApp(String appName, boolean forceStopApp) {
- Intent homeIntent = new Intent(Intent.ACTION_MAIN);
- homeIntent.addCategory(Intent.CATEGORY_HOME);
- homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- getInstrumentation().getContext().startActivity(homeIntent);
- sleep(POST_LAUNCH_IDLE_TIMEOUT);
- if (forceStopApp) {
- Intent startIntent = mNameToIntent.get(appName);
- if (startIntent != null) {
- String packageName = startIntent.getComponent().getPackageName();
- try {
- mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
- } catch (RemoteException e) {
- Log.w(TAG, "Error closing app", e);
- }
- }
- }
- }
-
- private void sleep(int time) {
- try {
- Thread.sleep(time);
- } catch (InterruptedException e) {
- // ignore
- }
- }
-
- private void reportError(String appName, String processName) {
- ActivityManager am = (ActivityManager) getInstrumentation()
- .getContext().getSystemService(Context.ACTIVITY_SERVICE);
- List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
- if (crashes != null) {
- for (ProcessErrorStateInfo crash : crashes) {
- if (!crash.processName.equals(processName))
- continue;
-
- Log.w(TAG, appName + " crashed: " + crash.shortMsg);
- mResult.putString(mNameToResultKey.get(appName), crash.shortMsg);
- return;
- }
- }
-
- mResult.putString(mNameToResultKey.get(appName),
- "Crashed for unknown reason");
- Log.w(TAG, appName
- + " not found in process list, most likely it is crashed");
- }
-
- private class LaunchOrder {
- private String mApp;
- private String mCompilerFilter;
- private String mLaunchReason;
-
- LaunchOrder(String app, String compilerFilter, String launchReason){
- mApp = app;
- mCompilerFilter = compilerFilter;
- mLaunchReason = launchReason;
- }
-
- public String getApp() {
- return mApp;
- }
-
- public void setApp(String app) {
- mApp = app;
- }
-
- public String getCompilerFilter() {
- return mCompilerFilter;
- }
-
- public String getLaunchReason() {
- return mLaunchReason;
- }
-
- public void setLaunchReason(String launchReason) {
- mLaunchReason = launchReason;
- }
- }
-
- private class AppLaunchResult {
- long mLaunchTime;
- long mCpuCycles;
- long mMajorFaults;
-
- AppLaunchResult() {
- mLaunchTime = -1L;
- mCpuCycles = -1L;
- mMajorFaults = -1L;
- }
-
- AppLaunchResult(String launchTime, String cpuCycles, String majorFaults) {
- try {
- mLaunchTime = Long.parseLong(launchTime, 10);
- mCpuCycles = Long.parseLong(cpuCycles, 10);
- mMajorFaults = Long.parseLong(majorFaults, 10);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Error parsing result", e);
- }
- }
- }
-
- private class AppLaunchRunnable implements Runnable {
- private Intent mLaunchIntent;
- private AppLaunchResult mLaunchResult;
- private boolean mForceStopBeforeLaunch;
- private String mLaunchReason;
-
- public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch,
- String launchReason) {
- mLaunchIntent = intent;
- mForceStopBeforeLaunch = forceStopBeforeLaunch;
- mLaunchReason = launchReason;
- mLaunchResult = new AppLaunchResult();
- }
-
- public AppLaunchResult getResult() {
- return mLaunchResult;
- }
-
- public void run() {
- File launchFile = null;
- try {
- String packageName = mLaunchIntent.getComponent().getPackageName();
- String componentName = mLaunchIntent.getComponent().flattenToShortString();
- if (mForceStopBeforeLaunch) {
- Log.v(TAG, "Stopping app before launch");
- mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
- } else {
- Log.v(TAG, "Not killing app. Going to Home Screen.");
- ParcelFileDescriptor goHome = getInstrumentation().getUiAutomation()
- .executeShellCommand("input keyevent 3");
- }
- String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName);
- if (mSimplePerfAppOnly) {
- try {
- // executeShellCommand cannot handle shell specific actions, like '&'.
- // Therefore, we create a file containing the command and make that
- // the command to launch.
- launchFile = File.createTempFile(LAUNCH_SCRIPT_NAME, ".sh");
- launchFile.setExecutable(true);
- try (FileOutputStream stream = new FileOutputStream(launchFile);
- BufferedWriter writer =
- new BufferedWriter(new OutputStreamWriter(stream))) {
- String cmd = String.format(SIMPLEPERF_APP_CMD, packageName, launchCmd);
- writer.write(cmd);
- }
- launchCmd = launchFile.getAbsolutePath();
- } catch (IOException e) {
- Log.w(TAG, "Error writing the launch command", e);
- return;
- }
- } else if (null != mSimplePerfCmd) {
- launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd);
- }
- Log.v(TAG, "Final launch cmd:" + launchCmd);
- ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation()
- .executeShellCommand(launchCmd);
- mLaunchResult = parseLaunchTimeAndWrite(parcelDesc, String.format
- ("App Launch :%s %s", componentName, mLaunchReason));
- } catch (RemoteException e) {
- Log.w(TAG, "Error launching app", e);
- } finally {
- if (launchFile != null) {
- launchFile.delete();
- }
- }
- }
-
- /**
- * Method to parse the launch time info and write the result to file
- *
- * @param parcelDesc
- * @return
- */
- private AppLaunchResult parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc,
- String headerInfo) {
- String launchTime = "-1";
- String cpuCycles = "-1";
- String majorFaults = "-1";
- boolean launchSuccess = false;
- try {
- InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor());
- /* SAMPLE OUTPUT :
- Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator }
- Status: ok
- Activity: com.google.android.calculator/com.android.calculator2.Calculator
- ThisTime: 357
- TotalTime: 357
- WaitTime: 377
- Complete*/
- /* WHEN NOT KILLING HOME :
- Starting: Intent { cmp=com.google.android.wearable.app/
- com.google.android.clockwork.home.calendar.AgendaActivity }
- Warning: Activity not started, its current task has been brought to the front
- Status: ok
- Activity: com.google.android.wearable.app/
- com.google.android.clockwork.home.calendar.AgendaActivity
- ThisTime: 209
- TotalTime: 209
- WaitTime: 285
- Complete*/
- /* WITH SIMPLEPERF :
- Performance counter statistics,
- 6595722690,cpu-cycles,4.511040,GHz,(100%),
- 0,major-faults,0.000,/sec,(100%),
- Total test time,1.462129,seconds,*/
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
- inputStream));
- String line = null;
- int lineCount = 1;
- int addLineForWarning = 0;
- mBufferedWriter.newLine();
- mBufferedWriter.write(headerInfo);
- mBufferedWriter.newLine();
- while ((line = bufferedReader.readLine()) != null) {
- if (lineCount == 2 && line.contains(WARNING_MESSAGE)) {
- addLineForWarning = 1;
- }
- if (lineCount == (2 + addLineForWarning) && line.contains(SUCCESS_MESSAGE)) {
- launchSuccess = true;
- }
- // Parse TotalTime which is the launch time
- if (launchSuccess && lineCount == (5 + addLineForWarning)) {
- String launchSplit[] = line.split(":");
- launchTime = launchSplit[1].trim();
- }
-
- if (mSimplePerfAppOnly) {
- // Parse simpleperf output.
- if (lineCount == (9 + addLineForWarning)) {
- if (!line.contains("cpu-cycles")) {
- Log.e(TAG, "Error in simpleperf output");
- } else {
- cpuCycles = line.split(",")[0].trim();
- }
- } else if (lineCount == (10 + addLineForWarning)) {
- if (!line.contains("major-faults")) {
- Log.e(TAG, "Error in simpleperf output");
- } else {
- majorFaults = line.split(",")[0].trim();
- }
- }
- }
- mBufferedWriter.write(line);
- mBufferedWriter.newLine();
- lineCount++;
- }
- mBufferedWriter.flush();
- inputStream.close();
- } catch (IOException e) {
- Log.w(TAG, "Error writing the launch file", e);
- }
- return new AppLaunchResult(launchTime, cpuCycles, majorFaults);
- }
-
- }
-}
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
index d03aee2..4a724b7 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 @@
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 @@
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 1f2905d..2aef9ae 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 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 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 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 @@
}
@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 @@
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 @@
}
@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 @@
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 652057f..cb5b47b 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 @@
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 @@
@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 f23d5bf..4bb7de8 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 @@
final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
new VcnWifiUnderlyingNetworkTemplate.Builder()
.setNetworkQuality(NETWORK_QUALITY_OK)
- .setAllowMetered(false /* allowMetered */)
+ .setMetered(MATCH_FORBIDDEN)
.build();
assertFalse(
@@ -164,7 +166,6 @@
final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
new VcnWifiUnderlyingNetworkTemplate.Builder()
.setNetworkQuality(NETWORK_QUALITY_OK)
- .setAllowMetered(true /* allowMetered */)
.build();
final UnderlyingNetworkRecord selectedNetworkRecord =
isSelectedNetwork ? mWifiNetworkRecord : null;
@@ -214,8 +215,7 @@
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 @@
}
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 @@
@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 @@
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 @@
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 @@
@Test
public void testMatchWifiFailWithoutNotRoamingBit() {
final VcnCellUnderlyingNetworkTemplate networkPriority =
- getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build();
+ getCellNetworkPriorityBuilder().setRoaming(MATCH_FORBIDDEN).build();
assertFalse(
checkMatchesCellPriorityRule(
@@ -353,7 +348,7 @@
calculatePriorityClass(
mVcnContext,
networkRecord,
- VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
null /* currentlySelected */,