Merge "[Output Switcher] Add RouteListingPreference.Item in MediaDevice"
diff --git a/core/api/current.txt b/core/api/current.txt
index 5f3b1f3..dd3d6eb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -125,6 +125,18 @@
field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS";
field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL";
field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL";
+ field public static final String MANAGE_DEVICE_POLICY_APPS_CONTROL = "android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL";
+ field public static final String MANAGE_DEVICE_POLICY_APP_RESTRICTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS";
+ field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE";
+ field public static final String MANAGE_DEVICE_POLICY_CALLS = "android.permission.MANAGE_DEVICE_POLICY_CALLS";
+ field public static final String MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES = "android.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES";
+ field public static final String MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES = "android.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES";
+ field public static final String MANAGE_DEVICE_POLICY_LOCK_TASK = "android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK";
+ field public static final String MANAGE_DEVICE_POLICY_MODIFY_USERS = "android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS";
+ field public static final String MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY = "android.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY";
+ field public static final String MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS = "android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS";
+ field public static final String MANAGE_DEVICE_POLICY_SAFE_BOOT = "android.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT";
+ field public static final String MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE = "android.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE";
field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
@@ -7535,6 +7547,7 @@
method @Nullable public android.app.admin.PackagePolicy getManagedProfileCallerIdAccessPolicy();
method @Nullable public android.app.admin.PackagePolicy getManagedProfileContactsAccessPolicy();
method public long getManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName);
+ method @NonNull public android.app.admin.ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy();
method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName);
method public long getMaximumTimeToLock(@Nullable android.content.ComponentName);
method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName);
@@ -7685,6 +7698,7 @@
method public void setManagedProfileCallerIdAccessPolicy(@Nullable android.app.admin.PackagePolicy);
method public void setManagedProfileContactsAccessPolicy(@Nullable android.app.admin.PackagePolicy);
method public void setManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName, long);
+ method public void setManagedSubscriptionsPolicy(@Nullable android.app.admin.ManagedSubscriptionsPolicy);
method public void setMasterVolumeMuted(@NonNull android.content.ComponentName, boolean);
method public void setMaximumFailedPasswordsForWipe(@NonNull android.content.ComponentName, int);
method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
@@ -7987,6 +8001,16 @@
method public java.time.MonthDay getStart();
}
+ public final class ManagedSubscriptionsPolicy implements android.os.Parcelable {
+ ctor public ManagedSubscriptionsPolicy(int);
+ method public int describeContents();
+ method public int getPolicyType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedSubscriptionsPolicy> CREATOR;
+ field public static final int TYPE_ALL_MANAGED_SUBSCRIPTIONS = 1; // 0x1
+ field public static final int TYPE_ALL_PERSONAL_SUBSCRIPTIONS = 0; // 0x0
+ }
+
public abstract class NetworkEvent implements android.os.Parcelable {
method public int describeContents();
method public long getId();
@@ -23773,6 +23797,8 @@
method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
+ method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int);
method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int);
method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int);
@@ -24314,15 +24340,19 @@
public final class RouteListingPreference implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.content.ComponentName getInAppOnlyItemRoutingReceiver();
method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
method public boolean getUseSystemOrdering();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference> CREATOR;
+ field public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
}
public static final class RouteListingPreference.Builder {
ctor public RouteListingPreference.Builder();
method @NonNull public android.media.RouteListingPreference build();
+ method @NonNull public android.media.RouteListingPreference.Builder setInAppOnlyItemRoutingReceiver(@Nullable android.content.ComponentName);
method @NonNull public android.media.RouteListingPreference.Builder setItems(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
method @NonNull public android.media.RouteListingPreference.Builder setUseSystemOrdering(boolean);
}
@@ -24337,6 +24367,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
field public static final int DISABLE_REASON_AD = 3; // 0x3
field public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2; // 0x2
+ field public static final int DISABLE_REASON_IN_APP_ONLY = 4; // 0x4
field public static final int DISABLE_REASON_NONE = 0; // 0x0
field public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1; // 0x1
field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index de03ba4..484ef4f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10997,6 +10997,51 @@
}
/**
+ * Called by a profile owner of an organization-owned device to specify {@link
+ * ManagedSubscriptionsPolicy}
+ *
+ * <p>Managed subscriptions policy controls how SIMs would be associated with the
+ * managed profile. For example a policy of type
+ * {@link ManagedSubscriptionsPolicy#TYPE_ALL_MANAGED_SUBSCRIPTIONS} assigns all
+ * SIM-based subscriptions to the managed profile. In this case OEM default
+ * dialer and messages app are automatically installed in the managed profile
+ * and all incoming and outgoing calls and text messages are handled by them.
+ * <p>This API can only be called during device setup.
+ *
+ * @param policy {@link ManagedSubscriptionsPolicy} policy, passing null for this resets the
+ * policy to be the default.
+ * @throws SecurityException if the caller is not a profile owner on an organization-owned
+ * managed profile.
+ * @throws IllegalStateException if called after the device setup has been completed.
+ * @see ManagedSubscriptionsPolicy
+ */
+ public void setManagedSubscriptionsPolicy(@Nullable ManagedSubscriptionsPolicy policy) {
+ throwIfParentInstance("setManagedSubscriptionsPolicy");
+ try {
+ mService.setManagedSubscriptionsPolicy(policy);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current {@link ManagedSubscriptionsPolicy}.
+ * If the policy has not been set, it will return a default policy of Type {@link
+ * ManagedSubscriptionsPolicy#TYPE_ALL_PERSONAL_SUBSCRIPTIONS}.
+ *
+ * @see #setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy)
+ */
+ @NonNull
+ public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
+ throwIfParentInstance("getManagedSubscriptionsPolicy");
+ try {
+ return mService.getManagedSubscriptionsPolicy();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Similar to {@link #logoutUser(ComponentName)}, except:
*
* <ul>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 26d7667..20695ca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -34,6 +34,7 @@
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.ManagedProfileProvisioningParams;
import android.app.admin.FullyManagedDeviceProvisioningParams;
+import android.app.admin.ManagedSubscriptionsPolicy;
import android.app.admin.WifiSsidPolicy;
import android.content.ComponentName;
import android.content.Intent;
@@ -583,4 +584,7 @@
void setMtePolicy(int flag);
int getMtePolicy();
+
+ void setManagedSubscriptionsPolicy(in ManagedSubscriptionsPolicy policy);
+ ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy();
}
diff --git a/core/java/android/app/timedetector/TimePoint.aidl b/core/java/android/app/admin/ManagedSubscriptionsPolicy.aidl
similarity index 74%
rename from core/java/android/app/timedetector/TimePoint.aidl
rename to core/java/android/app/admin/ManagedSubscriptionsPolicy.aidl
index 80d4bc1..f634f11 100644
--- a/core/java/android/app/timedetector/TimePoint.aidl
+++ b/core/java/android/app/admin/ManagedSubscriptionsPolicy.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2022, The Android Open Source Project
+ * 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
+ * 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,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.app.admin;
-parcelable TimePoint;
+parcelable ManagedSubscriptionsPolicy;
\ No newline at end of file
diff --git a/core/java/android/app/admin/ManagedSubscriptionsPolicy.java b/core/java/android/app/admin/ManagedSubscriptionsPolicy.java
new file mode 100644
index 0000000..1098c38
--- /dev/null
+++ b/core/java/android/app/admin/ManagedSubscriptionsPolicy.java
@@ -0,0 +1,156 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A policy class that describes how managed SIM subscriptions should behave on the device.
+ */
+public final class ManagedSubscriptionsPolicy implements Parcelable {
+ private static final String TAG = "ManagedSubscriptionsPolicy";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"TYPE_"}, value = {TYPE_ALL_PERSONAL_SUBSCRIPTIONS,
+ TYPE_ALL_MANAGED_SUBSCRIPTIONS})
+ @interface ManagedSubscriptionsPolicyType {
+ }
+
+ /**
+ * Represents default policy to not have any managed subscriptions on the device.
+ */
+ public static final int TYPE_ALL_PERSONAL_SUBSCRIPTIONS = 0;
+
+ /**
+ * Represents policy to have only managed subscriptions on the device, any existing and
+ * future subscriptions on the device are exclusively associated with the managed profile.
+ *
+ * <p>When a subscription is associated with the managed profile, incoming/outgoing calls and
+ * text message using that subscription would only work via apps on managed profile.
+ * Also, Call logs and messages would be accessible only from the managed profile.
+ */
+ public static final int TYPE_ALL_MANAGED_SUBSCRIPTIONS = 1;
+
+ @ManagedSubscriptionsPolicyType
+ private final int mPolicyType;
+
+ private static final String KEY_POLICY_TYPE = "policy_type";
+
+ public ManagedSubscriptionsPolicy(@ManagedSubscriptionsPolicyType int policyType) {
+ if (policyType != TYPE_ALL_PERSONAL_SUBSCRIPTIONS
+ && policyType != TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ throw new IllegalArgumentException("Invalid policy type");
+ }
+ mPolicyType = policyType;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ManagedSubscriptionsPolicy> CREATOR =
+ new Parcelable.Creator<ManagedSubscriptionsPolicy>() {
+ public ManagedSubscriptionsPolicy createFromParcel(Parcel in) {
+ ManagedSubscriptionsPolicy policy = new ManagedSubscriptionsPolicy(
+ in.readInt());
+ return policy;
+ }
+
+ @Override
+ public ManagedSubscriptionsPolicy[] newArray(int size) {
+ return new ManagedSubscriptionsPolicy[size];
+ }
+ };
+
+ /**
+ * Returns the type of managed subscriptions policy, or {@link #TYPE_ALL_PERSONAL_SUBSCRIPTIONS}
+ * if no policy has been set.
+ *
+ * @return The policy type.
+ */
+ @ManagedSubscriptionsPolicyType
+ public int getPolicyType() {
+ return mPolicyType;
+ }
+
+ @Override
+ public String toString() {
+ return TextUtils.formatSimple("ManagedSubscriptionsPolicy (type: %d)", mPolicyType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mPolicyType);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof ManagedSubscriptionsPolicy)) {
+ return false;
+ }
+ ManagedSubscriptionsPolicy that = (ManagedSubscriptionsPolicy) thatObject;
+ return mPolicyType == that.mPolicyType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPolicyType);
+ }
+
+ /** @hide */
+ @Nullable
+ public static ManagedSubscriptionsPolicy readFromXml(@NonNull TypedXmlPullParser parser) {
+ try {
+ ManagedSubscriptionsPolicy policy = new ManagedSubscriptionsPolicy(
+ parser.getAttributeInt(null, KEY_POLICY_TYPE, -1));
+
+ return policy;
+ } catch (IllegalArgumentException e) {
+ // Fail through
+ Log.w(TAG, "Load xml failed", e);
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void saveToXml(TypedXmlSerializer out) throws IOException {
+ out.attributeInt(null, KEY_POLICY_TYPE, mPolicyType);
+ }
+}
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index a0c898e..96eddd2 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -24,7 +24,6 @@
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
-import android.app.timedetector.TimePoint;
/**
* Binder APIs to communicate with the time detector service.
@@ -55,5 +54,5 @@
boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion);
void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion);
- TimePoint latestNetworkTime();
+ UnixEpochTime latestNetworkTime();
}
diff --git a/core/java/android/app/timedetector/TimePoint.java b/core/java/android/app/timedetector/TimePoint.java
deleted file mode 100644
index aa079a9..0000000
--- a/core/java/android/app/timedetector/TimePoint.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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 android.app.timedetector;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Data class for passing a Unix epoch time anchored to the elapsed realtime clock.
- *
- * @hide
- */
-public final class TimePoint implements Parcelable {
-
- private final long mUnixEpochTimeMillis;
- private final long mElapsedRealtimeMillis;
-
- public TimePoint(long unixEpochTimeMillis, long elapsedRealtimeMillis) {
- mUnixEpochTimeMillis = unixEpochTimeMillis;
- mElapsedRealtimeMillis = elapsedRealtimeMillis;
- }
-
- /**
- * The current Unix epoch time, according to the external source.
- */
- public long getUnixEpochTimeMillis() {
- return mUnixEpochTimeMillis;
- }
-
- /**
- * The elapsed millis since boot when {@link #getUnixEpochTimeMillis} was computed.
- */
- public long getElapsedRealtimeMillis() {
- return mElapsedRealtimeMillis;
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeLong(mUnixEpochTimeMillis);
- out.writeLong(mElapsedRealtimeMillis);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof TimePoint)) {
- return false;
- }
- TimePoint timePoint = (TimePoint) o;
- return mUnixEpochTimeMillis == timePoint.mUnixEpochTimeMillis
- && mElapsedRealtimeMillis == timePoint.mElapsedRealtimeMillis;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mUnixEpochTimeMillis, mElapsedRealtimeMillis);
- }
-
- @Override
- public String toString() {
- return "TimePoint{"
- + "mUnixEpochTimeMillis=" + mUnixEpochTimeMillis
- + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
- + '}';
- }
-
- public static final @NonNull Creator<TimePoint> CREATOR =
- new Creator<TimePoint>() {
- public TimePoint createFromParcel(Parcel in) {
- long unixEpochTime = in.readLong();
- long elapsedRealtimeMillis = in.readLong();
- return new TimePoint(unixEpochTime, elapsedRealtimeMillis);
- }
-
- public TimePoint[] newArray(int size) {
- return new TimePoint[size];
- }
- };
-}
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 2b75a23..831ca86 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.app.IAlarmManager;
+import android.app.time.UnixEpochTime;
import android.app.timedetector.ITimeDetectorService;
-import android.app.timedetector.TimePoint;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.location.ILocationManager;
@@ -300,7 +300,7 @@
ITimeDetectorService timeDetectorService = ITimeDetectorService.Stub
.asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
if (timeDetectorService != null) {
- TimePoint time;
+ UnixEpochTime time;
try {
time = timeDetectorService.latestNetworkTime();
} catch (ParcelableException e) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bfa5301..76fbffd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3119,12 +3119,99 @@
<!-- @SystemApi @hide Allows an application to exempt apps from platform restrictions.-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"
- android:protectionLevel="signature|role" />
+ android:protectionLevel="internal|role" />
- <!-- Allows an application to manage date and time device policy. -->
+ <!-- Allows an application to manage device policy relating to time.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_TIME"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to set the grant state of runtime permissions on packages.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage the identity of the managing organization.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set support messages for when a user action is affected by an
+ active policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage backup service policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage lock task policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy regarding modifying applications.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage installing from unknown sources policy.
+ <p>MANAGE_SECURITY_CRITICAL_DEVICE_POLICY_ACROSS_USERS is required to call APIs protected
+ by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage application restrictions.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage calling policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CALLS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage debugging features policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy preventing users from modifying users.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage safe boot policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT"
+ android:protectionLevel="internal|role" />
+
<!-- Allows an application to set device policies outside the current user
that are critical for securing data within the current user.
<p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index e8648cc..1022d93 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -353,6 +353,8 @@
final Set<String> mDeduplicationIds;
final Bundle mExtras;
final String mProviderId;
+ final boolean mIsVisibilityRestricted;
+ final Set<String> mAllowedPackages;
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
@@ -372,6 +374,8 @@
mDeduplicationIds = builder.mDeduplicationIds;
mExtras = builder.mExtras;
mProviderId = builder.mProviderId;
+ mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
+ mAllowedPackages = builder.mAllowedPackages;
}
MediaRoute2Info(@NonNull Parcel in) {
@@ -393,6 +397,8 @@
mDeduplicationIds = Set.of(in.readStringArray());
mExtras = in.readBundle();
mProviderId = in.readString();
+ mIsVisibilityRestricted = in.readBoolean();
+ mAllowedPackages = Set.of(in.createString8Array());
}
/**
@@ -628,6 +634,15 @@
}
/**
+ * Returns whether this route is visible to the package with the given name.
+ * @hide
+ */
+ public boolean isVisibleTo(String packageName) {
+ return !mIsVisibilityRestricted || getPackageName().equals(packageName)
+ || mAllowedPackages.contains(packageName);
+ }
+
+ /**
* Dumps the current state of the object to the given {@code pw} as a human-readable string.
*
* <p> Used in the context of dumpsys. </p>
@@ -655,6 +670,8 @@
pw.println(indent + "mDeduplicationIds=" + mDeduplicationIds);
pw.println(indent + "mExtras=" + mExtras);
pw.println(indent + "mProviderId=" + mProviderId);
+ pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
+ pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
}
private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -705,7 +722,9 @@
&& (mVolume == other.mVolume)
&& Objects.equals(mAddress, other.mAddress)
&& Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
- && Objects.equals(mProviderId, other.mProviderId);
+ && Objects.equals(mProviderId, other.mProviderId)
+ && (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
+ && Objects.equals(mAllowedPackages, other.mAllowedPackages);
}
@Override
@@ -713,7 +732,8 @@
// Note: mExtras is not included.
return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
- mVolume, mAddress, mDeduplicationIds, mProviderId);
+ mVolume, mAddress, mDeduplicationIds, mProviderId, mIsVisibilityRestricted,
+ mAllowedPackages);
}
@Override
@@ -733,6 +753,8 @@
.append(", volume=").append(getVolume())
.append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
.append(", providerId=").append(getProviderId())
+ .append(", isVisibilityRestricted=").append(mIsVisibilityRestricted)
+ .append(", allowedPackages=").append(String.join(",", mAllowedPackages))
.append(" }");
return result.toString();
}
@@ -761,6 +783,8 @@
dest.writeStringArray(mDeduplicationIds.toArray(new String[mDeduplicationIds.size()]));
dest.writeBundle(mExtras);
dest.writeString(mProviderId);
+ dest.writeBoolean(mIsVisibilityRestricted);
+ dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
}
/**
@@ -787,6 +811,8 @@
Set<String> mDeduplicationIds;
Bundle mExtras;
String mProviderId;
+ boolean mIsVisibilityRestricted;
+ Set<String> mAllowedPackages;
/**
* Constructor for builder to create {@link MediaRoute2Info}.
@@ -809,6 +835,7 @@
mName = name;
mFeatures = new ArrayList<>();
mDeduplicationIds = Set.of();
+ mAllowedPackages = Set.of();
}
/**
@@ -854,6 +881,8 @@
mExtras = new Bundle(routeInfo.mExtras);
}
mProviderId = routeInfo.mProviderId;
+ mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
+ mAllowedPackages = routeInfo.mAllowedPackages;
}
/**
@@ -1057,6 +1086,29 @@
}
/**
+ * Sets the visibility of this route to public. This is the default
+ * visibility for routes that are public to all other apps.
+ */
+ @NonNull
+ public Builder setVisibilityPublic() {
+ mIsVisibilityRestricted = false;
+ mAllowedPackages = Set.of();
+ return this;
+ }
+
+ /**
+ * Sets the visibility of this route to restricted. This means that the
+ * route is only visible to a set of package name.
+ * @param allowedPackages set of package names which are allowed to see this route.
+ */
+ @NonNull
+ public Builder setVisibilityRestricted(@NonNull Set<String> allowedPackages) {
+ mIsVisibilityRestricted = true;
+ mAllowedPackages = Set.copyOf(allowedPackages);
+ return this;
+ }
+
+ /**
* Builds the {@link MediaRoute2Info media route info}.
*
* @throws IllegalArgumentException if no features are added.
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index aea6bcb..3abfc629 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -247,7 +247,6 @@
return getTransferableRoutes(sessions.get(sessions.size() - 1));
}
-
/**
* Gets available routes for the given routing session.
* The returned routes can be passed to
@@ -313,9 +312,15 @@
mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
for (MediaRoute2Info route : getSortedRoutes(discoveryPreference)) {
- if (sessionInfo.getTransferableRoutes().contains(route.getId())
- || (includeSelectedRoutes
- && sessionInfo.getSelectedRoutes().contains(route.getId()))) {
+ if (!route.isVisibleTo(packageName)) {
+ continue;
+ }
+ boolean transferableRoutesContainRoute =
+ sessionInfo.getTransferableRoutes().contains(route.getId());
+ boolean selectedRoutesContainRoute =
+ sessionInfo.getSelectedRoutes().contains(route.getId());
+ if (transferableRoutesContainRoute
+ || (includeSelectedRoutes && selectedRoutesContainRoute)) {
routes.add(route);
continue;
}
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index b1d74d4..b03653c0 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -19,6 +19,9 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -40,6 +43,18 @@
*/
public final class RouteListingPreference implements Parcelable {
+ /**
+ * {@link Intent} action for apps to take the user to a screen for transferring media playback
+ * to the route with the id provided by the extra with key {@link #EXTRA_ROUTE_ID}.
+ */
+ public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
+
+ /**
+ * {@link Intent} string extra key that contains the {@link Item#getRouteId() id} of the route
+ * to transfer to, as part of an {@link #ACTION_TRANSFER_MEDIA} intent.
+ */
+ public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
+
@NonNull
public static final Creator<RouteListingPreference> CREATOR =
new Creator<>() {
@@ -56,10 +71,12 @@
@NonNull private final List<Item> mItems;
private final boolean mUseSystemOrdering;
+ @Nullable private final ComponentName mInAppOnlyItemRoutingReceiver;
private RouteListingPreference(Builder builder) {
mItems = builder.mItems;
mUseSystemOrdering = builder.mUseSystemOrdering;
+ mInAppOnlyItemRoutingReceiver = builder.mInAppOnlyItemRoutingReceiver;
}
private RouteListingPreference(Parcel in) {
@@ -67,6 +84,7 @@
in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
mItems = List.copyOf(items);
mUseSystemOrdering = in.readBoolean();
+ mInAppOnlyItemRoutingReceiver = ComponentName.readFromParcel(in);
}
/**
@@ -90,6 +108,21 @@
return mUseSystemOrdering;
}
+ /**
+ * Returns a {@link ComponentName} for handling routes disabled via {@link
+ * Item#DISABLE_REASON_IN_APP_ONLY}, or null if the user needs to manually navigate to the app
+ * in order to route to select the corresponding routes.
+ *
+ * <p>If the user selects an {@link Item} disabled via {@link Item#DISABLE_REASON_IN_APP_ONLY},
+ * and this method returns a non-null {@link ComponentName}, the system takes the user back to
+ * the app by launching an intent to the returned {@link ComponentName}, using action {@link
+ * #ACTION_TRANSFER_MEDIA}, with the extra {@link #EXTRA_ROUTE_ID}.
+ */
+ @Nullable
+ public ComponentName getInAppOnlyItemRoutingReceiver() {
+ return mInAppOnlyItemRoutingReceiver;
+ }
+
// RouteListingPreference Parcelable implementation.
@Override
@@ -101,6 +134,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelableList(mItems, flags);
dest.writeBoolean(mUseSystemOrdering);
+ ComponentName.writeToParcel(mInAppOnlyItemRoutingReceiver, dest);
}
// Equals and hashCode.
@@ -114,12 +148,15 @@
return false;
}
RouteListingPreference that = (RouteListingPreference) other;
- return mItems.equals(that.mItems) && mUseSystemOrdering == that.mUseSystemOrdering;
+ return mItems.equals(that.mItems)
+ && mUseSystemOrdering == that.mUseSystemOrdering
+ && Objects.equals(
+ mInAppOnlyItemRoutingReceiver, that.mInAppOnlyItemRoutingReceiver);
}
@Override
public int hashCode() {
- return Objects.hash(mItems, mUseSystemOrdering);
+ return Objects.hash(mItems, mUseSystemOrdering, mInAppOnlyItemRoutingReceiver);
}
/** Builder for {@link RouteListingPreference}. */
@@ -127,6 +164,7 @@
private List<Item> mItems;
private boolean mUseSystemOrdering;
+ private ComponentName mInAppOnlyItemRoutingReceiver;
/** Creates a new instance with default values (documented in the setters). */
public Builder() {
@@ -159,6 +197,18 @@
}
/**
+ * See {@link #getInAppOnlyItemRoutingReceiver()}.
+ *
+ * <p>The default value is {@code null}.
+ */
+ @NonNull
+ public Builder setInAppOnlyItemRoutingReceiver(
+ @Nullable ComponentName inAppOnlyItemRoutingReceiver) {
+ mInAppOnlyItemRoutingReceiver = inAppOnlyItemRoutingReceiver;
+ return this;
+ }
+
+ /**
* Creates and returns a new {@link RouteListingPreference} instance with the given
* parameters.
*/
@@ -203,7 +253,8 @@
DISABLE_REASON_NONE,
DISABLE_REASON_SUBSCRIPTION_REQUIRED,
DISABLE_REASON_DOWNLOADED_CONTENT,
- DISABLE_REASON_AD
+ DISABLE_REASON_AD,
+ DISABLE_REASON_IN_APP_ONLY
})
public @interface DisableReason {}
@@ -221,6 +272,14 @@
public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2;
/** The corresponding route is not available because an ad is in progress. */
public static final int DISABLE_REASON_AD = 3;
+ /**
+ * The corresponding route is only available for routing from within the app.
+ *
+ * <p>The user may still select the corresponding route if the app provides an {@link
+ * #getInAppOnlyItemRoutingReceiver() in-app routing receiver}, in which case the system
+ * will take the user to the app.
+ */
+ public static final int DISABLE_REASON_IN_APP_ONLY = 4;
@NonNull
public static final Creator<Item> CREATOR =
@@ -257,7 +316,11 @@
Preconditions.checkArgument(mSessionParticipantCount >= 0);
}
- /** Returns the id of the route that corresponds to this route listing preference item. */
+ /**
+ * Returns the id of the route that corresponds to this route listing preference item.
+ *
+ * @see MediaRoute2Info#getId()
+ */
@NonNull
public String getRouteId() {
return mRouteId;
@@ -282,6 +345,7 @@
* @see #DISABLE_REASON_SUBSCRIPTION_REQUIRED
* @see #DISABLE_REASON_DOWNLOADED_CONTENT
* @see #DISABLE_REASON_AD
+ * @see #DISABLE_REASON_IN_APP_ONLY
*/
@DisableReason
public int getDisableReason() {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index cc485ba..cde4ea9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -31,6 +31,7 @@
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -285,6 +286,15 @@
public void setRouteListingPreference(
@NonNull IMediaRouter2 router,
@Nullable RouteListingPreference routeListingPreference) {
+ ComponentName inAppOnlyItemRoutingReceiver =
+ routeListingPreference != null
+ ? routeListingPreference.getInAppOnlyItemRoutingReceiver()
+ : null;
+ if (inAppOnlyItemRoutingReceiver != null) {
+ MediaServerUtils.enforcePackageName(
+ inAppOnlyItemRoutingReceiver.getPackageName(), Binder.getCallingUid());
+ }
+
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -787,6 +797,12 @@
obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
routerRecord.mUserRecord.mHandler,
routerRecord.mPackageName, null));
+ routerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(
+ UserHandler::notifyRouteListingPreferenceChangeToManagers,
+ routerRecord.mUserRecord.mHandler,
+ routerRecord.mPackageName,
+ /* routeListingPreference= */ null));
userRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
userRecord.mHandler));
@@ -1626,7 +1642,7 @@
* <p>This list contains all routes exposed by route providers. This includes routes from
* both system route providers and user route providers.
*
- * <p>See {@link #getRouters(boolean hasModifyAudioRoutingPermission)}.
+ * <p>See {@link #getRouterRecords(boolean hasModifyAudioRoutingPermission)}.
*/
private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
new ArrayMap<>();
@@ -1896,9 +1912,10 @@
if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
return;
}
-
- List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
- List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
+ List<RouterRecord> routerRecordsWithModifyAudioRoutingPermission =
+ getRouterRecords(true);
+ List<RouterRecord> routerRecordsWithoutModifyAudioRoutingPermission =
+ getRouterRecords(false);
List<IMediaRouter2Manager> managers = getManagers();
// Managers receive all provider updates with all routes.
@@ -1907,22 +1924,22 @@
// Routers with modify audio permission (usually system routers) receive all provider
// updates with all routes.
- notifyRoutesUpdatedToRouters(
- routersWithModifyAudioRoutingPermission,
+ notifyRoutesUpdatedToRouterRecords(
+ routerRecordsWithModifyAudioRoutingPermission,
new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
if (!isSystemProvider) {
// Regular routers receive updates from all non-system providers with all non-system
// routes.
- notifyRoutesUpdatedToRouters(
- routersWithoutModifyAudioRoutingPermission,
+ notifyRoutesUpdatedToRouterRecords(
+ routerRecordsWithoutModifyAudioRoutingPermission,
new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
} else if (hasAddedOrModifiedRoutes) {
// On system provider updates, regular routers receive the updated default route.
// This is the only system route they should receive.
mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
- notifyRoutesUpdatedToRouters(
- routersWithoutModifyAudioRoutingPermission,
+ notifyRoutesUpdatedToRouterRecords(
+ routerRecordsWithoutModifyAudioRoutingPermission,
new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
}
}
@@ -2191,8 +2208,8 @@
if (mServiceRef.get() == null) {
return;
}
- notifySessionInfoChangedToRouters(getRouters(true), sessionInfo);
- notifySessionInfoChangedToRouters(getRouters(false),
+ notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
+ notifySessionInfoChangedToRouters(getRouterRecords(false),
mSystemProvider.getDefaultSessionInfo());
return;
}
@@ -2203,7 +2220,7 @@
+ sessionInfo);
return;
}
- notifySessionInfoChangedToRouters(Arrays.asList(routerRecord.mRouter), sessionInfo);
+ notifySessionInfoChangedToRouters(Arrays.asList(routerRecord), sessionInfo);
}
private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -2316,6 +2333,7 @@
== routerRecord.mHasModifyAudioRoutingPermission) {
routers.add(routerRecord.mRouter);
}
+ routers.add(routerRecord.mRouter);
}
}
return routers;
@@ -2345,6 +2363,23 @@
}
}
+ private List<RouterRecord> getRouterRecords(boolean hasModifyAudioRoutingPermission) {
+ MediaRouter2ServiceImpl service = mServiceRef.get();
+ List<RouterRecord> routerRecords = new ArrayList<>();
+ if (service == null) {
+ return routerRecords;
+ }
+ synchronized (service.mLock) {
+ for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
+ if (hasModifyAudioRoutingPermission
+ == routerRecord.mHasModifyAudioRoutingPermission) {
+ routerRecords.add(routerRecord);
+ }
+ }
+ return routerRecords;
+ }
+ }
+
private List<ManagerRecord> getManagerRecords() {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
@@ -2395,22 +2430,45 @@
}
}
- private void notifyRoutesUpdatedToRouters(
- @NonNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2 router : routers) {
+ private static void notifyRoutesUpdatedToRouterRecords(
+ @NonNull List<RouterRecord> routerRecords,
+ @NonNull List<MediaRoute2Info> routes) {
+ for (RouterRecord routerRecord: routerRecords) {
+ List<MediaRoute2Info> filteredRoutes = getFilteredRoutesForPackageName(routes,
+ routerRecord.mPackageName);
try {
- router.notifyRoutesUpdated(routes);
+ routerRecord.mRouter.notifyRoutesUpdated(filteredRoutes);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
}
}
}
+ /**
+ * Filters list of routes to return only public routes or routes provided by
+ * the same package name or routes containing this package name in its allow list.
+ * @param routes initial list of routes to be filtered.
+ * @param packageName router's package name to filter routes for it.
+ * @return only the routes that this package name is allowed to see.
+ */
+ private static List<MediaRoute2Info> getFilteredRoutesForPackageName(
+ @NonNull List<MediaRoute2Info> routes,
+ @NonNull String packageName) {
+ List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ for (MediaRoute2Info route : routes) {
+ if (route.isVisibleTo(packageName)) {
+ filteredRoutes.add(route);
+ }
+ }
+ return filteredRoutes;
+ }
+
private void notifySessionInfoChangedToRouters(
- @NonNull List<IMediaRouter2> routers, @NonNull RoutingSessionInfo sessionInfo) {
- for (IMediaRouter2 router : routers) {
+ @NonNull List<RouterRecord> routerRecords,
+ @NonNull RoutingSessionInfo sessionInfo) {
+ for (RouterRecord routerRecord : routerRecords) {
try {
- router.notifySessionInfoChanged(sessionInfo);
+ routerRecord.mRouter.notifySessionInfoChanged(sessionInfo);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
}
diff --git a/services/core/java/com/android/server/media/MediaServerUtils.java b/services/core/java/com/android/server/media/MediaServerUtils.java
index 5fa2b1c..a4a99af 100644
--- a/services/core/java/com/android/server/media/MediaServerUtils.java
+++ b/services/core/java/com/android/server/media/MediaServerUtils.java
@@ -18,7 +18,13 @@
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.server.LocalServices;
import java.io.PrintWriter;
@@ -26,6 +32,39 @@
* Util class for media server.
*/
class MediaServerUtils {
+
+ /**
+ * Throws if the given {@code packageName} does not correspond to the given {@code uid}.
+ *
+ * <p>This method trusts calls from {@link Process#ROOT_UID} and {@link Process#SHELL_UID}.
+ *
+ * @param packageName A package name to verify (usually sent over binder by an app).
+ * @param uid The calling uid, obtained via {@link Binder#getCallingUid()}.
+ * @throws IllegalArgumentException If the given {@code packageName} does not correspond to the
+ * given {@code uid}, and {@code uid} is not the root uid, or the shell uid.
+ */
+ public static void enforcePackageName(String packageName, int uid) {
+ if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) {
+ return;
+ }
+ if (TextUtils.isEmpty(packageName)) {
+ throw new IllegalArgumentException("packageName may not be empty");
+ }
+ final PackageManagerInternal packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ final int actualUid =
+ packageManagerInternal.getPackageUid(
+ packageName, 0 /* flags */, UserHandle.getUserId(uid));
+ if (!UserHandle.isSameApp(uid, actualUid)) {
+ throw new IllegalArgumentException(
+ "packageName does not belong to the calling uid; "
+ + "pkg="
+ + packageName
+ + ", uid="
+ + uid);
+ }
+ }
+
/**
* Verify that caller holds {@link android.Manifest.permission#DUMP}.
*/
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index e51ed1b..3a20cd9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -538,30 +538,11 @@
mHandler.postSessionsChanged(session);
}
- private void enforcePackageName(String packageName, int uid) {
- if (TextUtils.isEmpty(packageName)) {
- throw new IllegalArgumentException("packageName may not be empty");
- }
- if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) {
- // If the caller is shell, then trust the packageName given and allow it
- // to proceed.
- return;
- }
- final PackageManagerInternal packageManagerInternal =
- LocalServices.getService(PackageManagerInternal.class);
- final int actualUid = packageManagerInternal.getPackageUid(
- packageName, 0 /* flags */, UserHandle.getUserId(uid));
- if (!UserHandle.isSameApp(uid, actualUid)) {
- throw new IllegalArgumentException("packageName does not belong to the calling uid; "
- + "pkg=" + packageName + ", uid=" + uid);
- }
- }
-
void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
int callingPid, int callingUid, String callingPackage, String reason) {
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(callingPackage, callingUid);
+ MediaServerUtils.enforcePackageName(callingPackage, callingUid);
if (targetUid != callingUid) {
boolean canAllowWhileInUse = mActivityManagerLocal
.canAllowWhileInUsePermissionInFgs(callingPid, callingUid, callingPackage);
@@ -1206,7 +1187,7 @@
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);
if (cb == null) {
throw new IllegalArgumentException("Controller callback cannot be null");
@@ -1258,7 +1239,7 @@
final int userId = userHandle.getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
enforceMediaPermissions(packageName, pid, uid, userId);
MediaSessionRecordImpl record;
@@ -1289,7 +1270,7 @@
final int userId = userHandle.getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
enforceMediaPermissions(packageName, pid, uid, userId);
MediaSessionRecordImpl record;
@@ -1615,7 +1596,7 @@
final int userId = userHandle.getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
enforceMediaPermissions(packageName, pid, uid, userId);
synchronized (mLock) {
@@ -2129,7 +2110,7 @@
// If they gave us a component name verify they own the
// package
packageName = componentName.getPackageName();
- enforcePackageName(packageName, uid);
+ MediaServerUtils.enforcePackageName(packageName, uid);
}
// Check that they can make calls on behalf of the user and get the final user id
int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 9d098c6..1be9074 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -29,7 +29,6 @@
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
-import android.app.timedetector.TimePoint;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
@@ -361,6 +360,34 @@
mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal));
}
+ @Override
+ public UnixEpochTime latestNetworkTime() {
+ NetworkTimeSuggestion suggestion = getLatestNetworkSuggestion();
+ if (suggestion != null) {
+ return suggestion.getUnixEpochTime();
+ } else {
+ throw new ParcelableException(new DateTimeException("Missing network time fix"));
+ }
+ }
+
+ /**
+ * Returns the latest network suggestion accepted. For use by {@link TimeDetectorShellCommand}.
+ */
+ @Nullable
+ NetworkTimeSuggestion getLatestNetworkSuggestion() {
+ // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
+ // be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
+ // detector. mNtpTrustedTime can be removed once this happens.
+ NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
+ if (ntpResult != null) {
+ UnixEpochTime unixEpochTime = new UnixEpochTime(
+ ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+ return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
+ } else {
+ return null;
+ }
+ }
+
void suggestGnssTime(@NonNull GnssTimeSuggestion timeSignal) {
enforceSuggestGnssTimePermission();
Objects.requireNonNull(timeSignal);
@@ -377,19 +404,6 @@
}
@Override
- public TimePoint latestNetworkTime() {
- // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
- // be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
- // detector. mNtpTrustedTime can be removed once this happens.
- NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
- if (ntpResult != null) {
- return new TimePoint(ntpResult.getTimeMillis(), ntpResult.getElapsedRealtimeMillis());
- } else {
- throw new ParcelableException(new DateTimeException("Missing network time fix"));
- }
- }
-
- @Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -421,7 +435,7 @@
private void enforceSuggestNetworkTimePermission() {
mContext.enforceCallingPermission(
android.Manifest.permission.SET_TIME,
- "set time");
+ "suggest network time");
}
private void enforceSuggestGnssTimePermission() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 70a7a02..4c42791 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -34,6 +34,7 @@
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.app.admin.FactoryResetProtectionPolicy;
+import android.app.admin.ManagedSubscriptionsPolicy;
import android.app.admin.PackagePolicy;
import android.app.admin.PasswordPolicy;
import android.app.admin.PreferentialNetworkServiceConfig;
@@ -168,6 +169,7 @@
private static final String TAG_PROTECTED_PACKAGES = "protected_packages";
private static final String TAG_SUSPENDED_PACKAGES = "suspended-packages";
private static final String TAG_MTE_POLICY = "mte-policy";
+ private static final String TAG_MANAGED_SUBSCRIPTIONS_POLICY = "managed_subscriptions_policy";
private static final String ATTR_VALUE = "value";
private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -272,6 +274,9 @@
// Wi-Fi SSID restriction policy.
WifiSsidPolicy mWifiSsidPolicy;
+ // Managed subscriptions policy.
+ ManagedSubscriptionsPolicy mManagedSubscriptionsPolicy;
+
// TODO: review implementation decisions with frameworks team
boolean specifiesGlobalProxy = false;
String globalProxySpec = null;
@@ -642,6 +647,11 @@
mManagedProfileCallerIdAccess);
writePackagePolicy(out, TAG_CROSS_PROFILE_CONTACTS_SEARCH_POLICY,
mManagedProfileContactsAccess);
+ if (mManagedSubscriptionsPolicy != null) {
+ out.startTag(null, TAG_MANAGED_SUBSCRIPTIONS_POLICY);
+ mManagedSubscriptionsPolicy.saveToXml(out);
+ out.endTag(null, TAG_MANAGED_SUBSCRIPTIONS_POLICY);
+ }
}
private void writePackagePolicy(TypedXmlSerializer out, String tag,
@@ -946,6 +956,8 @@
mManagedProfileCallerIdAccess = readPackagePolicy(parser);
} else if (TAG_CROSS_PROFILE_CONTACTS_SEARCH_POLICY.equals(tag)) {
mManagedProfileContactsAccess = readPackagePolicy(parser);
+ } else if (TAG_MANAGED_SUBSCRIPTIONS_POLICY.equals(tag)) {
+ mManagedSubscriptionsPolicy = ManagedSubscriptionsPolicy.readFromXml(parser);
} else {
Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
XmlUtils.skipCurrentTag(parser);
@@ -1414,5 +1426,13 @@
pw.print("accountTypesWithManagementDisabled=");
pw.println(accountTypesWithManagementDisabled);
+
+ if (mManagedSubscriptionsPolicy != null) {
+ pw.println("mManagedSubscriptionsPolicy:");
+ pw.increaseIndent();
+ pw.print("mPolicyType=");
+ mManagedSubscriptionsPolicy.getPolicyType();
+ pw.decreaseIndent();
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b92b22..cdb2e08 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -218,6 +218,7 @@
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.FullyManagedDeviceProvisioningParams;
import android.app.admin.ManagedProfileProvisioningParams;
+import android.app.admin.ManagedSubscriptionsPolicy;
import android.app.admin.NetworkEvent;
import android.app.admin.PackagePolicy;
import android.app.admin.ParcelableGranteeMap;
@@ -334,6 +335,8 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.stats.devicepolicy.DevicePolicyEnums;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.text.TextUtils;
@@ -768,6 +771,9 @@
private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
private final Object mESIDInitilizationLock = new Object();
private EnterpriseSpecificIdCalculator mEsidCalculator;
+ private final Object mSubscriptionsChangedListenerLock = new Object();
+ @GuardedBy("mSubscriptionsChangedListenerLock")
+ private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
/**
* Contains the list of OEM Default Role Holders for Contact-related roles
@@ -973,8 +979,6 @@
@GuardedBy("getLockObject()")
final SparseArray<DevicePolicyData> mUserData;
- @GuardedBy("getLockObject()")
-
final Handler mHandler;
final Handler mBackgroundHandler;
@@ -3092,6 +3096,7 @@
onLockSettingsReady();
loadAdminDataAsync();
mOwners.systemReady();
+ applyManagedSubscriptionsPolicyIfRequired();
break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
synchronized (getLockObject()) {
@@ -3110,6 +3115,22 @@
}
}
+ private void applyManagedSubscriptionsPolicyIfRequired() {
+ int copeProfileUserId = getOrganizationOwnedProfileUserId();
+ // This policy is relevant only for COPE devices.
+ if (copeProfileUserId != UserHandle.USER_NULL) {
+ unregisterOnSubscriptionsChangedListener();
+ int policyType = getManagedSubscriptionsPolicy().getPolicyType();
+ if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS) {
+ // By default, assign all current and future subs to system user on COPE devices.
+ registerListenerToAssignSubscriptionsToUser(UserHandle.USER_SYSTEM);
+ } else if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ // Add listener to assign all current and future subs to managed profile.
+ registerListenerToAssignSubscriptionsToUser(copeProfileUserId);
+ }
+ }
+ }
+
private void updatePersonalAppsSuspensionOnUserStart(int userHandle) {
final int profileUserHandle = getManagedUserId(userHandle);
if (profileUserHandle >= 0) {
@@ -6993,9 +7014,23 @@
}
mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
+ clearManagedSubscriptionsPolicy();
+
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
}
+ private void clearManagedSubscriptionsPolicy() {
+ unregisterOnSubscriptionsChangedListener();
+
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ //Iterate over all the subscriptions and remove association with any user.
+ int[] subscriptionIds = subscriptionManager.getActiveSubscriptionIdList(false);
+ for (int subId : subscriptionIds) {
+ subscriptionManager.setSubscriptionUserHandle(subId, null);
+ }
+ }
+
/**
* @param factoryReset null: legacy behaviour, false: attempt to remove user, true: attempt to
* factory reset
@@ -10088,6 +10123,10 @@
mStateCache.dump(pw);
pw.println();
}
+
+ synchronized (mSubscriptionsChangedListenerLock) {
+ pw.println("Subscription changed listener : " + mSubscriptionsChangedListener);
+ }
mHandler.post(() -> handleDump(pw));
dumpResources(pw);
}
@@ -19952,4 +19991,139 @@
HEADLESS_FLAG,
DEFAULT_HEADLESS_FLAG);
}
+
+ @Override
+ public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
+ synchronized (getLockObject()) {
+ ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
+ if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
+ return admin.mManagedSubscriptionsPolicy;
+ }
+ }
+ return new ManagedSubscriptionsPolicy(
+ ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS);
+ }
+
+ @Override
+ public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) {
+ CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "This policy can only be set by a profile owner on an organization-owned device.");
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
+ if (hasUserSetupCompleted(UserHandle.USER_SYSTEM) && !isAdminTestOnlyLocked(
+ admin.info.getComponent(), caller.getUserId())) {
+ throw new IllegalStateException("Not allowed to apply this policy after setup");
+ }
+ boolean changed = false;
+ if (!Objects.equals(policy, admin.mManagedSubscriptionsPolicy)) {
+ admin.mManagedSubscriptionsPolicy = policy;
+ changed = true;
+ }
+ if (changed) {
+ saveSettingsLocked(caller.getUserId());
+ } else {
+ return;
+ }
+ }
+
+ applyManagedSubscriptionsPolicyIfRequired();
+
+ int policyType = getManagedSubscriptionsPolicy().getPolicyType();
+ if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ int parentUserId = getProfileParentId(caller.getUserId());
+ installOemDefaultDialerAndMessagesApp(parentUserId, caller.getUserId());
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+ }
+
+ private void installOemDefaultDialerAndMessagesApp(int sourceUserId, int targetUserId) {
+ try {
+ UserHandle sourceUserHandle = UserHandle.of(sourceUserId);
+ TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+ String dialerAppPackage = telecomManager.getDefaultDialerPackage(
+ sourceUserHandle);
+ String messagesAppPackage = SmsApplication.getDefaultSmsApplicationAsUser(mContext,
+ true, sourceUserHandle).getPackageName();
+ if (dialerAppPackage != null) {
+ mIPackageManager.installExistingPackageAsUser(dialerAppPackage, targetUserId,
+ PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+ PackageManager.INSTALL_REASON_POLICY, null);
+ } else {
+ Slogf.w(LOG_TAG, "Couldn't install dialer app, dialer app package is null");
+ }
+
+ if (messagesAppPackage != null) {
+ mIPackageManager.installExistingPackageAsUser(messagesAppPackage, targetUserId,
+ PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+ PackageManager.INSTALL_REASON_POLICY, null);
+ } else {
+ Slogf.w(LOG_TAG, "Couldn't install messages app, messages app package is null");
+ }
+ } catch (RemoteException re) {
+ // shouldn't happen
+ Slogf.wtf(LOG_TAG, "Failed to install dialer/messages app", re);
+ }
+ }
+
+ private void registerListenerToAssignSubscriptionsToUser(int userId) {
+ synchronized (mSubscriptionsChangedListenerLock) {
+ if (mSubscriptionsChangedListener != null) {
+ return;
+ }
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ // Listener to assign all current and future subs to managed profile.
+ mSubscriptionsChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener(
+ mHandler.getLooper()) {
+ @Override
+ public void onSubscriptionsChanged() {
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ int[] subscriptionIds = subscriptionManager.getActiveSubscriptionIdList(
+ false);
+ for (int subId : subscriptionIds) {
+ UserHandle associatedUserHandle =
+ subscriptionManager.getSubscriptionUserHandle(subId);
+ if (associatedUserHandle == null
+ || associatedUserHandle.getIdentifier() != userId) {
+ subscriptionManager.setSubscriptionUserHandle(subId,
+ UserHandle.of(userId));
+ }
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+ };
+
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ // When listener is added onSubscriptionsChanged gets called immediately for once
+ // (even if subscriptions are not changed) and later on when subscriptions changes.
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ mSubscriptionsChangedListener.getHandlerExecutor(),
+ mSubscriptionsChangedListener);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+ }
+
+ private void unregisterOnSubscriptionsChangedListener() {
+ synchronized (mSubscriptionsChangedListenerLock) {
+ if (mSubscriptionsChangedListener != null) {
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ subscriptionManager.removeOnSubscriptionsChangedListener(
+ mSubscriptionsChangedListener);
+ mSubscriptionsChangedListener = null;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a55b196..61c3f13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5014,7 +5014,8 @@
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
when(getServices().userManager.getPrimaryUser())
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
-
+ when(getServices().subscriptionManager.getActiveSubscriptionIdList(false)).thenReturn(
+ new int[1]);
// Set some device-wide policies:
// Security logging
when(getServices().settings.securityLogGetLoggingEnabledProperty()).thenReturn(true);
@@ -5062,6 +5063,7 @@
// Unsuspend personal apps
verify(getServices().packageManagerInternal)
.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
+ verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index ac1667d..c739969 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -239,6 +239,8 @@
return mMockSystemServices.locationManager;
case Context.ROLE_SERVICE:
return mMockSystemServices.roleManager;
+ case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
+ return mMockSystemServices.subscriptionManager;
}
throw new UnsupportedOperationException();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index cec9d50..2a6a979 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -63,6 +63,7 @@
import android.permission.IPermissionManager;
import android.provider.Settings;
import android.security.KeyChain;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
@@ -135,6 +136,7 @@
public final DevicePolicyManager devicePolicyManager;
public final LocationManager locationManager;
public final RoleManager roleManager;
+ public final SubscriptionManager subscriptionManager;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
public final BuildMock buildMock = new BuildMock();
@@ -188,6 +190,7 @@
devicePolicyManager = mock(DevicePolicyManager.class);
locationManager = mock(LocationManager.class);
roleManager = realContext.getSystemService(RoleManager.class);
+ subscriptionManager = mock(SubscriptionManager.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 4b417ba..a857238 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -44,7 +45,6 @@
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
-import android.app.timedetector.TimePoint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.HandlerThread;
@@ -385,8 +385,8 @@
1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
when(mMockNtpTrustedTime.getCachedTimeResult())
.thenReturn(latestNetworkTime);
- TimePoint expected = new TimePoint(latestNetworkTime.getTimeMillis(),
- latestNetworkTime.getElapsedRealtimeMillis());
+ UnixEpochTime expected = new UnixEpochTime(
+ latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
assertEquals(expected, mTimeDetectorService.latestNetworkTime());
}
@@ -397,6 +397,25 @@
}
@Test
+ public void testGetLatestNetworkSuggestion() {
+ NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
+ 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
+ when(mMockNtpTrustedTime.getCachedTimeResult())
+ .thenReturn(latestNetworkTime);
+ UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
+ latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
+ NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
+ expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
+ assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+ }
+
+ @Test
+ public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+ when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
+ assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ }
+
+ @Test
public void testGetTimeState() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TimeState fakeState = new TimeState(new UnixEpochTime(12345L, 98765L), true);