Merge "WindowInsetsAnimation: Clean up API"
diff --git a/Android.bp b/Android.bp
index dce8bf2..3fff2d4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -471,7 +471,6 @@
"framework-platform-compat-config",
"libcore-platform-compat-config",
"services-platform-compat-config",
- "media-provider-platform-compat-config",
],
static_libs: [
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 40b81dd..d9b9c56 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8497,6 +8497,11 @@
* From {@link android.os.Build.VERSION_CODES#N} the profile or device owner can still use
* {@link android.accounts.AccountManager} APIs to add or remove accounts when account
* management for a specific type is disabled.
+ * <p>
+ * This method may be called on the {@code DevicePolicyManager} instance returned from
+ * {@link #getParentProfileInstance(ComponentName)} by the profile owner on an
+ * organization-owned device, to restrict accounts that may not be managed on the primary
+ * profile.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param accountType For which account management is disabled or enabled.
@@ -8506,10 +8511,10 @@
*/
public void setAccountManagementDisabled(@NonNull ComponentName admin, String accountType,
boolean disabled) {
- throwIfParentInstance("setAccountManagementDisabled");
if (mService != null) {
try {
- mService.setAccountManagementDisabled(admin, accountType, disabled);
+ mService.setAccountManagementDisabled(admin, accountType, disabled,
+ mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8517,28 +8522,43 @@
}
/**
- * Gets the array of accounts for which account management is disabled by the profile owner.
+ * Gets the array of accounts for which account management is disabled by the profile owner
+ * or device owner.
*
* <p> Account management can be disabled/enabled by calling
* {@link #setAccountManagementDisabled}.
+ * <p>
+ * This method may be called on the {@code DevicePolicyManager} instance returned from
+ * {@link #getParentProfileInstance(ComponentName)}. Note that only a profile owner on
+ * an organization-deviced can affect account types on the parent profile instance.
*
* @return a list of account types for which account management has been disabled.
*
* @see #setAccountManagementDisabled
*/
public @Nullable String[] getAccountTypesWithManagementDisabled() {
- throwIfParentInstance("getAccountTypesWithManagementDisabled");
- return getAccountTypesWithManagementDisabledAsUser(myUserId());
+ return getAccountTypesWithManagementDisabledAsUser(myUserId(), mParentInstance);
+ }
+
+ /**
+ * @see #getAccountTypesWithManagementDisabled()
+ * Note that calling this method on the parent profile instance will return the same
+ * value as calling it on the main {@code DevicePolicyManager} instance.
+ * @hide
+ */
+ public @Nullable String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
+ return getAccountTypesWithManagementDisabledAsUser(userId, false);
}
/**
* @see #getAccountTypesWithManagementDisabled()
* @hide
*/
- public @Nullable String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
+ public @Nullable String[] getAccountTypesWithManagementDisabledAsUser(
+ int userId, boolean parentInstance) {
if (mService != null) {
try {
- return mService.getAccountTypesWithManagementDisabledAsUser(userId);
+ return mService.getAccountTypesWithManagementDisabledAsUser(userId, parentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -9876,6 +9896,7 @@
* <li>{@link #setTrustAgentConfiguration}</li>
* <li>{@link #getRequiredStrongAuthTimeout}</li>
* <li>{@link #setRequiredStrongAuthTimeout}</li>
+ * <li>{@link #getAccountTypesWithManagementDisabled}</li>
* </ul>
* <p>
* The following methods are supported for the parent instance but can only be called by the
@@ -9884,6 +9905,7 @@
* <li>{@link #getPasswordComplexity}</li>
* <li>{@link #setCameraDisabled}</li>
* <li>{@link #getCameraDisabled}</li>
+ * <li>{@link #setAccountManagementDisabled(ComponentName, String, boolean)}</li>
* </ul>
*
* <p>The following methods can be called by the profile owner of a managed profile
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0aed39c..84332ca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -248,9 +248,9 @@
int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
boolean installExistingPackage(in ComponentName admin, in String callerPackage, in String packageName);
- void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
+ void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled, in boolean parent);
String[] getAccountTypesWithManagementDisabled();
- String[] getAccountTypesWithManagementDisabledAsUser(int userId);
+ String[] getAccountTypesWithManagementDisabledAsUser(int userId, in boolean parent);
void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
boolean isSecondaryLockscreenEnabled(int userId);
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 9c4a8f4..5b98188 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -526,15 +526,17 @@
}
/**
- * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
- * statistics that cannot be seen by the kernel to system. To unregister, invoke
- * {@link NetworkStatsProviderCallback#unregister()}.
+ * Registers a custom provider of {@link android.net.NetworkStats} to provide network statistics
+ * to the system. To unregister, invoke {@link NetworkStatsProviderCallback#unregister()}.
+ * Note that no de-duplication of statistics between providers is performed, so each provider
+ * must only report network traffic that is not being reported by any other provider.
*
- * @param tag a human readable identifier of the custom network stats provider.
- * @param provider a custom implementation of {@link AbstractNetworkStatsProvider} that needs to
- * be registered to the system.
+ * @param tag a human readable identifier of the custom network stats provider. This is only
+ * used for debugging.
+ * @param provider the subclass of {@link AbstractNetworkStatsProvider} that needs to be
+ * registered to the system.
* @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the
- * system.
+ * system or unregister the provider.
* @hide
*/
@SystemApi
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index 059cd94..5ab0354 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.shared.InetAddressUtils;
import android.os.Parcel;
@@ -66,6 +67,9 @@
public String serverHostName;
+ @Nullable
+ public String captivePortalApiUrl;
+
public DhcpResults() {
super();
}
@@ -100,6 +104,7 @@
leaseDuration = source.leaseDuration;
mtu = source.mtu;
serverHostName = source.serverHostName;
+ captivePortalApiUrl = source.captivePortalApiUrl;
}
}
@@ -133,6 +138,7 @@
leaseDuration = 0;
mtu = 0;
serverHostName = null;
+ captivePortalApiUrl = null;
}
@Override
@@ -144,6 +150,9 @@
str.append(" lease ").append(leaseDuration).append(" seconds");
if (mtu != 0) str.append(" MTU ").append(mtu);
str.append(" Servername ").append(serverHostName);
+ if (captivePortalApiUrl != null) {
+ str.append(" CaptivePortalApiUrl ").append(captivePortalApiUrl);
+ }
return str.toString();
}
@@ -161,7 +170,8 @@
&& Objects.equals(vendorInfo, target.vendorInfo)
&& Objects.equals(serverHostName, target.serverHostName)
&& leaseDuration == target.leaseDuration
- && mtu == target.mtu;
+ && mtu == target.mtu
+ && Objects.equals(captivePortalApiUrl, target.captivePortalApiUrl);
}
/**
@@ -186,6 +196,7 @@
InetAddressUtils.parcelInetAddress(dest, serverAddress, flags);
dest.writeString(vendorInfo);
dest.writeString(serverHostName);
+ dest.writeString(captivePortalApiUrl);
}
@Override
@@ -201,6 +212,7 @@
dhcpResults.serverAddress = (Inet4Address) InetAddressUtils.unparcelInetAddress(in);
dhcpResults.vendorInfo = in.readString();
dhcpResults.serverHostName = in.readString();
+ dhcpResults.captivePortalApiUrl = in.readString();
return dhcpResults;
}
@@ -305,4 +317,12 @@
public void setMtu(int mtu) {
this.mtu = mtu;
}
+
+ public String getCaptivePortalApiUrl() {
+ return captivePortalApiUrl;
+ }
+
+ public void setCaptivePortalApiUrl(String url) {
+ captivePortalApiUrl = url;
+ }
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 57269c4..ec485d3 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -22,6 +22,7 @@
import android.annotation.TestApi;
import android.content.Context;
import android.os.AsyncTask;
+import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Size;
@@ -32,6 +33,7 @@
import android.view.inline.InlinePresentationSpec;
import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
import com.android.internal.view.inline.IInlineContentCallback;
import com.android.internal.view.inline.IInlineContentProvider;
@@ -62,6 +64,14 @@
private final @Nullable IInlineContentProvider mContentProvider;
/**
+ * Used to keep a strong reference to the callback so it doesn't get garbage collected.
+ *
+ * @hide
+ */
+ @DataClass.ParcelWith(InlineContentCallbackImplParceling.class)
+ private @Nullable InlineContentCallbackImpl mInlineContentCallback;
+
+ /**
* Creates a new {@link InlineSuggestion}, for testing purpose.
*
* @hide
@@ -69,9 +79,19 @@
@TestApi
@NonNull
public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) {
- return new InlineSuggestion(info, null);
+ return new InlineSuggestion(info, null, /* inlineContentCallback */ null);
}
+ /**
+ * Creates a new {@link InlineSuggestion}.
+ *
+ * @hide
+ */
+ public InlineSuggestion(
+ @NonNull InlineSuggestionInfo info,
+ @Nullable IInlineContentProvider contentProvider) {
+ this(info, contentProvider, /* inlineContentCallback */ null);
+ }
@@ -79,12 +99,14 @@
* Inflates a view with the content of this suggestion at a specific size.
* The size must be between the {@link InlinePresentationSpec#getMinSize() min size}
* and the {@link InlinePresentationSpec#getMaxSize() max size} of the presentation
- * spec returned by {@link InlineSuggestionInfo#getPresentationSpec()}. If an invalid
- * argument is passed an exception is thrown.
+ * spec returned by {@link InlineSuggestionInfo#getPresentationSpec()}.
*
* @param context Context in which to inflate the view.
* @param size The size at which to inflate the suggestion.
* @param callback Callback for receiving the inflated view.
+ *
+ * @throws IllegalArgumentException If an invalid argument is passed.
+ * @throws IllegalStateException if this method is already called.
*/
public void inflate(@NonNull Context context, @NonNull Size size,
@NonNull @CallbackExecutor Executor callbackExecutor,
@@ -96,19 +118,15 @@
throw new IllegalArgumentException("size not between min:"
+ minSize + " and max:" + maxSize);
}
+ mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback);
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
if (mContentProvider == null) {
callback.accept(/* view */ null);
return;
}
- // TODO(b/137800469): keep a strong reference to the contentCallback so it doesn't
- // get GC'd. Also add a isInflated() method and make sure the view can only be
- // inflated once.
try {
- InlineContentCallbackImpl contentCallback = new InlineContentCallbackImpl(context,
- callbackExecutor, callback);
mContentProvider.provideContent(size.getWidth(), size.getHeight(),
- new InlineContentCallbackWrapper(contentCallback));
+ new InlineContentCallbackWrapper(mInlineContentCallback));
} catch (RemoteException e) {
Slog.w(TAG, "Error creating suggestion content surface: " + e);
callback.accept(/* view */ null);
@@ -116,6 +134,14 @@
});
}
+ private synchronized InlineContentCallbackImpl getInlineContentCallback(Context context,
+ Executor callbackExecutor, Consumer<View> callback) {
+ if (mInlineContentCallback != null) {
+ throw new IllegalStateException("Already called #inflate()");
+ }
+ return new InlineContentCallbackImpl(context, callbackExecutor, callback);
+ }
+
private static final class InlineContentCallbackWrapper extends IInlineContentCallback.Stub {
private final WeakReference<InlineContentCallbackImpl> mCallbackImpl;
@@ -157,6 +183,22 @@
}
}
+ /**
+ * This class used to provide parcelling logic for InlineContentCallbackImpl. It's intended to
+ * make this parcelling a no-op, since it can't be parceled and we don't need to parcel it.
+ */
+ private static class InlineContentCallbackImplParceling implements
+ Parcelling<InlineContentCallbackImpl> {
+ @Override
+ public void parcel(InlineContentCallbackImpl item, Parcel dest, int parcelFlags) {
+ }
+
+ @Override
+ public InlineContentCallbackImpl unparcel(Parcel source) {
+ return null;
+ }
+ }
+
// Code below generated by codegen v1.0.14.
@@ -175,16 +217,20 @@
/**
* Creates a new InlineSuggestion.
*
+ * @param inlineContentCallback
+ * Used to keep a strong reference to the callback so it doesn't get garbage collected.
* @hide
*/
@DataClass.Generated.Member
public InlineSuggestion(
@NonNull InlineSuggestionInfo info,
- @Nullable IInlineContentProvider contentProvider) {
+ @Nullable IInlineContentProvider contentProvider,
+ @Nullable InlineContentCallbackImpl inlineContentCallback) {
this.mInfo = info;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mInfo);
this.mContentProvider = contentProvider;
+ this.mInlineContentCallback = inlineContentCallback;
// onConstructed(); // You can define this method to get a callback
}
@@ -194,6 +240,16 @@
return mInfo;
}
+ /**
+ * Used to keep a strong reference to the callback so it doesn't get garbage collected.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlineContentCallbackImpl getInlineContentCallback() {
+ return mInlineContentCallback;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -202,7 +258,8 @@
return "InlineSuggestion { " +
"info = " + mInfo + ", " +
- "contentProvider = " + mContentProvider +
+ "contentProvider = " + mContentProvider + ", " +
+ "inlineContentCallback = " + mInlineContentCallback +
" }";
}
@@ -220,7 +277,8 @@
//noinspection PointlessBooleanExpression
return true
&& java.util.Objects.equals(mInfo, that.mInfo)
- && java.util.Objects.equals(mContentProvider, that.mContentProvider);
+ && java.util.Objects.equals(mContentProvider, that.mContentProvider)
+ && java.util.Objects.equals(mInlineContentCallback, that.mInlineContentCallback);
}
@Override
@@ -232,20 +290,34 @@
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mInfo);
_hash = 31 * _hash + java.util.Objects.hashCode(mContentProvider);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mInlineContentCallback);
return _hash;
}
+ @DataClass.Generated.Member
+ static Parcelling<InlineContentCallbackImpl> sParcellingForInlineContentCallback =
+ Parcelling.Cache.get(
+ InlineContentCallbackImplParceling.class);
+ static {
+ if (sParcellingForInlineContentCallback == null) {
+ sParcellingForInlineContentCallback = Parcelling.Cache.put(
+ new InlineContentCallbackImplParceling());
+ }
+ }
+
@Override
@DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mContentProvider != null) flg |= 0x2;
+ if (mInlineContentCallback != null) flg |= 0x4;
dest.writeByte(flg);
dest.writeTypedObject(mInfo, flags);
if (mContentProvider != null) dest.writeStrongInterface(mContentProvider);
+ sParcellingForInlineContentCallback.parcel(mInlineContentCallback, dest, flags);
}
@Override
@@ -255,18 +327,20 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ InlineSuggestion(@NonNull android.os.Parcel in) {
+ /* package-private */ InlineSuggestion(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
InlineSuggestionInfo info = (InlineSuggestionInfo) in.readTypedObject(InlineSuggestionInfo.CREATOR);
IInlineContentProvider contentProvider = (flg & 0x2) == 0 ? null : IInlineContentProvider.Stub.asInterface(in.readStrongBinder());
+ InlineContentCallbackImpl inlineContentCallback = sParcellingForInlineContentCallback.unparcel(in);
this.mInfo = info;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mInfo);
this.mContentProvider = contentProvider;
+ this.mInlineContentCallback = inlineContentCallback;
// onConstructed(); // You can define this method to get a callback
}
@@ -280,16 +354,16 @@
}
@Override
- public InlineSuggestion createFromParcel(@NonNull android.os.Parcel in) {
+ public InlineSuggestion createFromParcel(@NonNull Parcel in) {
return new InlineSuggestion(in);
}
};
@DataClass.Generated(
- time = 1581377984320L,
+ time = 1581929285156L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e34aa97..a47bd17 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -653,17 +653,18 @@
String classPathForElement = "";
boolean compiledSomething = false;
for (String classPathElement : classPathElements) {
- // System server is fully AOTed and never profiled
- // for profile guided compilation.
+ // We default to the verify filter because the compilation will happen on /data and
+ // system server cannot load executable code outside /system.
String systemServerFilter = SystemProperties.get(
- "dalvik.vm.systemservercompilerfilter", "speed");
+ "dalvik.vm.systemservercompilerfilter", "verify");
+ String classLoaderContext =
+ getSystemServerClassLoaderContext(classPathForElement);
int dexoptNeeded;
try {
dexoptNeeded = DexFile.getDexOptNeeded(
classPathElement, instructionSet, systemServerFilter,
- null /* classLoaderContext */, false /* newProfile */,
- false /* downgrade */);
+ classLoaderContext, false /* newProfile */, false /* downgrade */);
} catch (FileNotFoundException ignored) {
// Do not add to the classpath.
Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
@@ -684,8 +685,6 @@
final String compilerFilter = systemServerFilter;
final String uuid = StorageManager.UUID_PRIVATE_INTERNAL;
final String seInfo = null;
- final String classLoaderContext =
- getSystemServerClassLoaderContext(classPathForElement);
final int targetSdkVersion = 0; // SystemServer targets the system's SDK version
try {
installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 4b95e4d..08b30f7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -219,6 +219,9 @@
<permission name="android.permission.WATCH_APPOPS"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.UPDATE_DEVICE_STATS"/>
+ <!-- Permissions required for reading and logging compat changes -->
+ <permission name="android.permission.LOG_COMPAT_CHANGE" />
+ <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
</privapp-permissions>
<privapp-permissions package="com.android.providers.telephony">
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 6689080..70d79378 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -26,6 +26,7 @@
import libcore.content.type.MimeMap;
import java.util.HashMap;
+import java.util.Locale;
/**
* MediaScanner helper class.
@@ -215,23 +216,23 @@
return true;
}
- switch (normalizedMimeType) {
+ switch (normalizedMimeType.toLowerCase(Locale.ROOT)) {
case "application/epub+zip":
case "application/msword":
case "application/pdf":
case "application/rtf":
case "application/vnd.ms-excel":
- case "application/vnd.ms-excel.addin.macroEnabled.12":
- case "application/vnd.ms-excel.sheet.binary.macroEnabled.12":
- case "application/vnd.ms-excel.sheet.macroEnabled.12":
- case "application/vnd.ms-excel.template.macroEnabled.12":
+ case "application/vnd.ms-excel.addin.macroenabled.12":
+ case "application/vnd.ms-excel.sheet.binary.macroenabled.12":
+ case "application/vnd.ms-excel.sheet.macroenabled.12":
+ case "application/vnd.ms-excel.template.macroenabled.12":
case "application/vnd.ms-powerpoint":
- case "application/vnd.ms-powerpoint.addin.macroEnabled.12":
- case "application/vnd.ms-powerpoint.presentation.macroEnabled.12":
- case "application/vnd.ms-powerpoint.slideshow.macroEnabled.12":
- case "application/vnd.ms-powerpoint.template.macroEnabled.12":
- case "application/vnd.ms-word.document.macroEnabled.12":
- case "application/vnd.ms-word.template.macroEnabled.12":
+ case "application/vnd.ms-powerpoint.addin.macroenabled.12":
+ case "application/vnd.ms-powerpoint.presentation.macroenabled.12":
+ case "application/vnd.ms-powerpoint.slideshow.macroenabled.12":
+ case "application/vnd.ms-powerpoint.template.macroenabled.12":
+ case "application/vnd.ms-word.document.macroenabled.12":
+ case "application/vnd.ms-word.template.macroenabled.12":
case "application/vnd.oasis.opendocument.chart":
case "application/vnd.oasis.opendocument.database":
case "application/vnd.oasis.opendocument.formula":
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaFileTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaFileTest.java
index 507dd4a..15a501d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaFileTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaFileTest.java
@@ -97,6 +97,9 @@
assertTrue(isDocumentMimeType("text/plain"));
assertTrue(isDocumentMimeType("application/pdf"));
assertTrue(isDocumentMimeType("application/msword"));
+ assertTrue(isDocumentMimeType("application/vnd.ms-excel.addin.macroEnabled.12"));
+ assertTrue(isDocumentMimeType("application/vnd.ms-powerpoint.addin.macroEnabled.12"));
+ assertTrue(isDocumentMimeType("application/vnd.ms-word.document.macroEnabled.12"));
assertFalse(isDocumentMimeType("audio/mpeg"));
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
index f341bf1..2821af9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
@@ -27,6 +27,11 @@
public static final String KEY_MEDIA_OUTPUT = "media_output";
/**
+ * Key for the Media output group setting.
+ */
+ public static final String KEY_MEDIA_OUTPUT_GROUP = "media_output_group";
+
+ /**
* Key for the Remote Media slice.
*/
public static final String KEY_REMOTE_MEDIA = "remote_media";
@@ -48,6 +53,13 @@
"com.android.settings.panel.action.MEDIA_OUTPUT";
/**
+ * Activity Action: Show a settings dialog containing {@link MediaDevice} to handle media group
+ * operation.
+ */
+ public static final String ACTION_MEDIA_OUTPUT_GROUP =
+ "com.android.settings.panel.action.MEDIA_OUTPUT_GROUP";
+
+ /**
* An string extra specifying a media package name.
*/
public static final String EXTRA_PACKAGE_NAME =
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 530823a..d126ee0 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -126,8 +126,6 @@
* <li>This service calls startBugreport() and passes in local file descriptors to receive
* bugreport artifacts.
* </ol>
- *
- * TODO: There are multiple threads involved. Add synchronization accordingly.
*/
public class BugreportProgressService extends Service {
private static final String TAG = "BugreportProgressService";
@@ -219,6 +217,7 @@
private final Object mLock = new Object();
/** Managed bugreport info (keyed by id) */
+ @GuardedBy("mLock")
private final SparseArray<BugreportInfo> mBugreportInfos = new SparseArray<>();
private Context mContext;
@@ -317,23 +316,26 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- final int size = mBugreportInfos.size();
- if (size == 0) {
- writer.println("No monitored processes");
- return;
- }
- writer.print("Foreground id: "); writer.println(mForegroundId);
- writer.println("\n");
- writer.println("Monitored dumpstate processes");
- writer.println("-----------------------------");
- for (int i = 0; i < size; i++) {
- writer.print("#"); writer.println(i + 1);
- writer.println(getInfo(mBugreportInfos.keyAt(i)));
+ synchronized (mLock) {
+ final int size = mBugreportInfos.size();
+ if (size == 0) {
+ writer.println("No monitored processes");
+ return;
+ }
+ writer.print("Foreground id: "); writer.println(mForegroundId);
+ writer.println("\n");
+ writer.println("Monitored dumpstate processes");
+ writer.println("-----------------------------");
+ for (int i = 0; i < size; i++) {
+ writer.print("#");
+ writer.println(i + 1);
+ writer.println(getInfoLocked(mBugreportInfos.keyAt(i)));
+ }
}
}
private static String getFileName(BugreportInfo info, String suffix) {
- return String.format("%s-%s%s", info.baseName, info.name, suffix);
+ return String.format("%s-%s%s", info.baseName, info.getName(), suffix);
}
private final class BugreportCallbackImpl extends BugreportCallback {
@@ -573,7 +575,8 @@
}
}
- private BugreportInfo getInfo(int id) {
+ @GuardedBy("mLock")
+ private BugreportInfo getInfoLocked(int id) {
final BugreportInfo bugreportInfo = mBugreportInfos.get(id);
if (bugreportInfo == null) {
Log.w(TAG, "Not monitoring bugreports with ID " + id);
@@ -668,12 +671,12 @@
* Updates the system notification for a given bugreport.
*/
private void updateProgress(BugreportInfo info) {
- if (info.progress < 0) {
- Log.e(TAG, "Invalid progress value for " + info);
+ if (info.getProgress() < 0) {
+ Log.e(TAG, "Invalid progress values for " + info);
return;
}
- if (info.finished) {
+ if (info.isFinished()) {
Log.w(TAG, "Not sending progress notification because bugreport has finished already ("
+ info + ")");
return;
@@ -682,7 +685,7 @@
final NumberFormat nf = NumberFormat.getPercentInstance();
nf.setMinimumFractionDigits(2);
nf.setMaximumFractionDigits(2);
- final String percentageText = nf.format((double) info.progress / 100);
+ final String percentageText = nf.format((double) info.getProgress() / 100);
String title = mContext.getString(R.string.bugreport_in_progress_title, info.id);
@@ -690,18 +693,19 @@
if (mIsWatch) {
nf.setMinimumFractionDigits(0);
nf.setMaximumFractionDigits(0);
- final String watchPercentageText = nf.format((double) info.progress / 100);
+ final String watchPercentageText = nf.format((double) info.getProgress() / 100);
title = title + "\n" + watchPercentageText;
}
final String name =
- info.name != null ? info.name : mContext.getString(R.string.bugreport_unnamed);
+ info.getName() != null ? info.getName()
+ : mContext.getString(R.string.bugreport_unnamed);
final Notification.Builder builder = newBaseNotification(mContext)
.setContentTitle(title)
.setTicker(title)
.setContentText(name)
- .setProgress(100 /* max value of progress percentage */, info.progress, false)
+ .setProgress(100 /* max value of progress percentage */, info.getProgress(), false)
.setOngoing(true);
// Wear and ATV bugreport doesn't need the bug info dialog, screenshot and cancel action.
@@ -730,10 +734,10 @@
.setActions(infoAction, screenshotAction, cancelAction);
}
// Show a debug log, every LOG_PROGRESS_STEP percent.
- final int progress = info.progress;
+ final int progress = info.getProgress();
- if ((info.progress == 0) || (info.progress >= 100) ||
- ((progress / LOG_PROGRESS_STEP) != (mLastProgressPercent / LOG_PROGRESS_STEP))) {
+ if ((info.getProgress() == 0) || (info.getProgress() >= 100)
+ || ((progress / LOG_PROGRESS_STEP) != (mLastProgressPercent / LOG_PROGRESS_STEP))) {
Log.d(TAG, "Progress #" + info.id + ": " + percentageText);
}
mLastProgressPercent = progress;
@@ -778,10 +782,10 @@
mBugreportInfos.remove(id);
}
// Must stop foreground service first, otherwise notif.cancel() will fail below.
- stopForegroundWhenDone(id);
+ stopForegroundWhenDoneLocked(id);
Log.d(TAG, "stopProgress(" + id + "): cancel notification");
NotificationManager.from(mContext).cancel(id);
- stopSelfWhenDone();
+ stopSelfWhenDoneLocked();
}
/**
@@ -791,13 +795,13 @@
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_CANCEL);
Log.v(TAG, "cancel: ID=" + id);
mInfoDialog.cancel();
- final BugreportInfo info = getInfo(id);
- if (info != null && !info.finished) {
- Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
- mBugreportManager.cancelBugreport();
- deleteScreenshots(info);
- }
synchronized (mLock) {
+ final BugreportInfo info = getInfoLocked(id);
+ if (info != null && !info.isFinished()) {
+ Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
+ mBugreportManager.cancelBugreport();
+ deleteScreenshots(info);
+ }
stopProgressLocked(id);
}
}
@@ -808,7 +812,10 @@
*/
private void launchBugreportInfoDialog(int id) {
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_DETAILS);
- final BugreportInfo info = getInfo(id);
+ final BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
if (info == null) {
// Most likely am killed Shell before user tapped the notification. Since system might
// be too busy anwyays, it's better to ignore the notification and switch back to the
@@ -842,7 +849,11 @@
*/
private void takeScreenshot(int id) {
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
- if (getInfo(id) == null) {
+ BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
+ if (info == null) {
// Most likely am killed Shell before user tapped the notification. Since system might
// be too busy anwyays, it's better to ignore the notification and switch back to the
// non-interactive mode (where the bugerport will be shared upon completion).
@@ -877,9 +888,11 @@
mServiceHandler.sendMessageDelayed(msg, DateUtils.SECOND_IN_MILLIS);
return;
}
-
+ final BugreportInfo info;
// It's time to take the screenshot: let the proper thread handle it
- final BugreportInfo info = getInfo(id);
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
if (info == null) {
return;
}
@@ -895,11 +908,11 @@
* SCREENSHOT button is enabled or disabled accordingly.
*/
private void setTakingScreenshot(boolean flag) {
- synchronized (BugreportProgressService.this) {
+ synchronized (mLock) {
mTakingScreenshot = flag;
for (int i = 0; i < mBugreportInfos.size(); i++) {
- final BugreportInfo info = getInfo(mBugreportInfos.keyAt(i));
- if (info.finished) {
+ final BugreportInfo info = getInfoLocked(mBugreportInfos.keyAt(i));
+ if (info.isFinished()) {
Log.d(TAG, "Not updating progress for " + info.id + " while taking screenshot"
+ " because share notification was already sent");
continue;
@@ -920,7 +933,10 @@
private void handleScreenshotResponse(Message resultMsg) {
final boolean taken = resultMsg.arg2 != 0;
- final BugreportInfo info = getInfo(resultMsg.arg1);
+ final BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(resultMsg.arg1);
+ }
if (info == null) {
return;
}
@@ -929,7 +945,7 @@
final String msg;
if (taken) {
info.addScreenshot(screenshotFile);
- if (info.finished) {
+ if (info.isFinished()) {
Log.d(TAG, "Screenshot finished after bugreport; updating share notification");
info.renameScreenshots();
sendBugreportNotification(info, mTakingScreenshot);
@@ -955,9 +971,10 @@
/**
* Stop running on foreground once there is no more active bugreports being watched.
*/
- private void stopForegroundWhenDone(int id) {
+ @GuardedBy("mLock")
+ private void stopForegroundWhenDoneLocked(int id) {
if (id != mForegroundId) {
- Log.d(TAG, "stopForegroundWhenDone(" + id + "): ignoring since foreground id is "
+ Log.d(TAG, "stopForegroundWhenDoneLocked(" + id + "): ignoring since foreground id is "
+ mForegroundId);
return;
}
@@ -970,8 +987,8 @@
final int total = mBugreportInfos.size();
if (total > 0) {
for (int i = 0; i < total; i++) {
- final BugreportInfo info = getInfo(mBugreportInfos.keyAt(i));
- if (!info.finished) {
+ final BugreportInfo info = getInfoLocked(mBugreportInfos.keyAt(i));
+ if (!info.isFinished()) {
updateProgress(info);
break;
}
@@ -982,7 +999,8 @@
/**
* Finishes the service when it's not monitoring any more processes.
*/
- private void stopSelfWhenDone() {
+ @GuardedBy("mLock")
+ private void stopSelfWhenDoneLocked() {
if (mBugreportInfos.size() > 0) {
if (DEBUG) Log.d(TAG, "Staying alive, waiting for IDs " + mBugreportInfos);
return;
@@ -995,12 +1013,14 @@
* Wraps up bugreport generation and triggers a notification to share the bugreport.
*/
private void onBugreportFinished(BugreportInfo info) {
- Log.d(TAG, "Bugreport finished with title: " + info.title
+ Log.d(TAG, "Bugreport finished with title: " + info.getTitle()
+ " and shareDescription: " + info.shareDescription);
- info.finished = true;
+ info.setFinished(true);
- // Stop running on foreground, otherwise share notification cannot be dismissed.
- stopForegroundWhenDone(info.id);
+ synchronized (mLock) {
+ // Stop running on foreground, otherwise share notification cannot be dismissed.
+ stopForegroundWhenDoneLocked(info.id);
+ }
triggerLocalNotification(mContext, info);
}
@@ -1062,8 +1082,8 @@
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setType(mimeType);
- final String subject = !TextUtils.isEmpty(info.title) ?
- info.title : bugreportUri.getLastPathSegment();
+ final String subject = !TextUtils.isEmpty(info.getTitle())
+ ? info.getTitle() : bugreportUri.getLastPathSegment();
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
// EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
@@ -1074,9 +1094,9 @@
.append("\nSerial number: ")
.append(SystemProperties.get("ro.serialno"));
int descriptionLength = 0;
- if (!TextUtils.isEmpty(info.description)) {
- messageBody.append("\nDescription: ").append(info.description);
- descriptionLength = info.description.length();
+ if (!TextUtils.isEmpty(info.getDescription())) {
+ messageBody.append("\nDescription: ").append(info.getDescription());
+ descriptionLength = info.getDescription().length();
}
intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
final ClipData clipData = new ClipData(null, new String[] { mimeType },
@@ -1117,12 +1137,17 @@
*/
private void shareBugreport(int id, BugreportInfo sharedInfo) {
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SHARE);
- BugreportInfo info = getInfo(id);
+ BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
if (info == null) {
// Service was terminated but notification persisted
info = sharedInfo;
- Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
- + mBugreportInfos + "), using info from intent instead (" + info + ")");
+ synchronized (mLock) {
+ Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
+ + mBugreportInfos + "), using info from intent instead (" + info + ")");
+ }
} else {
Log.v(TAG, "shareBugReport(): id " + id + " info = " + info);
}
@@ -1194,10 +1219,10 @@
mContext.getString(R.string.bugreport_finished_pending_screenshot_text)
: mContext.getString(R.string.bugreport_finished_text);
final String title;
- if (TextUtils.isEmpty(info.title)) {
+ if (TextUtils.isEmpty(info.getTitle())) {
title = mContext.getString(R.string.bugreport_finished_title, info.id);
} else {
- title = info.title;
+ title = info.getTitle();
if (!TextUtils.isEmpty(info.shareDescription)) {
if(!takingScreenshot) content = info.shareDescription;
}
@@ -1211,8 +1236,8 @@
PendingIntent.FLAG_UPDATE_CURRENT))
.setDeleteIntent(newCancelIntent(mContext, info));
- if (!TextUtils.isEmpty(info.name)) {
- builder.setSubText(info.name);
+ if (!TextUtils.isEmpty(info.getName())) {
+ builder.setSubText(info.getName());
}
Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
@@ -1305,13 +1330,14 @@
}
}
+ @GuardedBy("mLock")
private void addDetailsToZipFileLocked(BugreportInfo info) {
if (info.bugreportFile == null) {
// One possible reason is a bug in the Parcelization code.
Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
return;
}
- if (TextUtils.isEmpty(info.title) && TextUtils.isEmpty(info.description)) {
+ if (TextUtils.isEmpty(info.getTitle()) && TextUtils.isEmpty(info.getDescription())) {
Log.d(TAG, "Not touching zip file since neither title nor description are set");
return;
}
@@ -1344,8 +1370,8 @@
}
// Then add the user-provided info.
- addEntry(zos, "title.txt", info.title);
- addEntry(zos, "description.txt", info.description);
+ addEntry(zos, "title.txt", info.getTitle());
+ addEntry(zos, "description.txt", info.getDescription());
} catch (IOException e) {
Log.e(TAG, "exception zipping file " + tmpZip, e);
Toast.makeText(mContext, R.string.bugreport_add_details_to_zip_failed,
@@ -1355,7 +1381,7 @@
// Make sure it only tries to add details once, even it fails the first time.
info.addedDetailsToZip = true;
info.addingDetailsToZip = false;
- stopForegroundWhenDone(info.id);
+ stopForegroundWhenDoneLocked(info.id);
}
if (!tmpZip.renameTo(info.bugreportFile)) {
@@ -1508,24 +1534,27 @@
* Updates the user-provided details of a bugreport.
*/
private void updateBugreportInfo(int id, String name, String title, String description) {
- final BugreportInfo info = getInfo(id);
+ final BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
if (info == null) {
return;
}
- if (title != null && !title.equals(info.title)) {
+ if (title != null && !title.equals(info.getTitle())) {
Log.d(TAG, "updating bugreport title: " + title);
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_TITLE_CHANGED);
}
- info.title = title;
- if (description != null && !description.equals(info.description)) {
+ info.setTitle(title);
+ if (description != null && !description.equals(info.getDescription())) {
Log.d(TAG, "updating bugreport description: " + description.length() + " chars");
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_DESCRIPTION_CHANGED);
}
- info.description = description;
- if (name != null && !name.equals(info.name)) {
+ info.setDescription(description);
+ if (name != null && !name.equals(info.getName())) {
Log.d(TAG, "updating bugreport name: " + name);
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_NAME_CHANGED);
- info.name = name;
+ info.setName(name);
updateProgress(info);
}
}
@@ -1640,14 +1669,14 @@
// Then set fields.
mId = info.id;
- if (!TextUtils.isEmpty(info.name)) {
- mInfoName.setText(info.name);
+ if (!TextUtils.isEmpty(info.getName())) {
+ mInfoName.setText(info.getName());
}
- if (!TextUtils.isEmpty(info.title)) {
- mInfoTitle.setText(info.title);
+ if (!TextUtils.isEmpty(info.getTitle())) {
+ mInfoTitle.setText(info.getTitle());
}
- if (!TextUtils.isEmpty(info.description)) {
- mInfoDescription.setText(info.description);
+ if (!TextUtils.isEmpty(info.getDescription())) {
+ mInfoDescription.setText(info.getDescription());
}
// And finally display it.
@@ -1666,7 +1695,7 @@
@Override
public void onClick(View view) {
MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
- sanitizeName(info.name);
+ sanitizeName(info.getName());
final String name = mInfoName.getText().toString();
final String title = mInfoTitle.getText().toString();
final String description = mInfoDescription.getText().toString();
@@ -1817,6 +1846,8 @@
*/
int type;
+ private final Object mLock = new Object();
+
/**
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
*/
@@ -1855,6 +1886,78 @@
return getFd(screenshotFiles.get(0));
}
+ void setFinished(boolean isFinished) {
+ synchronized (mLock) {
+ this.finished = isFinished;
+ }
+ }
+
+ boolean isFinished() {
+ synchronized (mLock) {
+ return finished;
+ }
+ }
+
+ void setTitle(String title) {
+ synchronized (mLock) {
+ this.title = title;
+ }
+ }
+
+ String getTitle() {
+ synchronized (mLock) {
+ return title;
+ }
+ }
+
+ void setName(String name) {
+ synchronized (mLock) {
+ this.name = name;
+ }
+ }
+
+ String getName() {
+ synchronized (mLock) {
+ return name;
+ }
+ }
+
+ void setDescription(String description) {
+ synchronized (mLock) {
+ this.description = description;
+ }
+ }
+
+ String getDescription() {
+ synchronized (mLock) {
+ return description;
+ }
+ }
+
+ void setProgress(int progress) {
+ synchronized (mLock) {
+ this.progress = progress;
+ }
+ }
+
+ int getProgress() {
+ synchronized (mLock) {
+ return progress;
+ }
+ }
+
+ void setLastUpdate(long lastUpdate) {
+ synchronized (mLock) {
+ this.lastUpdate = lastUpdate;
+ }
+ }
+
+ long getLastUpdate() {
+ synchronized (mLock) {
+ return lastUpdate;
+ }
+ }
+
/**
* Gets the name for next user triggered screenshot file.
*/
@@ -1924,9 +2027,9 @@
if (context == null) {
// Restored from Parcel
return formattedLastUpdate == null ?
- Long.toString(lastUpdate) : formattedLastUpdate;
+ Long.toString(getLastUpdate()) : formattedLastUpdate;
}
- return DateUtils.formatDateTime(context, lastUpdate,
+ return DateUtils.formatDateTime(context, getLastUpdate(),
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
}
@@ -2047,13 +2150,13 @@
progress = CAPPED_PROGRESS;
}
if (DEBUG) {
- if (progress != info.progress) {
- Log.v(TAG, "Updating progress for name " + info.name + "(id: " + info.id
- + ") from " + info.progress + " to " + progress);
+ if (progress != info.getProgress()) {
+ Log.v(TAG, "Updating progress for name " + info.getName() + "(id: " + info.id
+ + ") from " + info.getProgress() + " to " + progress);
}
}
- info.progress = progress;
- info.lastUpdate = System.currentTimeMillis();
+ info.setProgress(progress);
+ info.setLastUpdate(System.currentTimeMillis());
updateProgress(info);
}
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index f39e7af..2653b6d 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -756,7 +756,7 @@
final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
mIpv4Address.getPrefixLength());
NetdUtils.tetherInterface(mNetd, mIfaceName, ipv4Prefix);
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | IllegalStateException e) {
mLog.e("Error Tethering: " + e);
mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
return;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b334b26..60a30d3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -64,6 +64,7 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.net.CaptivePortal;
+import android.net.CaptivePortalData;
import android.net.ConnectionInfo;
import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import android.net.ConnectivityDiagnosticsManager.DataStallReport;
@@ -546,6 +547,14 @@
public static final int EVENT_PROBE_STATUS_CHANGED = 46;
/**
+ * Event for NetworkMonitor to inform ConnectivityService that captive portal data has changed.
+ * arg1 = unused
+ * arg2 = netId
+ * obj = captive portal data
+ */
+ private static final int EVENT_CAPPORT_DATA_CHANGED = 47;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -2824,6 +2833,12 @@
updatePrivateDns(nai, (PrivateDnsConfig) msg.obj);
break;
}
+ case EVENT_CAPPORT_DATA_CHANGED: {
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
+ if (nai == null) break;
+ handleCaptivePortalDataUpdate(nai, (CaptivePortalData) msg.obj);
+ break;
+ }
}
return true;
}
@@ -2991,6 +3006,13 @@
}
@Override
+ public void notifyCaptivePortalDataChanged(CaptivePortalData data) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_CAPPORT_DATA_CHANGED,
+ 0, mNetId, data));
+ }
+
+ @Override
public void showProvisioningNotification(String action, String packageName) {
final Intent intent = new Intent(action);
intent.setPackage(packageName);
@@ -3118,6 +3140,13 @@
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
+ private void handleCaptivePortalDataUpdate(@NonNull final NetworkAgentInfo nai,
+ @Nullable final CaptivePortalData data) {
+ nai.captivePortalData = data;
+ // CaptivePortalData will be merged into LinkProperties from NetworkAgentInfo
+ handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
+ }
+
/**
* Updates the linger state from the network requests inside the NAI.
* @param nai the agent info to update
@@ -5838,6 +5867,10 @@
updateWakeOnLan(newLp);
+ // Captive portal data is obtained from NetworkMonitor and stored in NetworkAgentInfo,
+ // it is not contained in LinkProperties sent from NetworkAgents so needs to be merged here.
+ newLp.setCaptivePortalData(networkAgent.captivePortalData);
+
// TODO - move this check to cover the whole function
if (!Objects.equals(newLp, oldLp)) {
synchronized (networkAgent) {
@@ -6898,6 +6931,15 @@
// worry about multiple different substates of CONNECTED.
newInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, info.getReason(),
info.getExtraInfo());
+ } else if (!suspended && info.getDetailedState() == NetworkInfo.DetailedState.SUSPENDED) {
+ // SUSPENDED state is currently only overridden from CONNECTED state. In the case the
+ // network agent is created, then goes to suspended, then goes out of suspended without
+ // ever setting connected. Check if network agent is ever connected to update the state.
+ newInfo.setDetailedState(nai.everConnected
+ ? NetworkInfo.DetailedState.CONNECTED
+ : NetworkInfo.DetailedState.CONNECTING,
+ info.getReason(),
+ info.getExtraInfo());
}
newInfo.setRoaming(!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING));
return newInfo;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 592be2f..4612cfd 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.net.CaptivePortalData;
import android.net.IDnsResolver;
import android.net.INetd;
import android.net.INetworkMonitor;
@@ -167,6 +168,10 @@
// Set to true when partial connectivity was detected.
public boolean partialConnectivity;
+ // Captive portal info of the network, if any.
+ // Obtained by ConnectivityService and merged into NetworkAgent-provided information.
+ public CaptivePortalData captivePortalData;
+
// Networks are lingered when they become unneeded as a result of their NetworkRequests being
// satisfied by a higher-scoring network. so as to allow communication to wrap up before the
// network is taken down. This usually only happens to the default network. Lingering ends with
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index b24a938..563dcf7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -130,7 +130,7 @@
Set<String> packageNames, int userId);
/**
- * Notifies that any of the {@link AbstractNetworkStatsProvider} has reached its quota
+ * Notifies that the specified {@link AbstractNetworkStatsProvider} has reached its quota
* which was set through {@link AbstractNetworkStatsProvider#setLimit(String, long)}.
*
* @param tag the human readable identifier of the custom network stats provider.
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index bb954ab..aacb46e9 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4588,13 +4588,13 @@
final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
removeInterfaceQuota(iface);
setInterfaceQuota(iface, quota);
- mNetworkStats.setStatsProviderLimit(iface, quota);
+ mNetworkStats.setStatsProviderLimitAsync(iface, quota);
return true;
}
case MSG_REMOVE_INTERFACE_QUOTA: {
final String iface = (String) msg.obj;
removeInterfaceQuota(iface);
- mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED);
+ mNetworkStats.setStatsProviderLimitAsync(iface, QUOTA_UNLIMITED);
return true;
}
case MSG_RESET_FIREWALL_RULES_BY_UID: {
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 6d72cb5..0cb0bc2c 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -40,5 +40,5 @@
* Set the quota limit to all registered custom network stats providers.
* Note that invocation of any interface will be sent to all providers.
*/
- public abstract void setStatsProviderLimit(@NonNull String iface, long quota);
+ public abstract void setStatsProviderLimitAsync(@NonNull String iface, long quota);
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index bde9ee2..0662400 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -155,6 +155,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
/**
* Collect and persist detailed network statistics, and provide this data to
@@ -255,7 +257,6 @@
}
private final Object mStatsLock = new Object();
- private final Object mStatsProviderLock = new Object();
/** Set of currently active ifaces. */
@GuardedBy("mStatsLock")
@@ -280,8 +281,11 @@
private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
new DropBoxNonMonotonicObserver();
+ private static final int MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS = 100;
private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList =
new RemoteCallbackList<>();
+ /** Semaphore used to wait for stats provider to respond to request stats update. */
+ private final Semaphore mStatsProviderSem = new Semaphore(0, true);
@GuardedBy("mStatsLock")
private NetworkStatsRecorder mDevRecorder;
@@ -1337,6 +1341,25 @@
final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0;
+ // Request asynchronous stats update from all providers for next poll. And wait a bit of
+ // time to allow providers report-in given that normally binder call should be fast.
+ // TODO: request with a valid token.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "provider.requestStatsUpdate");
+ final int registeredCallbackCount = mStatsProviderCbList.getRegisteredCallbackCount();
+ mStatsProviderSem.drainPermits();
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */));
+ try {
+ mStatsProviderSem.tryAcquire(registeredCallbackCount,
+ MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // Strictly speaking it's possible a provider happened to deliver between the timeout
+ // and the log, and that doesn't matter too much as this is just a debug log.
+ Log.d(TAG, "requestStatsUpdate - providers responded "
+ + mStatsProviderSem.availablePermits()
+ + "/" + registeredCallbackCount + " : " + e);
+ }
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+
// TODO: consider marking "untrusted" times in historical stats
final long currentTime = mClock.millis();
@@ -1374,10 +1397,6 @@
performSampleLocked();
}
- // request asynchronous stats update from all providers for next poll.
- // TODO: request with a valid token.
- invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */));
-
// finally, dispatch updated event to any listeners
final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1501,8 +1520,8 @@
}
@Override
- public void setStatsProviderLimit(@NonNull String iface, long quota) {
- Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")");
+ public void setStatsProviderLimitAsync(@NonNull String iface, long quota) {
+ Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")");
invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota));
}
}
@@ -1783,9 +1802,9 @@
* {@code unregister()} of the returned callback.
*
* @param tag a human readable identifier of the custom network stats provider.
- * @param provider the binder interface of
- * {@link android.net.netstats.provider.AbstractNetworkStatsProvider} that
- * needs to be registered to the system.
+ * @param provider the {@link INetworkStatsProvider} binder corresponding to the
+ * {@link android.net.netstats.provider.AbstractNetworkStatsProvider} to be
+ * registered.
*
* @return a binder interface of
* {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be
@@ -1798,7 +1817,8 @@
Objects.requireNonNull(tag, "tag is null");
try {
NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
- tag, provider, mAlertObserver, mStatsProviderCbList);
+ tag, provider, mStatsProviderSem, mAlertObserver,
+ mStatsProviderCbList);
mStatsProviderCbList.register(callback);
Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
+ getCallingUid() + "/" + getCallingPid());
@@ -1823,7 +1843,7 @@
private void invokeForAllStatsProviderCallbacks(
@NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) {
- synchronized (mStatsProviderCbList) {
+ synchronized (mStatsLock) {
final int length = mStatsProviderCbList.beginBroadcast();
try {
for (int i = 0; i < length; i++) {
@@ -1844,25 +1864,30 @@
private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub
implements IBinder.DeathRecipient {
@NonNull final String mTag;
- @NonNull private final Object mProviderStatsLock = new Object();
+
@NonNull final INetworkStatsProvider mProvider;
+ @NonNull private final Semaphore mSemaphore;
@NonNull final INetworkManagementEventObserver mAlertObserver;
@NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+ @NonNull private final Object mProviderStatsLock = new Object();
+
@GuardedBy("mProviderStatsLock")
- // STATS_PER_IFACE and STATS_PER_UID
+ // Track STATS_PER_IFACE and STATS_PER_UID separately.
private final NetworkStats mIfaceStats = new NetworkStats(0L, 0);
@GuardedBy("mProviderStatsLock")
private final NetworkStats mUidStats = new NetworkStats(0L, 0);
NetworkStatsProviderCallbackImpl(
@NonNull String tag, @NonNull INetworkStatsProvider provider,
+ @NonNull Semaphore semaphore,
@NonNull INetworkManagementEventObserver alertObserver,
@NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList)
throws RemoteException {
mTag = tag;
mProvider = provider;
mProvider.asBinder().linkToDeath(this, 0);
+ mSemaphore = semaphore;
mAlertObserver = alertObserver;
mStatsProviderCbList = cbList;
}
@@ -1881,7 +1906,8 @@
default:
throw new IllegalArgumentException("Invalid type: " + how);
}
- // Return a defensive copy instead of local reference.
+ // Callers might be able to mutate the returned object. Return a defensive copy
+ // instead of local reference.
return stats.clone();
}
}
@@ -1895,6 +1921,7 @@
if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats);
if (uidStats != null) mUidStats.combineAllValues(uidStats);
}
+ mSemaphore.release();
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 063ad0f..707e8ad 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11055,14 +11055,21 @@
@Override
public void setAccountManagementDisabled(ComponentName who, String accountType,
- boolean disabled) {
+ boolean disabled, boolean parent) {
if (!mHasFeature) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
synchronized (getLockObject()) {
+ /*
+ * When called on the parent DPM instance (parent == true), affects active admin
+ * selection in two ways:
+ * * The ActiveAdmin must be of an org-owned profile owner.
+ * * The parent ActiveAdmin instance should be used for managing the restriction.
+ */
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ parent ? DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER
+ : DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
if (disabled) {
ap.accountTypesWithManagementDisabled.add(accountType);
} else {
@@ -11074,22 +11081,34 @@
@Override
public String[] getAccountTypesWithManagementDisabled() {
- return getAccountTypesWithManagementDisabledAsUser(UserHandle.getCallingUserId());
+ return getAccountTypesWithManagementDisabledAsUser(UserHandle.getCallingUserId(), false);
}
@Override
- public String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
+ public String[] getAccountTypesWithManagementDisabledAsUser(int userId, boolean parent) {
enforceFullCrossUsersPermission(userId);
if (!mHasFeature) {
return null;
}
synchronized (getLockObject()) {
- DevicePolicyData policy = getUserData(userId);
- final int N = policy.mAdminList.size();
- ArraySet<String> resultSet = new ArraySet<>();
- for (int i = 0; i < N; i++) {
- ActiveAdmin admin = policy.mAdminList.get(i);
- resultSet.addAll(admin.accountTypesWithManagementDisabled);
+ final ArraySet<String> resultSet = new ArraySet<>();
+
+ if (!parent) {
+ final DevicePolicyData policy = getUserData(userId);
+ for (ActiveAdmin admin : policy.mAdminList) {
+ resultSet.addAll(admin.accountTypesWithManagementDisabled);
+ }
+ }
+
+ // Check if there's a profile owner of an org-owned device and the method is called for
+ // the parent user of this profile owner.
+ final ActiveAdmin orgOwnedAdmin =
+ getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+ final boolean shouldGetParentAccounts = orgOwnedAdmin != null && (parent
+ || UserHandle.getUserId(orgOwnedAdmin.getUid()) != userId);
+ if (shouldGetParentAccounts) {
+ resultSet.addAll(
+ orgOwnedAdmin.getParentActiveAdmin().accountTypesWithManagementDisabled);
}
return resultSet.toArray(new String[resultSet.size()]);
}
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 7b2b30b..37ce510 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -96,7 +96,6 @@
import android.util.ArraySet;
import android.util.Pair;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -6058,6 +6057,55 @@
.thenReturn(packages);
}
+ public void testSetAccountTypesWithManagementDisabledOnManagedProfile() throws Exception {
+ setupProfileOwner();
+
+ final String accountType = "com.example.account.type";
+ int originalUid = mContext.binder.callingUid;
+ dpm.setAccountManagementDisabled(admin1, accountType, true);
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).asList().containsExactly(
+ accountType);
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+
+ mContext.binder.callingUid = originalUid;
+ dpm.setAccountManagementDisabled(admin1, accountType, false);
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ }
+
+ public void testSetAccountTypesWithManagementDisabledOnOrgOwnedManagedProfile()
+ throws Exception {
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
+
+ int originalUid = mContext.binder.callingUid;
+ final String accountType = "com.example.account.type";
+ dpm.getParentProfileInstance(admin1).setAccountManagementDisabled(admin1, accountType,
+ true);
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).asList().containsExactly(
+ accountType);
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).asList().containsExactly(
+ accountType);
+
+ mContext.binder.callingUid = originalUid;
+ dpm.getParentProfileInstance(admin1).setAccountManagementDisabled(admin1, accountType,
+ false);
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ }
+
// admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index a1baf0e..7e3cfc8 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -1779,7 +1779,7 @@
// Get active mobile network in place
expectMobileDefaults();
mService.updateNetworks();
- verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE);
+ verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, Long.MAX_VALUE);
// Set limit to 10KB.
setNetworkPolicies(new NetworkPolicy(
@@ -1788,7 +1788,7 @@
postMsgAndWaitForCompletion();
// Verifies that remaining quota is set to providers.
- verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L);
+ verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L);
reset(mStatsService);
@@ -1810,7 +1810,7 @@
postMsgAndWaitForCompletion();
verify(mStatsService).forceUpdate();
postMsgAndWaitForCompletion();
- verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L - 1999L);
+ verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L - 1999L);
}
/**
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 968f552..2e59966 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -799,6 +799,14 @@
mProbesSucceeded = probesSucceeded;
}
+ void notifyCaptivePortalDataChanged(CaptivePortalData data) {
+ try {
+ mNmCallbacks.notifyCaptivePortalDataChanged(data);
+ } catch (RemoteException e) {
+ throw new AssertionError("This cannot happen", e);
+ }
+ }
+
public String waitForRedirectUrl() {
assertTrue(mNetworkStatusReceived.block(TIMEOUT_MS));
return mRedirectUrl;
@@ -1845,18 +1853,21 @@
final Uri capportUrl = Uri.parse("https://capport.example.com/api");
final CaptivePortalData capportData = new CaptivePortalData.Builder()
.setCaptive(true).build();
- newLp.setCaptivePortalApiUrl(capportUrl);
- newLp.setCaptivePortalData(capportData);
- mWiFiNetworkAgent.sendLinkProperties(newLp);
final Uri expectedCapportUrl = sanitized ? null : capportUrl;
- final CaptivePortalData expectedCapportData = sanitized ? null : capportData;
+ newLp.setCaptivePortalApiUrl(capportUrl);
+ mWiFiNetworkAgent.sendLinkProperties(newLp);
callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
- Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())
- && Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
+ Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()));
defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
- Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())
- && Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
+ Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()));
+
+ final CaptivePortalData expectedCapportData = sanitized ? null : capportData;
+ mWiFiNetworkAgent.notifyCaptivePortalDataChanged(capportData);
+ callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
+ Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
+ defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
+ Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork());
assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl());
@@ -2810,6 +2821,40 @@
assertNoCallbacks(captivePortalCallback, validatedCallback);
}
+ @Test
+ public void testCaptivePortalApi() throws Exception {
+ mServiceContext.setPermission(
+ android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+
+ final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+ final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+ mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ final String redirectUrl = "http://example.com/firstPath";
+
+ mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
+ captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+ final CaptivePortalData testData = new CaptivePortalData.Builder()
+ .setUserPortalUrl(Uri.parse(redirectUrl))
+ .setBytesRemaining(12345L)
+ .build();
+
+ mWiFiNetworkAgent.notifyCaptivePortalDataChanged(testData);
+
+ captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+ lp -> testData.equals(lp.getCaptivePortalData()));
+
+ final LinkProperties newLps = new LinkProperties();
+ newLps.setMtu(1234);
+ mWiFiNetworkAgent.sendLinkProperties(newLps);
+ // CaptivePortalData is not lost and unchanged when LPs are received from the NetworkAgent
+ captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+ lp -> testData.equals(lp.getCaptivePortalData()) && lp.getMtu() == 1234);
+ }
+
private NetworkRequest.Builder newWifiRequestBuilder() {
return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
}
@@ -3154,6 +3199,7 @@
mCellNetworkAgent);
cellNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
+ assertEquals(NetworkInfo.State.SUSPENDED, mCm.getActiveNetworkInfo().getState());
// Register a garden variety default network request.
TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback();
@@ -3169,6 +3215,7 @@
mCellNetworkAgent);
cellNetworkCallback.expectCallback(CallbackEntry.RESUMED, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
+ assertEquals(NetworkInfo.State.CONNECTED, mCm.getActiveNetworkInfo().getState());
dfltNetworkCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(dfltNetworkCallback);