summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt22
-rw-r--r--api/system-current.txt21
-rw-r--r--api/test-current.txt22
-rw-r--r--core/java/android/app/AuthenticationRequiredException.java99
-rw-r--r--core/java/android/app/Fragment.java3
-rw-r--r--core/java/android/app/RecoverableSecurityException.java3
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java14
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/PackageParser.java2
-rw-r--r--core/java/android/hardware/Sensor.java12
-rw-r--r--core/java/android/provider/DocumentsContract.java9
-rw-r--r--core/java/android/provider/DocumentsProvider.java120
-rw-r--r--core/java/android/text/StaticLayout.java2
-rw-r--r--core/java/android/widget/PopupWindow.java25
-rw-r--r--core/java/android/widget/TextView.java4
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java14
-rwxr-xr-xcore/jni/android/graphics/Bitmap.cpp50
-rw-r--r--core/res/res/drawable-hdpi/stat_notify_mmcc_indication_icn.pngbin0 -> 3688 bytes
-rw-r--r--core/res/res/drawable-xhdpi/stat_notify_mmcc_indication_icn.pngbin0 -> 3720 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/stat_notify_mmcc_indication_icn.pngbin0 -> 3876 bytes
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--graphics/java/android/graphics/Typeface.java6
-rw-r--r--keystore/java/android/security/KeyChain.java3
-rw-r--r--libs/hwui/tests/unit/RenderNodeDrawableTests.cpp6
-rw-r--r--media/java/android/media/tv/TvContract.java5
-rw-r--r--packages/SystemUI/res/values/strings.xml47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java7
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java8
-rw-r--r--services/core/java/com/android/server/fingerprint/EnumerateClient.java6
-rw-r--r--services/core/java/com/android/server/fingerprint/FingerprintService.java198
-rw-r--r--services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java94
-rw-r--r--services/core/java/com/android/server/fingerprint/InternalRemovalClient.java33
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java2
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java56
-rw-r--r--services/core/java/com/android/server/policy/GlobalActions.java6
-rw-r--r--services/core/java/com/android/server/security/KeyChainSystemService.java86
-rw-r--r--services/java/com/android/server/SystemServer.java5
-rw-r--r--services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java2
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java73
-rw-r--r--tests/testables/src/android/testing/AndroidTestingRunner.java48
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java215
-rw-r--r--tests/testables/tests/src/android/testing/TestableLooperTest.java60
-rw-r--r--wifi/java/android/net/wifi/hotspot2/ConfigParser.java21
-rw-r--r--wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java6
-rw-r--r--wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64171
-rw-r--r--wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf5
-rw-r--r--wifi/tests/assets/pps/PerProviderSubscription.xml7
53 files changed, 1231 insertions, 482 deletions
diff --git a/api/current.txt b/api/current.txt
index e341739ab6ef..d61b7a86e567 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4297,6 +4297,14 @@ package android.app {
field public java.lang.String serviceDetails;
}
+ public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AuthenticationRequiredException> CREATOR;
+ }
+
public final class AutomaticZenRule implements android.os.Parcelable {
ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
@@ -5708,17 +5716,6 @@ package android.app {
field public static final int STYLE_SPINNER = 0; // 0x0
}
- public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
- ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction);
- method public int describeContents();
- method public android.app.RemoteAction getUserAction();
- method public java.lang.CharSequence getUserMessage();
- method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context, java.lang.String);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
- }
-
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.RemoteAction clone();
@@ -6426,7 +6423,7 @@ package android.app.admin {
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS";
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
- field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+ field public static final deprecated java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
@@ -24621,6 +24618,7 @@ package android.media.tv {
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+ field public static final java.lang.String COLUMN_LOCKED = "locked";
field public static final java.lang.String COLUMN_NETWORK_AFFILIATION = "network_affiliation";
field public static final java.lang.String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id";
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
diff --git a/api/system-current.txt b/api/system-current.txt
index 2cc0186e2e32..cead7b7f2be7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4441,6 +4441,14 @@ package android.app {
field public java.lang.String serviceDetails;
}
+ public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AuthenticationRequiredException> CREATOR;
+ }
+
public final class AutomaticZenRule implements android.os.Parcelable {
ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
@@ -5900,17 +5908,6 @@ package android.app {
field public static final int STYLE_SPINNER = 0; // 0x0
}
- public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
- ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction);
- method public int describeContents();
- method public android.app.RemoteAction getUserAction();
- method public java.lang.CharSequence getUserMessage();
- method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context, java.lang.String);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
- }
-
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.RemoteAction clone();
@@ -6654,7 +6651,7 @@ package android.app.admin {
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS";
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
- field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+ field public static final deprecated java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
diff --git a/api/test-current.txt b/api/test-current.txt
index bafee4a3ecae..8bae27c1b593 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4307,6 +4307,14 @@ package android.app {
field public java.lang.String serviceDetails;
}
+ public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AuthenticationRequiredException> CREATOR;
+ }
+
public final class AutomaticZenRule implements android.os.Parcelable {
ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
@@ -5719,17 +5727,6 @@ package android.app {
field public static final int STYLE_SPINNER = 0; // 0x0
}
- public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
- ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction);
- method public int describeContents();
- method public android.app.RemoteAction getUserAction();
- method public java.lang.CharSequence getUserMessage();
- method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context, java.lang.String);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
- }
-
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.RemoteAction clone();
@@ -6452,7 +6449,7 @@ package android.app.admin {
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS";
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
- field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+ field public static final deprecated java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
@@ -24732,6 +24729,7 @@ package android.media.tv {
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+ field public static final java.lang.String COLUMN_LOCKED = "locked";
field public static final java.lang.String COLUMN_NETWORK_AFFILIATION = "network_affiliation";
field public static final java.lang.String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id";
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
diff --git a/core/java/android/app/AuthenticationRequiredException.java b/core/java/android/app/AuthenticationRequiredException.java
new file mode 100644
index 000000000000..89609794a615
--- /dev/null
+++ b/core/java/android/app/AuthenticationRequiredException.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Specialization of {@link SecurityException} that is thrown when authentication is needed from the
+ * end user before viewing the content.
+ * <p>
+ * This exception is only appropriate where there is a concrete action the user can take to
+ * authorize and make forward progress, such as confirming or entering authentication credentials,
+ * or granting access via other means.
+ * <p class="note">
+ * Note: legacy code that receives this exception may treat it as a general
+ * {@link SecurityException}, and thus there is no guarantee that the action contained will be
+ * invoked by the user.
+ * </p>
+ */
+public final class AuthenticationRequiredException extends SecurityException implements Parcelable {
+ private static final String TAG = "AuthenticationRequiredException";
+
+ private final PendingIntent mUserAction;
+
+ /** {@hide} */
+ public AuthenticationRequiredException(Parcel in) {
+ this(new SecurityException(in.readString()), PendingIntent.CREATOR.createFromParcel(in));
+ }
+
+ /**
+ * Create an instance ready to be thrown.
+ *
+ * @param cause original cause with details designed for engineering
+ * audiences.
+ * @param userAction primary action that will initiate the recovery. This
+ * must launch an activity that is expected to set
+ * {@link Activity#setResult(int)} before finishing to
+ * communicate the final status of the recovery. For example,
+ * apps that observe {@link Activity#RESULT_OK} may choose to
+ * immediately retry their operation. If this exception was
+ * thrown from a {@link ContentProvider}, you should also send
+ * any relevant {@link ContentResolver#notifyChange} events to
+ * trigger reloading of data.
+ */
+ public AuthenticationRequiredException(Throwable cause, PendingIntent userAction) {
+ super(cause.getMessage());
+ mUserAction = Preconditions.checkNotNull(userAction);
+ }
+
+ /**
+ * Return primary action that will initiate the authorization.
+ */
+ public PendingIntent getUserAction() {
+ return mUserAction;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(getMessage());
+ mUserAction.writeToParcel(dest, flags);
+ }
+
+ public static final Creator<AuthenticationRequiredException> CREATOR =
+ new Creator<AuthenticationRequiredException>() {
+ @Override
+ public AuthenticationRequiredException createFromParcel(Parcel source) {
+ return new AuthenticationRequiredException(source);
+ }
+
+ @Override
+ public AuthenticationRequiredException[] newArray(int size) {
+ return new AuthenticationRequiredException[size];
+ }
+ };
+}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index eff77b56068e..44fefd38048c 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -1670,8 +1670,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
if (!mCheckedForLoaderManager) {
mCheckedForLoaderManager = true;
mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
- }
- if (mLoaderManager != null) {
+ } else if (mLoaderManager != null) {
mLoaderManager.doStart();
}
}
diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java
index 8612f186ade4..a503a46a29dc 100644
--- a/core/java/android/app/RecoverableSecurityException.java
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -45,7 +45,8 @@ import com.android.internal.util.Preconditions;
* Note: legacy code that receives this exception may treat it as a general
* {@link SecurityException}, and thus there is no guarantee that the messages
* contained will be shown to the end user.
- * </p>
+ *
+ * @hide
*/
public final class RecoverableSecurityException extends SecurityException implements Parcelable {
private static final String TAG = "RecoverableSecurityException";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3be4dd85262f..391885dad45a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -535,18 +535,10 @@ public class DevicePolicyManager {
= "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
/**
- * A String extra that, holds the email address of the account which a managed profile is
- * created for. Used with {@link #ACTION_PROVISION_MANAGED_PROFILE} and
- * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}.
- *
- * <p> This extra is part of the {@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}.
- *
- * <p> If the {@link #ACTION_PROVISION_MANAGED_PROFILE} intent that starts managed provisioning
- * contains this extra, it is forwarded in the
- * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} intent to the mobile
- * device management application that was set as the profile owner during provisioning.
- * It is usually used to avoid that the user has to enter their email address twice.
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}, never used while provisioning the
+ * device.
*/
+ @Deprecated
public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS
= "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4de64c41e913..59b022ded9bf 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -628,4 +628,6 @@ interface IPackageManager {
ParceledListSlice getSharedLibraries(int flags, int userId);
boolean canRequestPackageInstalls(String packageName, int userId);
+
+ void deletePreloadsFileCache();
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2d073ab2b168..fb69986686bd 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -184,7 +184,7 @@ public class PackageParser {
private static final String TAG_RESTRICT_UPDATE = "restrict-update";
private static final String TAG_USES_SPLIT = "uses-split";
- // STOPSHIP remove the ability to expose components via meta-data
+ // [b/36551762] STOPSHIP remove the ability to expose components via meta-data
// Temporary workaround; allow meta-data to expose components to instant apps
private static final String META_DATA_INSTANT_APPS = "instantapps.clients.allowed";
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 8c13cc893cc7..0218cb5f3058 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -771,15 +771,15 @@ public final class Sensor {
3, // SENSOR_TYPE_GEOMAGNETIC_FIELD
3, // SENSOR_TYPE_ORIENTATION
3, // SENSOR_TYPE_GYROSCOPE
- 3, // SENSOR_TYPE_LIGHT
- 3, // SENSOR_TYPE_PRESSURE
- 3, // SENSOR_TYPE_TEMPERATURE
- 3, // SENSOR_TYPE_PROXIMITY
+ 1, // SENSOR_TYPE_LIGHT
+ 1, // SENSOR_TYPE_PRESSURE
+ 1, // SENSOR_TYPE_TEMPERATURE
+ 1, // SENSOR_TYPE_PROXIMITY
3, // SENSOR_TYPE_GRAVITY
3, // SENSOR_TYPE_LINEAR_ACCELERATION
5, // SENSOR_TYPE_ROTATION_VECTOR
- 3, // SENSOR_TYPE_RELATIVE_HUMIDITY
- 3, // SENSOR_TYPE_AMBIENT_TEMPERATURE
+ 1, // SENSOR_TYPE_RELATIVE_HUMIDITY
+ 1, // SENSOR_TYPE_AMBIENT_TEMPERATURE
6, // SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED
4, // SENSOR_TYPE_GAME_ROTATION_VECTOR
6, // SENSOR_TYPE_GYROSCOPE_UNCALIBRATED
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 56d4ff79eb04..9e56e01c24c4 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -1444,14 +1444,15 @@ public final class DocumentsContract {
* <p>Providers are required to show confirmation UI for all new permissions granted
* for the linked document.
*
- * <p>If list of recipients is known, then it can be passed in options as
- * {@link Intent#EXTRA_EMAIL} as either a string or list of strings. Note, that
+ * <p>If list of recipients is known, then it should be passed in options as
+ * {@link Intent#EXTRA_EMAIL} as a list of email addresses. Note, that
* this is just a hint for the provider, which can ignore the list. In either
* case the provider is required to show a UI for letting the user confirm
* any new permission grants.
*
- * <p>Note, that the entire <code>options</code> bundle is send to the provider.
- * Make sure that you trust the provider before passing any sensitive information.
+ * <p>Note, that the entire <code>options</code> bundle will be sent to the provider
+ * backing the passed <code>uri</code>. Make sure that you trust the provider
+ * before passing any sensitive information.
*
* <p>Since this API may show a UI, it cannot be called from background.
*
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 620d33a5e915..9e68afbb5297 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -38,7 +38,7 @@ import static android.provider.DocumentsContract.isTreeUri;
import android.Manifest;
import android.annotation.CallSuper;
import android.annotation.Nullable;
-import android.app.RecoverableSecurityException;
+import android.app.AuthenticationRequiredException;
import android.content.ClipDescription;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -235,10 +235,6 @@ public abstract class DocumentsProvider extends ContentProvider {
* {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
* {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
* not change once returned.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
*
* @param parentDocumentId the parent directory to create the new document
* under.
@@ -247,6 +243,10 @@ public abstract class DocumentsProvider extends ContentProvider {
* @param displayName the display name of the new document. The provider may
* alter this name to meet any internal constraints, such as
* avoiding conflicting names.
+
+ * @throws AuthenticationRequiredException If authentication is required from the user (such as
+ * login credentials), but it is not guaranteed that the client will handle this
+ * properly.
*/
@SuppressWarnings("unused")
public String createDocument(String parentDocumentId, String mimeType, String displayName)
@@ -262,15 +262,14 @@ public abstract class DocumentsProvider extends ContentProvider {
* URI permission grants will be updated to point at the new document. If
* the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
* rename, return {@code null}.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
*
* @param documentId the document to rename.
* @param displayName the updated display name of the document. The provider
* may alter this name to meet any internal constraints, such as
* avoiding conflicting names.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*/
@SuppressWarnings("unused")
public String renameDocument(String documentId, String displayName)
@@ -286,12 +285,11 @@ public abstract class DocumentsProvider extends ContentProvider {
* call (such as documents inside a directory) the implementor is
* responsible for revoking those permissions using
* {@link #revokeDocumentPermission(String)}.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
*
* @param documentId the document to delete.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*/
@SuppressWarnings("unused")
public void deleteDocument(String documentId) throws FileNotFoundException {
@@ -305,13 +303,12 @@ public abstract class DocumentsProvider extends ContentProvider {
* the same document provider. Upon completion returns the document id of
* the copied document at the target destination. {@code null} must never
* be returned.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
*
* @param sourceDocumentId the document to copy.
* @param targetParentDocumentId the target document to be copied into as a child.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*/
@SuppressWarnings("unused")
public String copyDocument(String sourceDocumentId, String targetParentDocumentId)
@@ -329,15 +326,14 @@ public abstract class DocumentsProvider extends ContentProvider {
*
* <p>It's the responsibility of the provider to revoke grants if the document
* is no longer accessible using <code>sourceDocumentId</code>.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
*
* @param sourceDocumentId the document to move.
* @param sourceParentDocumentId the parent of the document to move.
* @param targetParentDocumentId the target document to be a new parent of the
* source document.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*/
@SuppressWarnings("unused")
public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
@@ -355,11 +351,11 @@ public abstract class DocumentsProvider extends ContentProvider {
* <p>It's the responsibility of the provider to revoke grants if the document is
* removed from the last parent, and effectively the document is deleted.
*
- * <p>{@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
* @param documentId the document to remove.
* @param parentDocumentId the parent of the document to move.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*/
@SuppressWarnings("unused")
public void removeDocument(String documentId, String parentDocumentId)
@@ -377,9 +373,6 @@ public abstract class DocumentsProvider extends ContentProvider {
* <p>This API assumes that document ID has enough info to infer the root.
* Different roots should use different document ID to refer to the same
* document.
- * <p>{@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.perly.
*
*
* @param parentDocumentId the document from which the path starts if not null,
@@ -388,6 +381,9 @@ public abstract class DocumentsProvider extends ContentProvider {
* @return the path of the requested document. If parentDocumentId is null
* returned root ID must not be null. If parentDocumentId is not null
* returned root ID must be null.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*/
public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId)
throws FileNotFoundException {
@@ -397,7 +393,7 @@ public abstract class DocumentsProvider extends ContentProvider {
/**
* Creates an intent sender for a web link, if the document is web linkable.
* <p>
- * {@link RecoverableSecurityException} can be thrown if user does not have
+ * {@link AuthenticationRequiredException} can be thrown if user does not have
* sufficient permission for the linked document. Before any new permissions
* are granted for the linked document, a visible UI must be shown, so the
* user can explicitly confirm whether the permission grants are expected.
@@ -414,6 +410,9 @@ public abstract class DocumentsProvider extends ContentProvider {
*
* @param documentId the document to create a web link intent for.
* @param options additional information, such as list of recipients. Optional.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*
* @see DocumentsContract.Document#FLAG_WEB_LINKABLE
* @see android.app.PendingIntent#getIntentSender
@@ -436,9 +435,6 @@ public abstract class DocumentsProvider extends ContentProvider {
* android.database.ContentObserver, boolean)} with
* {@link DocumentsContract#buildRootsUri(String)} to notify the system.
* <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), or returned as part of
- * Cursor's bundle. It is not guaranteed that the client will handle this properly.
*
* @param projection list of {@link Root} columns to put into the cursor. If
* {@code null} all supported columns should be included.
@@ -452,10 +448,6 @@ public abstract class DocumentsProvider extends ContentProvider {
* sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
* limited to only return the 64 most recently modified documents.
* <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), or returned as part of
- * Cursor's bundle. It is not guaranteed that the client will handle this properly.
- * <p>
* Recent documents do not support change notifications.
*
* @param projection list of {@link Document} columns to put into the
@@ -472,16 +464,14 @@ public abstract class DocumentsProvider extends ContentProvider {
/**
* Return metadata for the single requested document. You should avoid
* making network requests to keep this request fast.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), or returned as part of
- * Cursor's bundle. It is not guaranteed that the client will handle this properly.
- *
*
* @param documentId the document to return.
* @param projection list of {@link Document} columns to put into the
* cursor. If {@code null} all supported columns should be
* included.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*/
public abstract Cursor queryDocument(String documentId, String[] projection)
throws FileNotFoundException;
@@ -509,11 +499,6 @@ public abstract class DocumentsProvider extends ContentProvider {
* you can call {@link ContentResolver#notifyChange(Uri,
* android.database.ContentObserver, boolean)} with that Uri to send change
* notifications.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), or returned as part of
- * Cursor's bundle. It is not guaranteed that the client will handle this properly.
- *
*
* @param parentDocumentId the directory to return children for.
* @param projection list of {@link Document} columns to put into the
@@ -525,6 +510,9 @@ public abstract class DocumentsProvider extends ContentProvider {
* may be unordered. This ordering is a hint that can be used to
* prioritize how data is fetched from the network, but UI may
* always enforce a specific ordering.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
* @see DocumentsContract#EXTRA_LOADING
* @see DocumentsContract#EXTRA_INFO
* @see DocumentsContract#EXTRA_ERROR
@@ -552,10 +540,6 @@ public abstract class DocumentsProvider extends ContentProvider {
* you can call {@link ContentResolver#notifyChange(Uri,
* android.database.ContentObserver, boolean)} with that Uri to send change
* notifications.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), or returned as part of
- * Cursor's bundle. It is not guaranteed that the client will handle this properly.
*
* @param parentDocumentId the directory to return children for.
* @param projection list of {@link Document} columns to put into the
@@ -567,6 +551,9 @@ public abstract class DocumentsProvider extends ContentProvider {
* will be used, which may be unordered. See
* {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
* details.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*
* @see DocumentsContract#EXTRA_LOADING
* @see DocumentsContract#EXTRA_INFO
@@ -609,16 +596,16 @@ public abstract class DocumentsProvider extends ContentProvider {
* String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
* android.database.ContentObserver, boolean)} with that Uri to send change
* notifications.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), or returned as part of
- * Cursor's bundle. It is not guaranteed that the client will handle this properly.
*
* @param rootId the root to search under.
* @param query string to match documents against.
* @param projection list of {@link Document} columns to put into the
* cursor. If {@code null} all supported columns should be
* included.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
+ *
* @see DocumentsContract#EXTRA_LOADING
* @see DocumentsContract#EXTRA_INFO
* @see DocumentsContract#EXTRA_ERROR
@@ -641,9 +628,9 @@ public abstract class DocumentsProvider extends ContentProvider {
* implementation queries {@link #queryDocument(String, String[])}, so
* providers may choose to override this as an optimization.
* <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
*/
public String getDocumentType(String documentId) throws FileNotFoundException {
final Cursor cursor = queryDocument(documentId, null);
@@ -669,15 +656,14 @@ public abstract class DocumentsProvider extends ContentProvider {
* <p>
* If you block while downloading content, you should periodically check
* {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
*
* @param documentId the document to return.
* @param mode the mode to open with, such as 'r', 'w', or 'rw'.
* @param signal used by the caller to signal if the request should be
* cancelled. May be null.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
* @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
* OnCloseListener)
* @see ParcelFileDescriptor#createReliablePipe()
@@ -697,15 +683,14 @@ public abstract class DocumentsProvider extends ContentProvider {
* If you perform expensive operations to download or generate a thumbnail,
* you should periodically check {@link CancellationSignal#isCanceled()} to
* abort abandoned thumbnail requests.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
*
* @param documentId the document to return.
* @param sizeHint hint of the optimal thumbnail dimensions.
* @param signal used by the caller to signal if the request should be
* cancelled. May be null.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
* @see Document#FLAG_SUPPORTS_THUMBNAIL
*/
@SuppressWarnings("unused")
@@ -723,10 +708,6 @@ public abstract class DocumentsProvider extends ContentProvider {
* matching the specified MIME type filter.
* <p>
* Virtual documents must have at least one streamable format.
- * <p>
- * {@link RecoverableSecurityException} can be thrown if more input is required
- * from the user (such as insufficient permission), but it is not guaranteed that
- * the client will handle this properly.
*
* @param documentId the document to return.
* @param mimeTypeFilter the MIME type filter for the requested format. May
@@ -735,6 +716,9 @@ public abstract class DocumentsProvider extends ContentProvider {
* provider.
* @param signal used by the caller to signal if the request should be
* cancelled. May be null.
+ * @throws AuthenticationRequiredException If authentication is required from
+ * the user (such as login credentials), but it is not guaranteed
+ * that the client will handle this properly.
* @see #getDocumentStreamTypes(String, String)
*/
@SuppressWarnings("unused")
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 353dfed343e0..9a2e0bb9a0b8 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -322,6 +322,8 @@ public class StaticLayout extends Layout {
/**
* Enables or disables paragraph justification. The default value is disabled (false).
+ * If the last line is too short for justification, the last line will be displayed with
+ * the alignment set by {@link #setAlignment}.
*
* @param justify true for enabling and false for disabling paragraph justification.
* @return this builder, useful for chaining.
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index d096baf3c943..26b3ae2c42fc 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1620,17 +1620,32 @@ public class PopupWindow {
int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop,
int displayFrameBottom, boolean allowResize) {
final int winOffsetY = screenLocationY - drawingLocationY;
- final int anchorTopInScreen = outParams.y + winOffsetY;
- final int spaceBelow = displayFrameBottom - anchorTopInScreen;
- if (anchorTopInScreen >= 0 && height <= spaceBelow) {
+ final int popupScreenTop = outParams.y + winOffsetY;
+ final int spaceBelow = displayFrameBottom - popupScreenTop;
+ if (popupScreenTop >= 0 && height <= spaceBelow) {
return true;
}
- final int spaceAbove = anchorTopInScreen - anchorHeight - displayFrameTop;
+ final int popupScreenBottom;
+ if (mOverlapAnchor) {
+ // popupScreenTop equals the anchor's top at this point.
+ // When shown above the anchor, an overlapping popup's bottom should be aligned with
+ // the anchor's bottom.
+ popupScreenBottom = popupScreenTop + anchorHeight;
+ } else {
+ // popupScreenTop equals the anchor's bottom at this point.
+ // When shown above the anchor, a non-overlapping popup's bottom is aligned with
+ // the anchor's top.
+ popupScreenBottom = popupScreenTop - anchorHeight;
+ }
+ final int spaceAbove = popupScreenBottom - displayFrameTop;
if (height <= spaceAbove) {
// Move everything up.
if (mOverlapAnchor) {
- yOffset += anchorHeight;
+ // Add one anchorHeight to compensate for the correction made at the start of
+ // findDropDownPosition, and another to account for being aligned to the anchor's
+ // bottom, not top.
+ yOffset += anchorHeight * 2;
}
outParams.y = drawingLocationY - height + yOffset;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f2a7f25decc1..8d0ea08ba593 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3743,7 +3743,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Enables or disables full justification. The default value is false.
+ * Enables or disables full justification. The default value is false. If the last line is too
+ * short for justification, the last line will be displayed with the alignment set by
+ * {@link android.view.View#setTextAlignment}.
*
* @see #getJustify()
*/
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6aa77665db51..9d2141d814b9 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -31,12 +31,15 @@ import android.os.BatteryStats;
import android.os.Build;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.IBatteryPropertiesRegistrar;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.WorkSource;
@@ -3151,6 +3154,7 @@ public class BatteryStatsImpl extends BatteryStats {
boolean unpluggedScreenOff = unplugged && screenOff;
if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) {
updateKernelWakelocksLocked();
+ updateBatteryPropertiesLocked();
if (DEBUG_ENERGY_CPU) {
Slog.d(TAG, "Updating cpu time because screen is now " +
(unpluggedScreenOff ? "off" : "on"));
@@ -3160,6 +3164,16 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ private void updateBatteryPropertiesLocked() {
+ try {
+ IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub.asInterface(
+ ServiceManager.getService("batteryproperties"));
+ registrar.scheduleUpdate();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ }
+
public void addIsolatedUidLocked(int isolatedUid, int appUid) {
mIsolatedUids.put(isolatedUid, appUid);
}
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index a8d683028c13..de5e505af3da 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -621,7 +621,7 @@ static void ToColor_SA8(SkColor dst[], const void* src, int width, SkColorTable*
const uint8_t* s = (const uint8_t*)src;
do {
uint8_t c = *s++;
- *dst++ = SkColorSetARGB(c, c, c, c);
+ *dst++ = SkColorSetARGB(c, 0, 0, 0);
} while (--width != 0);
}
@@ -727,6 +727,50 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
}
+static bool bitmapCopyTo(SkBitmap* dst, SkColorType dstCT, const SkBitmap& src,
+ SkBitmap::Allocator* alloc) {
+ // Skia does not support copying from kAlpha8 to types that are not alpha only.
+ // We will handle this case here.
+ if (kAlpha_8_SkColorType == src.colorType() && kAlpha_8_SkColorType != dstCT) {
+ SkAutoPixmapUnlock srcUnlocker;
+ if (!src.requestLock(&srcUnlocker)) {
+ return false;
+ }
+ SkPixmap srcPixmap = srcUnlocker.pixmap();
+
+ SkImageInfo dstInfo = src.info().makeColorType(dstCT);
+ if (!dst->setInfo(dstInfo)) {
+ return false;
+ }
+ if (!dst->tryAllocPixels(alloc, nullptr)) {
+ return false;
+ }
+
+ switch (dstCT) {
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType: {
+ for (int y = 0; y < src.height(); y++) {
+ const uint8_t* srcRow = srcPixmap.addr8(0, y);
+ uint32_t* dstRow = dst->getAddr32(0, y);
+ ToColor_SA8(dstRow, srcRow, src.width(), nullptr);
+ }
+ return true;
+ }
+ case kRGB_565_SkColorType: {
+ for (int y = 0; y < src.height(); y++) {
+ uint16_t* dstRow = dst->getAddr16(0, y);
+ memset(dstRow, 0, sizeof(uint16_t) * src.width());
+ }
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
+
+ return src.copyTo(dst, dstCT, alloc);
+}
+
static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle,
jint dstConfigHandle, jboolean isMutable) {
SkBitmap src;
@@ -743,7 +787,7 @@ static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle,
SkBitmap result;
HeapAllocator allocator;
- if (!src.copyTo(&result, dstCT, &allocator)) {
+ if (!bitmapCopyTo(&result, dstCT, src, &allocator)) {
return NULL;
}
auto bitmap = allocator.getStorageObjAndReset();
@@ -754,7 +798,7 @@ static Bitmap* Bitmap_copyAshmemImpl(JNIEnv* env, SkBitmap& src, SkColorType& ds
SkBitmap result;
AshmemPixelAllocator allocator(env);
- if (!src.copyTo(&result, dstCT, &allocator)) {
+ if (!bitmapCopyTo(&result, dstCT, src, &allocator)) {
return NULL;
}
auto bitmap = allocator.getStorageObjAndReset();
diff --git a/core/res/res/drawable-hdpi/stat_notify_mmcc_indication_icn.png b/core/res/res/drawable-hdpi/stat_notify_mmcc_indication_icn.png
new file mode 100644
index 000000000000..d7049517dc2a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_notify_mmcc_indication_icn.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/stat_notify_mmcc_indication_icn.png b/core/res/res/drawable-xhdpi/stat_notify_mmcc_indication_icn.png
new file mode 100644
index 000000000000..5dfc89ec1856
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/stat_notify_mmcc_indication_icn.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/stat_notify_mmcc_indication_icn.png b/core/res/res/drawable-xxhdpi/stat_notify_mmcc_indication_icn.png
new file mode 100644
index 000000000000..a648b0bffcbf
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/stat_notify_mmcc_indication_icn.png
Binary files differ
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d4db258bd13d..d1c590069c94 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4601,4 +4601,10 @@
<!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for others -->
<string name="etws_primary_default_message_others"></string>
+
+ <!-- Title of notification when UE fails to register network with MM reject cause code. -->
+ <string name="mmcc_authentication_reject">SIM not allowed</string>
+ <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned</string>
+ <string name="mmcc_illegal_ms">SIM not allowed</string>
+ <string name="mmcc_illegal_me">Phone not allowed</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4ef3922aa1fe..11cde7a573ed 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1326,6 +1326,8 @@
<java-symbol type="drawable" name="ic_sim_card_multi_24px_clr" />
<java-symbol type="drawable" name="ic_sim_card_multi_48px_clr" />
+ <java-symbol type="drawable" name="stat_notify_mmcc_indication_icn" />
+
<java-symbol type="drawable" name="ic_account_circle" />
<java-symbol type="color" name="user_icon_1" />
<java-symbol type="color" name="user_icon_2" />
@@ -1911,6 +1913,10 @@
<java-symbol type="string" name="low_internal_storage_view_text" />
<java-symbol type="string" name="low_internal_storage_view_text_no_boot" />
<java-symbol type="string" name="low_internal_storage_view_title" />
+ <java-symbol type="string" name="mmcc_authentication_reject" />
+ <java-symbol type="string" name="mmcc_imsi_unknown_in_hlr" />
+ <java-symbol type="string" name="mmcc_illegal_ms" />
+ <java-symbol type="string" name="mmcc_illegal_me" />
<java-symbol type="string" name="notification_listener_binding_label" />
<java-symbol type="string" name="vr_listener_binding_label" />
<java-symbol type="string" name="condition_provider_service_binding_label" />
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 042bac6441a9..228d9500180a 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -358,10 +358,10 @@ public class Typeface {
FileChannel.MapMode.READ_ONLY, 0, fontSize);
int style = result.getStyle();
int weight = (style & BOLD) != 0 ? 700 : 400;
- // TODO: this method should be
- // create(fd, ttcIndex, fontVariationSettings, style).
+ final ArrayList<FontConfig.Axis> axes = FontListParser.parseFontVariationSettings(
+ result.getFontVariationSettings());
if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(),
- null, weight,
+ axes.toArray(new FontConfig.Axis[axes.size()]), weight,
(style & ITALIC) == 0 ? Builder.NORMAL : Builder.ITALIC)) {
Log.e(TAG, "Error creating font " + request.getQuery());
callback.onTypefaceRequestFailed(
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 9981668b7cca..5c64566f7987 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -196,7 +196,8 @@ public final class KeyChain {
*
* @deprecated Use {@link #ACTION_KEYCHAIN_CHANGED}, {@link #ACTION_TRUST_STORE_CHANGED} or
* {@link #ACTION_KEY_ACCESS_CHANGED}. Apps that target a version higher than
- * {@link Build.VERSION_CODES#N_MR1} will not receive this broadcast.
+ * {@link Build.VERSION_CODES#N_MR1} will only receive this broadcast if they register for it
+ * at runtime.
*/
public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED";
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index cbea501e3df3..652954bd213e 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -389,10 +389,10 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
return new ProjectionTestCanvas(mDrawCounter);
}
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
- return sk_sp<SkSurface>();
+ return nullptr;
}
- sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) override {
- return sk_sp<SkImage>();
+ sk_sp<SkImage> onNewImageSnapshot() override {
+ return nullptr;
}
void onCopyOnWrite(ContentChangeMode) override {}
int* mDrawCounter;
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index c28aa5ec8711..6808b57c2642 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1971,10 +1971,11 @@ public final class TvContract {
* channel is not locked thus the user is not prompted to enter passcode If not specified,
* this value is set to 0 (not locked) by default.
*
+ * <p>This column can only be set by applications having proper system permission to
+ * modify parental control settings. For other applications, this is a read-only column.
+
* <p>Type: INTEGER (boolean)
- * @hide
*/
- @SystemApi
public static final String COLUMN_LOCKED = "locked";
/**
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 79ea6fe513c3..d6d01d8abac4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1036,12 +1036,48 @@
<!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=35]-->
<string name="monitoring_subtitle_network_logging">Network Logging</string>
+ <!-- Monitoring dialog subtitle for the section describing certificate authorities [CHAR LIMIT=35]-->
+ <string name="monitoring_subtitle_ca_certificate">CA certificates</string>
+
<!-- Monitoring dialog disable vpn button [CHAR LIMIT=30] -->
<string name="disable_vpn">Disable VPN</string>
<!-- Monitoring dialog disconnect vpn button [CHAR LIMIT=30] -->
<string name="disconnect_vpn">Disconnect VPN</string>
+ <!-- Monitoring dialog label for button opening a page with more information on the admin's abilities [CHAR LIMIT=30] -->
+ <string name="monitoring_button_view_policies">View Policies</string>
+
+ <!-- Monitoring dialog: Description of the device owner by name. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_named_management">Your device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g>.\n\nYour admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device's location information.\n\nFor more information, contact your admin." </string>
+
+ <!-- Monitoring dialog: Description of a device owner. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_management">Your device is managed by your organization.\n\nYour admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device's location information.\n\nFor more information, contact your admin." </string>
+
+ <!-- Monitoring dialog: Description of a CA Certificate. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_management_ca_certificate">Your organization installed a certificate authority on this device. Your secure network traffic may be monitored or modified.</string>
+
+ <!-- Monitoring dialog: Description of a CA Certificate in the work profile. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_managed_profile_ca_certificate">Your organization installed a certificate authority in your work profile. Your secure network traffic may be monitored or modified.</string>
+
+ <!-- Monitoring dialog: Description of a CA Certificate. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_ca_certificate">A certificate authority is installed on this device. Your secure network traffic may be monitored or modified.</string>
+
+ <!-- Monitoring dialog: Description of Network Logging. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitores traffic on your device.</string>
+
+ <!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_named_vpn">You're connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
+
+ <!-- Monitoring dialog: Description of two active VPNs. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_two_named_vpns">You're connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g> and <xliff:g id="vpn_app" example="Bar VPN App">%2$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
+
+ <!-- Monitoring dialog: Description of an active VPN in the work profile. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_managed_profile_named_vpn">Your work profile is connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
+
+ <!-- Monitoring dialog: Description of an active VPN in the personal profile (as opposed to the work profile). [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_personal_profile_named_vpn">Your personal profile is connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
+
<!-- Monitoring dialog: Header indicating that the device is managed by a Device Owner app [CHAR LIMIT=80] -->
<string name="monitoring_description_do_header_generic">Your device is managed by <xliff:g id="device_owner_app" example="Google Mobile Management">%1$s</xliff:g>.</string>
@@ -1069,7 +1105,6 @@
<!-- Monitoring dialog: Network logging text [CHAR LIMIT=400] -->
<string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information, contact your admin.</string>
-
<!-- Monitoring dialog VPN text [CHAR LIMIT=400] -->
<string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps, and websites.</string>
@@ -1347,11 +1382,15 @@
of notifications. Replaces the channel name and only appears when there is more than one channel. -->
<string name="notification_num_channels"> <xliff:g id="number">%d</xliff:g> notification categories</string>
+ <!-- Notification: Control panel: Label that shows when an app has not upgraded to use channels.
+ Hints that the user's only option is to block all of the app's notifications. -->
+ <string name="notification_default_channel_desc">This app doesn\'t have notification categories</string>
+
<!-- Notification: Control panel: Label that shows how many channels this application has
- defined, describing the current notification channel as "1 out of n categories from this app". -->
+ defined, describing the current notification channel as "1 out of n notification categories from this app". -->
<plurals name="notification_num_channels_desc">
- <item quantity="one">1 out of <xliff:g id="number">%d</xliff:g> category from this app</item>
- <item quantity="other">1 out of <xliff:g id="number">%d</xliff:g> categories from this app</item>
+ <item quantity="one">1 out of <xliff:g id="number">%d</xliff:g> notification category from this app</item>
+ <item quantity="other">1 out of <xliff:g id="number">%d</xliff:g> notification categories from this app</item>
</plurals>
<!-- Notification: Control panel: For bundles of notifications, this label that lists the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 043490cc5823..2d0fe6f63219 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -20,6 +20,7 @@ import android.graphics.drawable.RippleDrawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
@@ -110,7 +111,6 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
public void init(OnClickListener click, OnClickListener secondaryClick,
OnLongClickListener longClick) {
- setClickable(true);
setOnClickListener(click);
setOnLongClickListener(longClick);
}
@@ -148,6 +148,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
}
protected void handleStateChanged(QSTile.State state) {
+ setClickable(state.state != Tile.STATE_UNAVAILABLE);
mIcon.setIcon(state);
setContentDescription(state.contentDescription);
mAccessibilityClass = state.expandedAccessibilityClassName;
@@ -157,6 +158,12 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
}
@Override
+ public void setClickable(boolean clickable) {
+ super.setClickable(clickable);
+ setBackground(clickable ? mRipple : null);
+ }
+
+ @Override
public int getDetailY() {
return getTop() + getHeight() / 2;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index ca703367ac23..4351b2ca6dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -137,7 +137,8 @@ public class CellularTile extends QSTileImpl<SignalState> {
state.expandedAccessibilityClassName = Switch.class.getName();
state.value = mDataController.isMobileDataSupported()
&& mDataController.isMobileDataEnabled();
- state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.state = cb.airplaneModeEnabled ? Tile.STATE_UNAVAILABLE
+ : state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index a9043e4c83bb..54921a7108c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -99,11 +99,14 @@ public class NotificationInfo extends LinearLayout implements GutsContent {
mINotificationManager = iNotificationManager;
mPkg = pkg;
mNotificationChannels = notificationChannels;
+ boolean isSingleDefaultChannel = false;
if (mNotificationChannels.isEmpty()) {
throw new IllegalArgumentException("bindNotification requires at least one channel");
} else if (mNotificationChannels.size() == 1) {
mSingleNotificationChannel = mNotificationChannels.get(0);
mStartingUserImportance = mSingleNotificationChannel.getImportance();
+ isSingleDefaultChannel = mSingleNotificationChannel.getId()
+ .equals(NotificationChannel.DEFAULT_CHANNEL_ID);
} else {
mSingleNotificationChannel = null;
}
@@ -135,24 +138,30 @@ public class NotificationInfo extends LinearLayout implements GutsContent {
String channelsDescText;
mNumChannelsView = (TextView) (findViewById(R.id.num_channels_desc));
- switch (mNotificationChannels.size()) {
- case 1:
- channelsDescText = String.format(mContext.getResources().getQuantityString(
- R.plurals.notification_num_channels_desc, numChannels), numChannels);
- break;
- case 2:
- channelsDescText = mContext.getString(R.string.notification_channels_list_desc_2,
- mNotificationChannels.get(0).getName(),
- mNotificationChannels.get(1).getName());
- break;
- default:
- final int numOthers = mNotificationChannels.size() - 2;
- channelsDescText = String.format(
- mContext.getResources().getQuantityString(
- R.plurals.notification_channels_list_desc_2_and_others, numOthers),
- mNotificationChannels.get(0).getName(),
- mNotificationChannels.get(1).getName(),
- numOthers);
+ if (isSingleDefaultChannel) {
+ channelsDescText = mContext.getString(R.string.notification_default_channel_desc);
+ } else {
+ switch (mNotificationChannels.size()) {
+ case 1:
+ channelsDescText = String.format(mContext.getResources().getQuantityString(
+ R.plurals.notification_num_channels_desc, numChannels), numChannels);
+ break;
+ case 2:
+ channelsDescText = mContext.getString(
+ R.string.notification_channels_list_desc_2,
+ mNotificationChannels.get(0).getName(),
+ mNotificationChannels.get(1).getName());
+ break;
+ default:
+ final int numOthers = mNotificationChannels.size() - 2;
+ channelsDescText = String.format(
+ mContext.getResources().getQuantityString(
+ R.plurals.notification_channels_list_desc_2_and_others,
+ numOthers),
+ mNotificationChannels.get(0).getName(),
+ mNotificationChannels.get(1).getName(),
+ numOthers);
+ }
}
mNumChannelsView.setText(channelsDescText);
@@ -160,9 +169,8 @@ public class NotificationInfo extends LinearLayout implements GutsContent {
// Multiple channels don't use a channel name for the title.
channelNameText = mContext.getString(R.string.notification_num_channels,
mNotificationChannels.size());
- } else if (mSingleNotificationChannel.getId()
- .equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- // If this is the placeholder channel, don't use our channel-specific text.
+ } else if (isSingleDefaultChannel) {
+ // If this is the default channel, don't use our channel-specific text.
channelNameText = mContext.getString(R.string.notification_header_default_channel);
} else {
channelNameText = mSingleNotificationChannel.getName();
@@ -282,15 +290,9 @@ public class NotificationInfo extends LinearLayout implements GutsContent {
}
private void updateSecondaryText() {
- final boolean defaultChannel = mSingleNotificationChannel != null &&
- mSingleNotificationChannel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID);
final boolean disabled = mSingleNotificationChannel != null &&
getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
- if (defaultChannel) {
- // Don't show any secondary text if this is from the default channel.
- mChannelDisabledView.setVisibility(View.GONE);
- mNumChannelsView.setVisibility(View.GONE);
- } else if (disabled) {
+ if (disabled) {
mChannelDisabledView.setVisibility(View.VISIBLE);
mNumChannelsView.setVisibility(View.GONE);
} else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 8aca546b2730..5632b719df23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -260,12 +260,14 @@ public class NotificationInfoTest extends SysuiTestCase {
}
@Test
- public void testBindNotification_NumChannelsTextHiddenWhenDefaultChannel() throws Exception {
+ public void testBindNotification_NumChannelsTextUniqueWhenDefaultChannel() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
- assertTrue(numChannelsView.getVisibility() != View.VISIBLE);
+ assertEquals(View.VISIBLE, numChannelsView.getVisibility());
+ assertEquals(mContext.getString(R.string.notification_default_channel_desc),
+ numChannelsView.getText());
}
@Test
@@ -390,13 +392,14 @@ public class NotificationInfoTest extends SysuiTestCase {
@Test
@UiThreadTest
- public void testBindNotification_ChannelDisabledTextHiddenWhenDefaultChannel()
+ public void testBindNotification_ChannelDisabledTextShowsForDefaultChannel()
throws Exception {
+ mDefaultNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null);
final TextView channelDisabledView =
(TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
- assertTrue(channelDisabledView.getVisibility() != View.VISIBLE);
+ assertEquals(View.VISIBLE, channelDisabledView.getVisibility());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 8808988406a9..f516d74f4062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -20,7 +20,9 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
+import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -31,10 +33,13 @@ import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
public class BluetoothControllerImplTest extends SysuiTestCase {
private LocalBluetoothManager mMockBluetoothManager;
@@ -47,7 +52,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
@Before
public void setup() throws Exception {
- mTestableLooper = new TestableLooper();
+ mTestableLooper = TestableLooper.get(this);
mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
mDevices = new ArrayList<>();
mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 01fc8ecd78bd..8e3e3eaa1a60 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5393,7 +5393,7 @@ public class AccountManagerService
@NonNull
private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid,
- String callingPackage, boolean includeManagedNotVisible) {
+ @Nullable String callingPackage, boolean includeManagedNotVisible) {
String visibilityFilterPackage = callingPackage;
if (visibilityFilterPackage == null) {
visibilityFilterPackage = getPackageNameForUid(callingUid);
@@ -5429,8 +5429,7 @@ public class AccountManagerService
}
UserInfo user = getUserManager().getUserInfo(userAccounts.userId);
if (user != null && user.isRestricted()) {
- String[] packages =
- mPackageManager.getPackagesForUid(callingUid);
+ String[] packages = mPackageManager.getPackagesForUid(callingUid);
if (packages == null) {
packages = new String[] {};
}
@@ -5501,9 +5500,6 @@ public class AccountManagerService
@NonNull
protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
int callingUid, @Nullable String callingPackage, boolean includeManagedNotVisible) {
- if (callingPackage == null) {
- callingPackage = getPackageNameForUid(callingUid);
- }
if (accountType != null) {
final Account[] accounts = userAccounts.accountCache.get(accountType);
if (accounts == null) {
diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
index 34f245f19d12..1b8b89c8bc37 100644
--- a/services/core/java/com/android/server/fingerprint/EnumerateClient.java
+++ b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
@@ -58,7 +58,7 @@ public abstract class EnumerateClient extends ClientMonitor {
public int stop(boolean initiatedByClient) {
IBiometricsFingerprint daemon = getFingerprintDaemon();
if (daemon == null) {
- Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
+ Slog.w(TAG, "stopEnumeration: no fingerprint HAL!");
return ERROR_ESRCH;
}
try {
@@ -102,12 +102,12 @@ public abstract class EnumerateClient extends ClientMonitor {
@Override
public boolean onEnrollResult(int fingerId, int groupId, int rem) {
if (DEBUG) Slog.w(TAG, "onEnrollResult() called for enumerate!");
- return true; // Invalid for Remove
+ return true; // Invalid for Enumerate.
}
@Override
public boolean onRemoved(int fingerId, int groupId, int remaining) {
if (DEBUG) Slog.w(TAG, "onRemoved() called for enumerate!");
- return true; // Invalid for Authenticate
+ return true; // Invalid for Enumerate.
}
}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 7d97ce41e8f3..b6e7932bdec4 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -85,6 +85,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.LinkedList;
/**
* A service to manage multiple clients that want to access the fingerprint HAL API.
@@ -134,6 +135,20 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
private ClientMonitor mPendingClient;
private PerformanceStats mPerformanceStats;
+
+ private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
+ private LinkedList<Integer> mEnumeratingUserIds = new LinkedList<>();
+ private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw finterprints
+
+ private class UserFingerprint {
+ Fingerprint f;
+ int userId;
+ public UserFingerprint(Fingerprint f, int userId) {
+ this.f = f;
+ this.userId = userId;
+ }
+ }
+
// Normal fingerprint authentications are tracked by mPerformanceMap.
private HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
@@ -257,10 +272,12 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
// This operation can be expensive, so keep track of the elapsed time. Might need to move to
// background if it takes too long.
long t = System.currentTimeMillis();
-
mAuthenticatorIds.clear();
+ mEnumeratingUserIds.clear();
+ mUnknownFingerprints.clear();
for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
int userId = getUserOrWorkProfileId(null, user.id);
+ mEnumeratingUserIds.add(userId);
if (!mAuthenticatorIds.containsKey(userId)) {
updateActiveGroup(userId, null);
}
@@ -270,12 +287,70 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
if (t > 1000) {
Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
}
+
+ if (!mEnumeratingUserIds.isEmpty()) {
+ enumerateNextUser();
+ }
+ }
+
+ private void enumerateNextUser() {
+ int nextUser = mEnumeratingUserIds.getFirst();
+ updateActiveGroup(nextUser, null);
+ boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
+
+ if (DEBUG) Slog.v(TAG, "Enumerating user id " + nextUser + " of "
+ + mEnumeratingUserIds.size() + " remaining users");
+
+ startEnumerate(mToken, nextUser, null, restricted, true /* internal */);
+ }
+
+ // Remove unknown fingerprints from hardware
+ private void cleanupUnknownFingerprints() {
+ if (!mUnknownFingerprints.isEmpty()) {
+ Slog.w(TAG, "unknown fingerprint size: " + mUnknownFingerprints.size());
+ UserFingerprint uf = mUnknownFingerprints.get(0);
+ mUnknownFingerprints.remove(uf);
+ boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
+ updateActiveGroup(uf.userId, null);
+ startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null,
+ restricted, true /* internal */);
+ }
}
protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId + ", gid="
- + groupId + "rem=" + remaining);
- // TODO: coordinate names with framework
+ if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId
+ + ", gid=" + groupId
+ + ", dev=" + deviceId
+ + ", rem=" + remaining);
+
+ ClientMonitor client = mCurrentClient;
+
+ if (client != null) {
+ client.onEnumerationResult(fingerId, groupId, remaining);
+ }
+
+ // All fingerprints in hardware for this user were enumerated
+ if (remaining == 0) {
+ mEnumeratingUserIds.poll();
+
+ if (client instanceof InternalEnumerateClient) {
+ List<Fingerprint> enrolled = ((InternalEnumerateClient) client).getEnumeratedList();
+ Slog.w(TAG, "Added " + enrolled.size() + " enumerated fingerprints for deletion");
+ for (Fingerprint f : enrolled) {
+ mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
+ }
+ }
+
+ removeClient(client);
+
+ if (!mEnumeratingUserIds.isEmpty()) {
+ enumerateNextUser();
+ } else if (client instanceof InternalEnumerateClient) {
+ if (DEBUG) Slog.v(TAG, "Finished enumerating all users");
+ // This will start a chain of InternalRemovalClients
+ cleanupUnknownFingerprints();
+ }
+ }
}
protected void handleError(long deviceId, int error, int vendorCode) {
@@ -304,10 +379,18 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
}
protected void handleRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+ if (DEBUG) Slog.w(TAG, "Removed: fid=" + fingerId
+ + ", gid=" + groupId
+ + ", dev=" + deviceId
+ + ", rem=" + remaining);
+
ClientMonitor client = mCurrentClient;
if (client != null && client.onRemoved(fingerId, groupId, remaining)) {
removeClient(client);
}
+ if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
+ cleanupUnknownFingerprints();
+ }
}
protected void handleAuthenticated(long deviceId, int fingerId, int groupId,
@@ -434,7 +517,15 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
ClientMonitor currentClient = mCurrentClient;
if (currentClient != null) {
if (DEBUG) Slog.v(TAG, "request stop current client " + currentClient.getOwnerString());
- currentClient.stop(initiatedByClient);
+ if (currentClient instanceof InternalEnumerateClient ||
+ currentClient instanceof InternalRemovalClient) {
+ // This condition means we're currently running internal diagnostics to
+ // remove extra fingerprints in the hardware and/or the software
+ // TODO: design an escape hatch in case client never finishes
+ }
+ else {
+ currentClient.stop(initiatedByClient);
+ }
mPendingClient = newClient;
mHandler.removeCallbacks(mResetClientState);
mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
@@ -451,47 +542,86 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
}
void startRemove(IBinder token, int fingerId, int groupId, int userId,
- IFingerprintServiceReceiver receiver, boolean restricted) {
+ IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) {
IBiometricsFingerprint daemon = getFingerprintDaemon();
if (daemon == null) {
Slog.w(TAG, "startRemove: no fingerprint HAL!");
return;
}
- RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
- receiver, fingerId, groupId, userId, restricted, token.toString()) {
- @Override
- public void notifyUserActivity() {
- FingerprintService.this.userActivity();
- }
- @Override
- public IBiometricsFingerprint getFingerprintDaemon() {
- return FingerprintService.this.getFingerprintDaemon();
- }
- };
- startClient(client, true);
+ if (internal) {
+ Context context = getContext();
+ InternalRemovalClient client = new InternalRemovalClient(context, mHalDeviceId,
+ token, receiver, fingerId, groupId, userId, restricted,
+ context.getOpPackageName()) {
+ @Override
+ public void notifyUserActivity() {
+
+ }
+ @Override
+ public IBiometricsFingerprint getFingerprintDaemon() {
+ return FingerprintService.this.getFingerprintDaemon();
+ }
+ };
+ startClient(client, true);
+ }
+ else {
+ RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
+ receiver, fingerId, groupId, userId, restricted, token.toString()) {
+ @Override
+ public void notifyUserActivity() {
+ FingerprintService.this.userActivity();
+ }
+
+ @Override
+ public IBiometricsFingerprint getFingerprintDaemon() {
+ return FingerprintService.this.getFingerprintDaemon();
+ }
+ };
+ startClient(client, true);
+ }
}
void startEnumerate(IBinder token, int userId,
- IFingerprintServiceReceiver receiver, boolean restricted) {
+ IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) {
IBiometricsFingerprint daemon = getFingerprintDaemon();
if (daemon == null) {
Slog.w(TAG, "startEnumerate: no fingerprint HAL!");
return;
}
- EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token,
- receiver, userId, userId, restricted, token.toString()) {
- @Override
- public void notifyUserActivity() {
- FingerprintService.this.userActivity();
- }
+ if (internal) {
+ List<Fingerprint> enrolledList = getEnrolledFingerprints(userId);
+ Context context = getContext();
+ InternalEnumerateClient client = new InternalEnumerateClient(context, mHalDeviceId,
+ token, receiver, userId, userId, restricted, context.getOpPackageName(),
+ enrolledList) {
+ @Override
+ public void notifyUserActivity() {
- @Override
- public IBiometricsFingerprint getFingerprintDaemon() {
- return FingerprintService.this.getFingerprintDaemon();
- }
- };
- startClient(client, true);
+ }
+
+ @Override
+ public IBiometricsFingerprint getFingerprintDaemon() {
+ return FingerprintService.this.getFingerprintDaemon();
+ }
+ };
+ startClient(client, true);
+ }
+ else {
+ EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token,
+ receiver, userId, userId, restricted, token.toString()) {
+ @Override
+ public void notifyUserActivity() {
+ FingerprintService.this.userActivity();
+ }
+
+ @Override
+ public IBiometricsFingerprint getFingerprintDaemon() {
+ return FingerprintService.this.getFingerprintDaemon();
+ }
+ };
+ startClient(client, true);
+ }
}
public List<Fingerprint> getEnrolledFingerprints(int userId) {
@@ -978,12 +1108,14 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
mHandler.post(new Runnable() {
@Override
public void run() {
- startRemove(token, fingerId, groupId, userId, receiver, restricted);
+ startRemove(token, fingerId, groupId, userId, receiver,
+ restricted, false /* internal */);
}
});
}
+ @Override // Binder call
public void enumerate(final IBinder token, final int userId,
final IFingerprintServiceReceiver receiver) {
checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
@@ -991,7 +1123,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
mHandler.post(new Runnable() {
@Override
public void run() {
- startEnumerate(token, userId, receiver, restricted);
+ startEnumerate(token, userId, receiver, restricted, false /* internal */);
}
});
diff --git a/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
new file mode 100644
index 000000000000..f4d2596c85bf
--- /dev/null
+++ b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.fingerprint;
+
+import android.content.Context;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.IBinder;
+import android.util.Slog;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An internal class to help clean up unknown fingerprints in the hardware and software
+ */
+public abstract class InternalEnumerateClient extends EnumerateClient {
+
+ private List<Fingerprint> mEnrolledList;
+ private List<Fingerprint> mEnumeratedList = new ArrayList<>(); // list of fp to delete
+
+ public InternalEnumerateClient(Context context, long halDeviceId, IBinder token,
+ IFingerprintServiceReceiver receiver, int groupId, int userId,
+ boolean restricted, String owner, List<Fingerprint> enrolledList) {
+
+ super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
+ mEnrolledList = enrolledList;
+ }
+
+ private void handleEnumeratedFingerprint(int fingerId, int groupId, int remaining) {
+
+ boolean matched = false;
+ for (int i=0; i<mEnrolledList.size(); i++) {
+ if (mEnrolledList.get(i).getFingerId() == fingerId) {
+ mEnrolledList.remove(i);
+ matched = true;
+ Slog.e(TAG, "Matched fingerprint fid=" + fingerId);
+ break;
+ }
+ }
+
+ // fingerId 0 means no fingerprints are in hardware
+ if (!matched && fingerId != 0) {
+ Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
+ mEnumeratedList.add(fingerprint);
+ }
+ }
+
+ private void doFingerprintCleanup() {
+
+ if (mEnrolledList == null) {
+ return;
+ }
+
+ for (Fingerprint f : mEnrolledList) {
+ Slog.e(TAG, "Internal Enumerate: Removing dangling enrolled fingerprint: "
+ + f.getName() + " " + f.getFingerId() + " " + f.getGroupId()
+ + " " + f.getDeviceId());
+
+ FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(),
+ f.getFingerId(), getTargetUserId());
+ }
+ mEnrolledList.clear();
+ }
+
+ public List<Fingerprint> getEnumeratedList() {
+ return mEnumeratedList;
+ }
+
+ @Override
+ public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
+
+ handleEnumeratedFingerprint(fingerId, groupId, remaining);
+ if (remaining == 0) {
+ doFingerprintCleanup();
+ }
+
+ return fingerId == 0; // done when id hits 0
+ }
+
+}
diff --git a/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java b/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java
new file mode 100644
index 000000000000..19f61feac1f4
--- /dev/null
+++ b/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.fingerprint;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import com.android.server.fingerprint.RemovalClient;
+
+public abstract class InternalRemovalClient extends RemovalClient {
+
+ public InternalRemovalClient(Context context, long halDeviceId, IBinder token,
+ IFingerprintServiceReceiver receiver, int fingerId, int groupId, int userId,
+ boolean restricted, String owner) {
+
+ super(context, halDeviceId, token, receiver, fingerId, groupId, userId, restricted, owner);
+
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 11cc52df180d..0774779a4cf2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -583,7 +583,7 @@ abstract public class ManagedServices {
ServiceInfo info = mPm.getServiceInfo(component,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
- if (!mConfig.bindPermission.equals(info.permission)) {
+ if (info == null || !mConfig.bindPermission.equals(info.permission)) {
Slog.w(TAG, "Skipping " + getCaption() + " service " + component
+ ": it does not require the permission " + mConfig.bindPermission);
continue;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3727a5b79440..eb22d5da4395 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1441,11 +1441,6 @@ public class NotificationManagerService extends SystemService {
return ;
}
- if (isCallerInstantApp(pkg)) {
- throw new SecurityException("Instant app " + pkg
- + " is not allowed to create toasts");
- }
-
final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
final boolean isPackageSuspended =
isPackageSuspendedForUser(pkg, Binder.getCallingUid());
@@ -3121,8 +3116,19 @@ public class NotificationManagerService extends SystemService {
+ " - notification=" + notification);
return;
}
- throw new IllegalArgumentException("No Channel found for channelId=" + channelId
- + ", notification=" + notification);
+ final String noChannelStr = "No Channel found for "
+ + "pkg=" + pkg
+ + ", channelId=" + channelId
+ + ", opPkg=" + opPkg
+ + ", callingUid=" + callingUid
+ + ", userId=" + userId
+ + ", incomingUserId=" + incomingUserId
+ + ", notificationUid=" + notificationUid
+ + ", notification=" + notification;
+ // STOPSHIP TODO: should throw instead of logging.
+ // throw new IllegalArgumentException(noChannelStr);
+ Log.e(TAG, noChannelStr);
+ return;
}
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index cb499d8f019a..f5ea74d6606b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2879,24 +2879,17 @@ public class PackageManagerService extends IPackageManager.Stub {
}
mInstallerService = new PackageInstallerService(context, this);
-
final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
if (ephemeralResolverComponent != null) {
if (DEBUG_EPHEMERAL) {
- Slog.i(TAG, "Ephemeral resolver: " + ephemeralResolverComponent);
+ Slog.d(TAG, "Set ephemeral resolver: " + ephemeralResolverComponent);
}
mInstantAppResolverConnection =
new EphemeralResolverConnection(mContext, ephemeralResolverComponent);
} else {
mInstantAppResolverConnection = null;
}
- mInstantAppInstallerComponent = getEphemeralInstallerLPr();
- if (mInstantAppInstallerComponent != null) {
- if (DEBUG_EPHEMERAL) {
- Slog.i(TAG, "Ephemeral installer: " + mInstantAppInstallerComponent);
- }
- setUpInstantAppInstallerActivityLP(mInstantAppInstallerComponent);
- }
+ updateInstantAppInstallerLocked();
// Read and update the usage of dex files.
// Do this at the end of PM init so that all the packages have their
@@ -2936,6 +2929,21 @@ public class PackageManagerService extends IPackageManager.Stub {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ private void updateInstantAppInstallerLocked() {
+ final ComponentName oldInstantAppInstallerComponent = mInstantAppInstallerComponent;
+ final ComponentName newInstantAppInstallerComponent = getEphemeralInstallerLPr();
+ if (newInstantAppInstallerComponent != null
+ && !newInstantAppInstallerComponent.equals(oldInstantAppInstallerComponent)) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Set ephemeral installer: " + newInstantAppInstallerComponent);
+ }
+ setUpInstantAppInstallerActivityLP(newInstantAppInstallerComponent);
+ } else if (DEBUG_EPHEMERAL && newInstantAppInstallerComponent == null) {
+ Slog.d(TAG, "Unset ephemeral installer; none available");
+ }
+ mInstantAppInstallerComponent = newInstantAppInstallerComponent;
+ }
+
private static File preparePackageParserCache(boolean isUpgrade) {
if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
return null;
@@ -3811,6 +3819,16 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
+ public void deletePreloadsFileCache() {
+ if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
+ throw new SecurityException("Only system or settings may call deletePreloadsFileCache");
+ }
+ File dir = Environment.getDataPreloadsFileCacheDirectory();
+ Slog.i(TAG, "Deleting preloaded file cache " + dir);
+ FileUtils.deleteContents(dir);
+ }
+
+ @Override
public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize,
final IPackageDataObserver observer) {
mContext.enforceCallingOrSelfPermission(
@@ -3863,19 +3881,27 @@ public class PackageManagerService extends IPackageManager.Stub {
public void freeStorage(String volumeUuid, long bytes, int storageFlags) throws IOException {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
final File file = storage.findPathForUuid(volumeUuid);
+ if (file.getUsableSpace() >= bytes) return;
if (ENABLE_FREE_CACHE_V2) {
final boolean aggressive = (storageFlags
& StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+ final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL,
+ volumeUuid);
// 1. Pre-flight to determine if we have any chance to succeed
// 2. Consider preloaded data (after 1w honeymoon, unless aggressive)
+ if (internalVolume && (aggressive || SystemProperties
+ .getBoolean("persist.sys.preloads.file_cache_expired", false))) {
+ deletePreloadsFileCache();
+ if (file.getUsableSpace() >= bytes) return;
+ }
// 3. Consider parsed APK data (aggressive only)
- if (aggressive) {
+ if (internalVolume && aggressive) {
FileUtils.deleteContents(mCacheDir);
+ if (file.getUsableSpace() >= bytes) return;
}
- if (file.getUsableSpace() >= bytes) return;
// 4. Consider cached app data (above quotas)
try {
@@ -16928,6 +16954,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
updateSequenceNumberLP(pkgName, res.newUsers);
+ updateInstantAppInstallerLocked();
}
}
}
@@ -17503,6 +17530,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers);
}
updateSequenceNumberLP(packageName, info.removedUsers);
+ updateInstantAppInstallerLocked();
}
}
}
@@ -19848,6 +19876,12 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
scheduleWritePackageRestrictionsLocked(userId);
updateSequenceNumberLP(packageName, new int[] { userId });
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ updateInstantAppInstallerLocked();
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
components = mPendingBroadcasts.get(userId, packageName);
final boolean newPackage = components == null;
if (newPackage) {
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 17e5e9f0fe69..db23a6ac8305 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -43,7 +43,11 @@ class GlobalActions implements GlobalActionsListener {
mLegacyGlobalActions = new LegacyGlobalActions(context, windowManagerFuncs,
this::onGlobalActionsDismissed);
mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class);
- mStatusBarInternal.setGlobalActionsListener(this);
+
+ // Some form factors do not have a status bar.
+ if (mStatusBarInternal != null) {
+ mStatusBarInternal.setGlobalActionsListener(this);
+ }
}
public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
diff --git a/services/core/java/com/android/server/security/KeyChainSystemService.java b/services/core/java/com/android/server/security/KeyChainSystemService.java
new file mode 100644
index 000000000000..bfeaee6af7c4
--- /dev/null
+++ b/services/core/java/com/android/server/security/KeyChainSystemService.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.security.IKeyChainService;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+/**
+ * Service related to {@link android.security.KeyChain}.
+ * <p>
+ * Most of the implementation of KeyChain is provided by the com.android.keychain app. Until O,
+ * this was OK because a system app has roughly the same privileges as the system process.
+ * <p>
+ * With the introduction of background check, PACKAGE_* broadcasts (_ADDED, _REMOVED, _REPLACED)
+ * aren't received when the KeyChain app is in the background, which is bad as it uses those to
+ * drive internal cleanup.
+ * <p>
+ * TODO (b/35968281): take a more sophisticated look at what bits of KeyChain should be inside the
+ * system server and which make sense inside a system app.
+ */
+public class KeyChainSystemService extends SystemService {
+
+ private static final String TAG = "KeyChainSystemService";
+
+ public KeyChainSystemService(final Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ IntentFilter packageFilter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme("package");
+ try {
+ getContext().registerReceiverAsUser(mPackageReceiver, UserHandle.ALL,
+ packageFilter, null /*broadcastPermission*/, null /*handler*/);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Unable to register for package removed broadcast", e);
+ }
+ }
+
+ private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent broadcastIntent) {
+ if (broadcastIntent.getPackage() != null) {
+ return;
+ }
+
+ try {
+ final Intent intent = new Intent(IKeyChainService.class.getName());
+ ComponentName service =
+ intent.resolveSystemService(getContext().getPackageManager(), 0 /*flags*/);
+ if (service == null) {
+ return;
+ }
+ intent.setComponent(service);
+ intent.setAction(broadcastIntent.getAction());
+ getContext().startServiceAsUser(intent, UserHandle.of(getSendingUserId()));
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Unable to forward package removed broadcast to KeyChain", e);
+ }
+ }
+ };
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ce28cbaaf16a..f0732dd56f7a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -99,6 +99,7 @@ import com.android.server.power.ShutdownThread;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.retaildemo.RetailDemoModeService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
+import com.android.server.security.KeyChainSystemService;
import com.android.server.soundtrigger.SoundTriggerService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
@@ -736,6 +737,10 @@ public final class SystemServer {
new KeyAttestationApplicationIdProviderService(context));
traceEnd();
+ traceBeginAndSlog("StartKeyChainSystemService");
+ mSystemServiceManager.startService(KeyChainSystemService.class);
+ traceEnd();
+
traceBeginAndSlog("StartSchedulingPolicyService");
ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());
traceEnd();
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index ab83b9d84747..4c23d79cf7ff 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -52,6 +52,7 @@ import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import com.android.server.lights.Light;
@@ -259,6 +260,7 @@ public class NotificationManagerServiceTest {
@Test
@UiThreadTest
+ @Ignore("Flaky")
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index ca3763127f4d..1aa952cd58b9 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -34,6 +34,7 @@ import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
import android.hardware.soundtrigger.SoundTriggerModule;
+import android.os.DeadObjectException;
import android.os.PowerManager;
import android.os.RemoteException;
import android.telephony.PhoneStateListener;
@@ -46,6 +47,8 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
import java.util.UUID;
/**
@@ -376,7 +379,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
if (modelData == null || !modelData.isKeyphraseModel()) {
- Slog.e(TAG, "No model exists for given keyphrase Id.");
+ Slog.e(TAG, "No model exists for given keyphrase Id " + keyphraseId);
return STATUS_ERROR;
}
@@ -609,13 +612,16 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return;
}
+ model.setStopped();
try {
callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
+ } catch (DeadObjectException e) {
+ forceStopAndUnloadModel(model, e);
+ return;
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
}
- model.setStopped();
RecognitionConfig config = model.getRecognitionConfig();
if (config == null) {
Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
@@ -699,6 +705,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
modelData.setStopped();
try {
modelData.getCallback().onRecognitionPaused();
+ } catch (DeadObjectException e) {
+ forceStopAndUnloadModel(modelData, e);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
}
@@ -710,8 +718,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
try {
sendErrorCallbacksToAll(STATUS_ERROR);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onError", e);
} finally {
internalClearModelStateLocked();
internalClearGlobalStateLocked();
@@ -748,15 +754,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
Slog.w(TAG, "Received onRecognition event without callback for keyphrase model.");
return;
}
+ modelData.setStopped();
try {
modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
+ } catch (DeadObjectException e) {
+ forceStopAndUnloadModel(modelData, e);
+ return;
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
}
- modelData.setStopped();
-
RecognitionConfig config = modelData.getRecognitionConfig();
if (config != null) {
// Whether we should continue by starting this again.
@@ -791,10 +799,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private void onServiceDiedLocked() {
try {
- MetricsLogger.count(mContext, "sth_service_died", 1);
+ MetricsLogger.count(mContext, "sth_service_died", 1);
sendErrorCallbacksToAll(SoundTrigger.STATUS_DEAD_OBJECT);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onError", e);
} finally {
internalClearModelStateLocked();
internalClearGlobalStateLocked();
@@ -879,11 +885,48 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
// Sends an error callback to all models with a valid registered callback.
- private void sendErrorCallbacksToAll(int errorCode) throws RemoteException {
+ private void sendErrorCallbacksToAll(int errorCode) {
for (ModelData modelData : mModelDataMap.values()) {
IRecognitionStatusCallback callback = modelData.getCallback();
if (callback != null) {
- callback.onError(STATUS_ERROR);
+ try {
+ callback.onError(errorCode);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException sendErrorCallbacksToAll for model handle " +
+ modelData.getHandle(), e);
+ }
+ }
+ }
+ }
+
+ private void forceStopAndUnloadModel(ModelData modelData, Exception exception) {
+ if (exception != null) {
+ Slog.e(TAG, "forceStopAndUnloadModel", exception);
+ }
+ if (modelData.isModelStarted()) {
+ Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
+ if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
+ modelData.setStopped();
+ modelData.setRequested(false);
+ } else {
+ Slog.e(TAG, "Failed to stop model " + modelData.getHandle());
+ }
+ }
+ if (modelData.isModelLoaded()) {
+ Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
+ if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
+ // Remove the model data from existence.
+ mModelDataMap.remove(modelData.getModelId());
+ Iterator it = mKeyphraseUuidMap.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry pair = (Map.Entry) it.next();
+ if (pair.getValue().equals(modelData.getModelId())) {
+ it.remove();
+ }
+ }
+ modelData.clearState();
+ } else {
+ Slog.e(TAG, "Failed to unload model " + modelData.getHandle());
}
}
}
@@ -976,6 +1019,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
if (notify) {
try {
callback.onError(status);
+ } catch (DeadObjectException e) {
+ forceStopAndUnloadModel(modelData, e);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onError", e);
}
@@ -988,6 +1033,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
if (notify) {
try {
callback.onRecognitionResumed();
+ } catch (DeadObjectException e) {
+ forceStopAndUnloadModel(modelData, e);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
}
@@ -1013,6 +1060,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
if (notify) {
try {
callback.onError(status);
+ } catch (DeadObjectException e) {
+ forceStopAndUnloadModel(modelData, e);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onError", e);
}
@@ -1024,6 +1073,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
if (notify) {
try {
callback.onRecognitionPaused();
+ } catch (DeadObjectException e) {
+ forceStopAndUnloadModel(modelData, e);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
}
diff --git a/tests/testables/src/android/testing/AndroidTestingRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java
index 816ed033a3e2..a425f70e836c 100644
--- a/tests/testables/src/android/testing/AndroidTestingRunner.java
+++ b/tests/testables/src/android/testing/AndroidTestingRunner.java
@@ -18,7 +18,7 @@ import android.support.test.internal.runner.junit4.statement.RunAfters;
import android.support.test.internal.runner.junit4.statement.RunBefores;
import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
-import android.testing.TestableLooper.LooperStatement;
+import android.testing.TestableLooper.LooperFrameworkMethod;
import android.testing.TestableLooper.RunWithLooper;
import org.junit.After;
@@ -30,6 +30,7 @@ import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -49,28 +50,21 @@ public class AndroidTestingRunner extends BlockJUnit4ClassRunner {
@Override
protected Statement methodInvoker(FrameworkMethod method, Object test) {
- return shouldRunOnUiThread(method) ? new UiThreadStatement(
- methodInvokerInt(method, test), true) : methodInvokerInt(method, test);
- }
-
- protected Statement methodInvokerInt(FrameworkMethod method, Object test) {
- RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
- if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
- if (annotation != null) {
- return new LooperStatement(super.methodInvoker(method, test),
- annotation.setAsMainLooper(), test);
- }
- return super.methodInvoker(method, test);
+ method = looperWrap(method, test, method);
+ final Statement statement = super.methodInvoker(method, test);
+ return shouldRunOnUiThread(method) ? new UiThreadStatement(statement, true) : statement;
}
protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
- List befores = this.getTestClass().getAnnotatedMethods(Before.class);
+ List befores = looperWrap(method, target,
+ this.getTestClass().getAnnotatedMethods(Before.class));
return befores.isEmpty() ? statement : new RunBefores(method, statement,
befores, target);
}
protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
- List afters = this.getTestClass().getAnnotatedMethods(After.class);
+ List afters = looperWrap(method, target,
+ this.getTestClass().getAnnotatedMethods(After.class));
return afters.isEmpty() ? statement : new RunAfters(method, statement, afters,
target);
}
@@ -88,6 +82,30 @@ public class AndroidTestingRunner extends BlockJUnit4ClassRunner {
return annotation == null ? 0L : annotation.timeout();
}
+ protected List<FrameworkMethod> looperWrap(FrameworkMethod method, Object test,
+ List<FrameworkMethod> methods) {
+ RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
+ if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
+ if (annotation != null) {
+ methods = new ArrayList<>(methods);
+ for (int i = 0; i < methods.size(); i++) {
+ methods.set(i, LooperFrameworkMethod.get(methods.get(i),
+ annotation.setAsMainLooper(), test));
+ }
+ }
+ return methods;
+ }
+
+ protected FrameworkMethod looperWrap(FrameworkMethod method, Object test,
+ FrameworkMethod base) {
+ RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
+ if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
+ if (annotation != null) {
+ return LooperFrameworkMethod.get(base, annotation.setAsMainLooper(), test);
+ }
+ return base;
+ }
+
public boolean shouldRunOnUiThread(FrameworkMethod method) {
if (mKlass.getAnnotation(UiThreadTest.class) != null) {
return true;
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 8a33cf918646..62490bc214a4 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -15,20 +15,21 @@
package android.testing;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
+import android.os.TestLooperManager;
+import android.support.test.InstrumentationRegistry;
import android.util.ArrayMap;
-import org.junit.runners.model.Statement;
+import org.junit.runners.model.FrameworkMethod;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.util.Map;
/**
@@ -38,65 +39,35 @@ import java.util.Map;
*/
public class TestableLooper {
- private final Method mNext;
- private final Method mRecycleUnchecked;
-
private Looper mLooper;
private MessageQueue mQueue;
private boolean mMain;
private Object mOriginalMain;
private MessageHandler mMessageHandler;
- private int mParsedCount;
private Handler mHandler;
private Message mEmptyMessage;
+ private TestLooperManager mQueueWrapper;
- public TestableLooper() throws Exception {
- this(true);
- }
-
- public TestableLooper(boolean setMyLooper) throws Exception {
- setupQueue(setMyLooper);
- mNext = mQueue.getClass().getDeclaredMethod("next");
- mNext.setAccessible(true);
- mRecycleUnchecked = Message.class.getDeclaredMethod("recycleUnchecked");
- mRecycleUnchecked.setAccessible(true);
+ public TestableLooper(Looper l) throws Exception {
+ this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
}
- public Looper getLooper() {
- return mLooper;
+ private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
+ mQueueWrapper = wrapper;
+ setupQueue(l);
}
- private void clearLooper() throws NoSuchFieldException, IllegalAccessException {
- Field field = Looper.class.getDeclaredField("sThreadLocal");
- field.setAccessible(true);
- ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null);
- sThreadLocal.set(null);
+ private TestableLooper(Looper looper, boolean b) throws Exception {
+ setupQueue(looper);
}
- private boolean setForCurrentThread() throws NoSuchFieldException, IllegalAccessException {
- if (Looper.myLooper() != mLooper) {
- Field field = Looper.class.getDeclaredField("sThreadLocal");
- field.setAccessible(true);
- ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null);
- sThreadLocal.set(mLooper);
- return true;
- }
- return false;
+ public Looper getLooper() {
+ return mLooper;
}
- private void setupQueue(boolean setMyLooper) throws Exception {
- if (setMyLooper) {
- clearLooper();
- Looper.prepare();
- mLooper = Looper.myLooper();
- } else {
- Constructor<Looper> constructor = Looper.class.getDeclaredConstructor(
- boolean.class);
- constructor.setAccessible(true);
- mLooper = constructor.newInstance(true);
- }
-
+ private void setupQueue(Looper l) throws Exception {
+ mLooper = l;
mQueue = mLooper.getQueue();
mHandler = new Handler(mLooper);
}
@@ -121,9 +92,7 @@ public class TestableLooper {
* tests.
*/
public void destroy() throws NoSuchFieldException, IllegalAccessException {
- if (Looper.myLooper() == mLooper) {
- clearLooper();
- }
+ mQueueWrapper.release();
if (mMain && mOriginalMain != null) {
Field field = mLooper.getClass().getDeclaredField("sMainLooper");
field.setAccessible(true);
@@ -164,26 +133,26 @@ public class TestableLooper {
private boolean parseMessageInt() {
try {
- Message result = (Message) mNext.invoke(mQueue);
+ Message result = mQueueWrapper.next();
if (result != null) {
// This is a break message.
if (result == mEmptyMessage) {
- mRecycleUnchecked.invoke(result);
+ mQueueWrapper.recycle(result);
return false;
}
if (mMessageHandler != null) {
if (mMessageHandler.onMessageHandled(result)) {
result.getTarget().dispatchMessage(result);
- mRecycleUnchecked.invoke(result);
+ mQueueWrapper.recycle(result);
} else {
- mRecycleUnchecked.invoke(result);
+ mQueueWrapper.recycle(result);
// Message handler indicated it doesn't want us to continue.
return false;
}
} else {
result.getTarget().dispatchMessage(result);
- mRecycleUnchecked.invoke(result);
+ mQueueWrapper.recycle(result);
}
} else {
// No messages, don't continue parsing
@@ -199,10 +168,14 @@ public class TestableLooper {
* Runs an executable with myLooper set and processes all messages added.
*/
public void runWithLooper(RunnableWithException runnable) throws Exception {
- boolean set = setForCurrentThread();
- runnable.run();
+ new Handler(getLooper()).post(() -> {
+ try {
+ runnable.run();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
processAllMessages();
- if (set) clearLooper();
}
public interface RunnableWithException {
@@ -221,33 +194,131 @@ public class TestableLooper {
return sLoopers.get(test);
}
- public static class LooperStatement extends Statement {
- private final boolean mSetAsMain;
- private final Statement mBase;
- private final TestableLooper mLooper;
+ public static class LooperFrameworkMethod extends FrameworkMethod {
+ private HandlerThread mHandlerThread;
+
+ private final TestableLooper mTestableLooper;
+ private final Looper mLooper;
+ private final Handler mHandler;
- public LooperStatement(Statement base, boolean setAsMain, Object test) {
- mBase = base;
+ public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
+ super(base.getMethod());
try {
- mLooper = new TestableLooper(false);
- sLoopers.put(test, mLooper);
- mSetAsMain = setAsMain;
+ mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
+ mTestableLooper = new TestableLooper(mLooper, false);
} catch (Exception e) {
throw new RuntimeException(e);
}
+ sLoopers.put(test, mTestableLooper);
+ mHandler = new Handler(mLooper);
}
- @Override
- public void evaluate() throws Throwable {
- mLooper.setForCurrentThread();
- if (mSetAsMain) {
- mLooper.setAsMainLooper();
+ public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
+ super(base.getMethod());
+ mLooper = other.mLooper;
+ mTestableLooper = other;
+ mHandler = new Handler(mLooper);
+ }
+
+ public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
+ if (sLoopers.containsKey(test)) {
+ return new LooperFrameworkMethod(sLoopers.get(test), base);
}
+ return new LooperFrameworkMethod(base, setAsMain, test);
+ }
+ @Override
+ public Object invokeExplosively(Object target, Object... params) throws Throwable {
+ if (Looper.myLooper() == mLooper) {
+ // Already on the right thread from another statement, just execute then.
+ return super.invokeExplosively(target, params);
+ }
+ boolean set = mTestableLooper.mQueueWrapper == null;
+ if (set) {
+ mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation()
+ .acquireLooperManager(mLooper);
+ }
try {
- mBase.evaluate();
+ Object[] ret = new Object[1];
+ // Run the execution on the looper thread.
+ Runnable execute = () -> {
+ try {
+ ret[0] = super.invokeExplosively(target, params);
+ } catch (Throwable throwable) {
+ throw new LooperException(throwable);
+ }
+ };
+ mHandler.post(execute);
+ // Try to wait for the message to be queued.
+ for (int i = 0; i < 10; i++) {
+ if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
+ Thread.sleep(1);
+ }
+ }
+ if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
+ throw new RuntimeException("Message didn't queue...");
+ }
+ Message m = mTestableLooper.mQueueWrapper.next();
+ // Parse all other messages until we get to ours.
+ while (m.getTarget() != mHandler) {
+ try {
+ mTestableLooper.mQueueWrapper.execute(m);
+ } catch (LooperException e) {
+ throw e.getSource();
+ } finally {
+ mTestableLooper.mQueueWrapper.recycle(m);
+ }
+ m = mTestableLooper.mQueueWrapper.next();
+ }
+ // Dispatch our message.
+ try {
+ mTestableLooper.mQueueWrapper.execute(m);
+ } catch (LooperException e) {
+ throw e.getSource();
+ } catch (RuntimeException re) {
+ // If the TestLooperManager has to post, it will wrap what it throws in a
+ // RuntimeException, make sure we grab the actual source.
+ if (re.getCause() instanceof LooperException) {
+ throw ((LooperException) re.getCause()).getSource();
+ } else {
+ throw re.getCause();
+ }
+ } finally {
+ mTestableLooper.mQueueWrapper.recycle(m);
+ }
+ return ret[0];
} finally {
- mLooper.destroy();
+ if (set) {
+ mTestableLooper.mQueueWrapper.release();
+ mTestableLooper.mQueueWrapper = null;
+ }
+ }
+ }
+
+ private Looper createLooper() {
+ // TODO: Find way to share these.
+ mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
+ mHandlerThread.start();
+ return mHandlerThread.getLooper();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ if (mHandlerThread != null) {
+ mHandlerThread.quit();
+ }
+ }
+
+ private static class LooperException extends RuntimeException {
+ private final Throwable mSource;
+
+ public LooperException(Throwable t) {
+ mSource = t;
+ }
+
+ public Throwable getSource() {
+ return mSource;
}
}
}
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 18e5fffef992..12f1d0a5f414 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -24,17 +24,16 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.testing.TestableLooper.MessageHandler;
import android.testing.TestableLooper.RunWithLooper;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class TestableLooperTest {
@@ -46,11 +45,6 @@ public class TestableLooperTest {
mTestableLooper = TestableLooper.get(this);
}
- @After
- public void tearDown() throws Exception {
- mTestableLooper.destroy();
- }
-
@Test
public void testMessageExecuted() throws Exception {
Handler h = new Handler();
@@ -133,39 +127,23 @@ public class TestableLooperTest {
@Test
public void testMainLooper() throws Exception {
assertNotEquals(Looper.myLooper(), Looper.getMainLooper());
-
- Looper originalMain = Looper.getMainLooper();
- mTestableLooper.setAsMainLooper();
- assertEquals(Looper.myLooper(), Looper.getMainLooper());
- Runnable r = mock(Runnable.class);
-
- new Handler(Looper.getMainLooper()).post(r);
- mTestableLooper.processAllMessages();
-
- verify(r).run();
- mTestableLooper.destroy();
-
- assertEquals(originalMain, Looper.getMainLooper());
- }
-
- @Test
- public void testNotMyLooper() throws Exception {
- TestableLooper looper = new TestableLooper(false);
-
- assertEquals(Looper.myLooper(), mTestableLooper.getLooper());
- assertNotEquals(Looper.myLooper(), looper.getLooper());
-
Runnable r = mock(Runnable.class);
Runnable r2 = mock(Runnable.class);
- new Handler().post(r);
- new Handler(looper.getLooper()).post(r2);
-
- looper.processAllMessages();
- verify(r2).run();
- verify(r, never()).run();
-
- mTestableLooper.processAllMessages();
- verify(r).run();
+ TestableLooper testableLooper = new TestableLooper(Looper.getMainLooper());
+
+ try {
+ testableLooper.setMessageHandler(m -> {
+ if (m.getCallback() == r) return true;
+ return false;
+ });
+ new Handler(Looper.getMainLooper()).post(r);
+ testableLooper.processAllMessages();
+
+ verify(r).run();
+ verify(r2, never()).run();
+ } finally {
+ testableLooper.destroy();
+ }
}
@Test
diff --git a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
index 027b049a68c9..e8e873199e17 100644
--- a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
@@ -111,6 +111,7 @@ public final class ConfigParser {
*
* Content-Type: multipart/mixed; boundary={boundary}
* Content-Transfer-Encoding: base64
+ * [Skip uninterested headers]
*
* --{boundary}
* Content-Type: application/x-passpoint-profile
@@ -326,7 +327,8 @@ public final class ConfigParser {
header.encodingType = entry.getValue();
break;
default:
- throw new IOException("Unexpected header: " + entry.getKey());
+ Log.d(TAG, "Ignore header: " + entry.getKey());
+ break;
}
}
return header;
@@ -344,21 +346,24 @@ public final class ConfigParser {
* @throws IOException
*/
private static Pair<String, String> parseContentType(String contentType) throws IOException {
- String[] attributes = contentType.toString().split(";");
+ String[] attributes = contentType.split(";");
String type = null;
String boundary = null;
- if (attributes.length < 1 || attributes.length > 2) {
+ if (attributes.length < 1) {
throw new IOException("Invalid Content-Type: " + contentType);
}
+ // The type is always the first attribute.
type = attributes[0].trim();
- if (attributes.length == 2) {
- boundary = attributes[1].trim();
- if (!boundary.startsWith(BOUNDARY)) {
- throw new IOException("Invalid Content-Type: " + contentType);
+ // Look for boundary string from the rest of the attributes.
+ for (int i = 1; i < attributes.length; i++) {
+ String attribute = attributes[i].trim();
+ if (!attribute.startsWith(BOUNDARY)) {
+ Log.d(TAG, "Ignore Content-Type attribute: " + attributes[i]);
+ continue;
}
- boundary = boundary.substring(BOUNDARY.length());
+ boundary = attribute.substring(BOUNDARY.length());
// Remove the leading and trailing quote if present.
if (boundary.length() > 1 && boundary.startsWith("\"") && boundary.endsWith("\"")) {
boundary = boundary.substring(1, boundary.length()-1);
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
index 2ffe42859fc8..5dc5d13569c6 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
@@ -144,6 +144,8 @@ public final class PpsMoParser {
private static final String NODE_TIME_LIMIT = "TimeLimit";
private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+ private static final String NODE_EXTENSION = "Extension";
+
/**
* Fields under HomeSP subtree.
*/
@@ -629,6 +631,10 @@ public final class PpsMoParser {
case NODE_CREDENTIAL_PRIORITY:
config.setCredentialPriority(parseInteger(getPpsNodeValue(child)));
break;
+ case NODE_EXTENSION:
+ // All vendor specific information will be under this node.
+ Log.d(TAG, "Ignore Extension node for vendor specific information");
+ break;
default:
throw new ParsingException("Unknown node: " + child.getName());
}
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
index 995963d2b4cc..56919c25de46 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
@@ -1,85 +1,86 @@
-Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5PXtib3VuZGFyeX0KQ29udGVu
-dC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBh
-cHBsaWNhdGlvbi94LXBhc3Nwb2ludC1wcm9maWxlCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6
-IGJhc2U2NAoKUEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29n
-SUR4V1pYSkVWRVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVo
-YldVK1VHVnlVSEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0Fn
-UEZKVVVISnZjR1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhi
-V1UrZFhKdU9uZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZ
-M0pwY0hScGIyNDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThM
-MUpVVUhKdmNHVnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVSth
-VEF3TVR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFRt
-RnRaVDVJYjIxbFUxQTgKTDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lD
-QWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdApaVHd2VG05a1pVNWhiV1UrQ2lBZ0lD
-QWdJQ0FnSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0FnCklD
-QWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
-bFBrWlJSRTQ4TDA1dlpHVk8KWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBtMXBOaTVqYnk1
-MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnSUNBZ0lEeE9iMlJsUGdv
-Z0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhMMDV2
-ClpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThMMVpo
-YkhWbFBnb2dJQ0FnSUNBZ0lEd3YKVG05a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0E4
-VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdQpkR2xoYkR3dlRtOWtaVTVo
-YldVK0NpQWdJQ0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1VtVmhi
-RzA4CkwwNXZaR1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBuTm9ZV3RsYmk1emRHbHlj
-bVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9i
-MlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbApVR0Z6YzNkdmNtUThM
-MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2Iy
-UmxUbUZ0ClpUNVZjMlZ5Ym1GdFpUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
-eDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lD
-QWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaApjM04zYjNKa1BD
-OU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5UFR3
-dlZtRnNkV1UrCkNpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0
-S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtWQlVFMWxkR2h2WkR3dlRtOWtaVTVoYldV
-K0NpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4TwpiMlJsVG1G
-dFpUNUZRVkJVZVhCbFBDOU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1Ur
-TWpFOEwxWmhiSFZsClBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0Fn
-SUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRaVDVKYm01bGNrMWxkR2h2
-WkR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSQpRVkF0
-VmpJOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThM
-MDV2WkdVK0NpQWdJQ0FnCklDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJ
-Q0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjAKYVdacFkyRjBaVHd2VG05a1pVNWhi
-V1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQ
-a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
-eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
-QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
-VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
-KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
-bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
-OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
-Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0E4
-VG05awpaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrbE5VMGs4TDA1dlpHVk9ZVzFs
-UGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzCmRXVSthVzF6YVR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0Fn
-SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWcKSUNBZ0lDQWdQRTV2
-WkdWT1lXMWxQa1ZCVUZSNWNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
-V1UrTWpROApMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEd3ZU
-bTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdQQzlPYjJSbFBnb2dJRHd2VG05a1pUNEtQ
-QzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94
-LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRMUzFD
-UlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlowbEtR
-VWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQwSm5U
-bFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYyaGpU
-azFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZrRm5V
-VEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtOblMw
-TkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIxV1ZY
-TldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJOQ01T
-dHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpVClJV
-RkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwyUzJo
-U1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dyVUdK
-ck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIzUWtz
-M2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNLMGxh
-CmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFVRzVX
-TTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNXVVZH
-U1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJOMDFF
-YlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RWSkJk
-MFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldVUldV
-akJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNXaDJZ
-MDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQwaEZD
-amxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5VVNY
-ZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5KT1Ew
-OTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhsT2JX
-MVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhnMmVF
-MXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxXYUhw
-aFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5WnpG
-S1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQTDNS
-a1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJKUmts
-RFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
+TUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5
+PXtib3VuZGFyeX07IGNoYXJzZXQ9VVRGLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmFz
+ZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXBhc3Nwb2ludC1w
+cm9maWxlOyBjaGFyc2V0PVVURi04CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJhc2U2NAoK
+UEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29nSUR4V1pYSkVW
+RVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVoYldVK1VHVnlV
+SEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0FnUEZKVVVISnZj
+R1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhiV1UrZFhKdU9u
+ZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZM0pwY0hScGIy
+NDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThMMUpVVUhKdmNH
+VnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVSthVEF3TVR3dlRt
+OWtaVTVoYldVK0NpQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFRtRnRaVDVJYjIx
+bFUxQTgKTDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnUEU1
+dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdApaVHd2VG05a1pVNWhiV1UrQ2lBZ0lDQWdJQ0FnSUNB
+OFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0FnCklDQWdQQzlPYjJS
+bFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrWlJSRTQ4
+TDA1dlpHVk8KWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBtMXBOaTVqYnk1MWF6d3ZWbUZz
+ZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0Fn
+SUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhMMDV2ClpHVk9ZVzFs
+UGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThMMVpoYkhWbFBnb2dJ
+Q0FnSUNBZ0lEd3YKVG05a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0E4VG05a1pUNEtJ
+Q0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdQpkR2xoYkR3dlRtOWtaVTVoYldVK0NpQWdJ
+Q0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1VtVmhiRzA4CkwwNXZa
+R1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBuTm9ZV3RsYmk1emRHbHljbVZrTG1OdmJU
+d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lD
+QWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbApVR0Z6YzNkdmNtUThMMDV2WkdWT1lX
+MWxQZ29nSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxUbUZ0ClpU
+NVZjMlZ5Ym1GdFpUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lXeDFaVDVxWVcx
+bGN6d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0E4VG05
+a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaApjM04zYjNKa1BDOU9iMlJsVG1G
+dFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5UFR3dlZtRnNkV1Ur
+CkNpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0S0lDQWdJQ0Fn
+SUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtWQlVFMWxkR2h2WkR3dlRtOWtaVTVoYldVK0NpQWdJQ0Fn
+SUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4TwpiMlJsVG1GdFpUNUZRVkJV
+ZVhCbFBDOU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1UrTWpFOEwxWmhi
+SFZsClBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxQ
+Z29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRaVDVKYm01bGNrMWxkR2h2WkR3dlRtOWta
+VTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSQpRVkF0VmpJOEwxWmhi
+SFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0Np
+QWdJQ0FnCklDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BF
+NXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjAKYVdacFkyRjBaVHd2VG05a1pVNWhiV1UrQ2lBZ0lD
+QWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQa05sY25ScFpt
+bGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lXeDFaVDU0TlRB
+NWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnUEU1
+dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1qVTJSbWx1WjJW
+eWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdVKwpNV1l4WmpG
+bU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZt
+TVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNBOEwwNXZaR1Ur
+Q2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0Fn
+UEU1dlpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0E4VG05awpaVDRL
+SUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrbE5VMGs4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJ
+Q0FnSUNBZ0lDQThWbUZzCmRXVSthVzF6YVR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNBOEwwNXZa
+R1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWcKSUNBZ0lDQWdQRTV2WkdWT1lXMWxQ
+a1ZCVUZSNWNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1UrTWpROApM
+MVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEd3ZUbTlrWlQ0S0lD
+QWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdQQzlPYjJSbFBnb2dJRHd2VG05a1pUNEtQQzlOWjIxMFZI
+SmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXg1MDktY2Et
+Y2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRMUzFDUlVkSlRpQkRS
+VkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlowbEtRVWxNYkVaa2Qz
+cE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQwSm5UbFlLUWtGTlZF
+SXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYyaGpUazFxV1hkTlZF
+RTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZrRm5VVEJGZUUxSlNV
+Skpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtOblMwTkJVVVZCQ25w
+dVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIxV1ZYTldkVzFFWWxs
+SWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJOQ01TdHZSMWhhZGto
+M2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpVClJVRkRaV1pXYW1v
+d2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwyUzJoU1FqZzFNVEpR
+UWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dyVUdKck1IVjVhM1Jr
+WW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIzUWtzM2FFUTRjRkIy
+WmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNLMGxhCmFYQllOREY0
+UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFVRzVXTTJreGRIRXdO
+R3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNXVVZHU1hkWU5IWnpP
+RUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJOMDFFYlVGR1NYZFlO
+SFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RWSkJkMFJuCldVUldV
+VkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldVUldVakJVUWtGVmQw
+RjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNXaDJZMDVCVVVWTVFs
+RkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQwaEZDamxIUlZBdmRX
+OW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5VVNYZEpWV00zCmQy
+azNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5KT1EwOTBhWE5rUW5F
+eWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhsT2JXMVdUQW94Y1VK
+S2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhnMmVFMXVWR3c0ZUVW
+WFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxXYUhwaFNFb3hkVlk0
+Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5WnpGS1VDdHNlRllL
+YlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQTDNSa1RrNTJRMWw2
+YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJKUmtsRFFWUkZMUzB0
+TFMwSwotLXtib3VuZGFyeX0tLQo=
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
index 3ddd09f91ff4..a44b54222589 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
@@ -1,8 +1,9 @@
-Content-Type: multipart/mixed; boundary={boundary}
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary={boundary}; charset=UTF-8
Content-Transfer-Encoding: base64
--{boundary}
-Content-Type: application/x-passpoint-profile
+Content-Type: application/x-passpoint-profile; charset=UTF-8
Content-Transfer-Encoding: base64
PE1nbXRUcmVlIHhtbG5zPSJzeW5jbWw6ZG1kZGYxLjIiPgogIDxWZXJEVEQ+MS4yPC9WZXJEVEQ+
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 7f2d95de95e9..1fb83094a002 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -14,6 +14,13 @@
<Node>
<NodeName>i001</NodeName>
<Node>
+ <NodeName>Extension</NodeName>
+ <Node>
+ <NodeName>VendorSpecific</NodeName>
+ <Value>Test</Value>
+ </Node>
+ </Node>
+ <Node>
<NodeName>HomeSP</NodeName>
<Node>
<NodeName>FriendlyName</NodeName>