merge in oc-release history after reset to master
diff --git a/api/current.txt b/api/current.txt
index bc41d65..6da09e3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1234,6 +1234,7 @@
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportsAssist = 16844016; // 0x10104f0
+ field public static final int supportsDismissingWindow = 16844104; // 0x1010548
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
field public static final int supportsLocalInteraction = 16844047; // 0x101050f
field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
@@ -8959,6 +8960,7 @@
field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+ field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 24f9aad..5640eaf 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1347,6 +1347,7 @@
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportsAssist = 16844016; // 0x10104f0
+ field public static final int supportsDismissingWindow = 16844104; // 0x1010548
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
field public static final int supportsLocalInteraction = 16844047; // 0x101050f
field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
@@ -9335,6 +9336,7 @@
field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+ field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
@@ -41395,6 +41397,8 @@
method public java.lang.String getDeviceSoftwareVersion();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
+ method public java.lang.String getImei();
+ method public java.lang.String getImei(int);
method public java.lang.String getLine1Number();
method public java.lang.String getMmsUAProfUrl();
method public java.lang.String getMmsUserAgent();
diff --git a/api/test-current.txt b/api/test-current.txt
index 4c277e4..714cac9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1234,6 +1234,7 @@
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportsAssist = 16844016; // 0x10104f0
+ field public static final int supportsDismissingWindow = 16844104; // 0x1010548
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
field public static final int supportsLocalInteraction = 16844047; // 0x101050f
field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
@@ -8984,6 +8985,7 @@
field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+ field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index f2a8777..a248bce 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -768,6 +768,10 @@
/**
* Called when a new batch of security logs can be retrieved.
*
+ * <p>If a secondary user or profile is created, this callback won't be received until all users
+ * become affiliated again (even if security logging is enabled).
+ * See {@link DevicePolicyManager#setAffiliationIds}
+ *
* <p>This callback is only applicable to device owners.
*
* @param context The running context as per {@link #onReceive}.
@@ -782,13 +786,18 @@
* ever be called when network logging is enabled. The logs can only be retrieved while network
* logging is enabled.
*
+ * <p>If a secondary user or profile is created, this callback won't be received until all users
+ * become affiliated again (even if network logging is enabled). It will also no longer be
+ * possible to retrieve the network logs batch with the most recent {@code batchToken} provided
+ * by this callback. See {@link DevicePolicyManager#setAffiliationIds}.
+ *
* <p>This callback is only applicable to device owners.
*
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
* @param batchToken The token representing the current batch of network logs.
* @param networkLogsCount The total count of events in the current batch of network logs.
- * @see DevicePolicyManager#retrieveNetworkLogs(ComponentName)
+ * @see DevicePolicyManager#retrieveNetworkLogs
*/
public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
int networkLogsCount) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 074326f..c95e011 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3651,15 +3651,16 @@
/**
* Called by a device owner to request a bugreport.
* <p>
- * There must be only one user on the device, managed by the device owner. Otherwise a
- * {@link SecurityException} will be thrown.
+ * If the device contains secondary users or profiles, they must be affiliated with the device
+ * owner user. Otherwise a {@link SecurityException} will be thrown. See
+ * {@link #setAffiliationIds}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return {@code true} if the bugreport collection started successfully, or {@code false} if it
* wasn't triggered because a previous bugreport operation is still active (either the
* bugreport is still running or waiting for the user to share or decline)
- * @throws SecurityException if {@code admin} is not a device owner, or if there are users other
- * than the one managed by the device owner.
+ * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
+ * profile or secondary user that is not affiliated with the device owner user.
*/
public boolean requestBugreport(@NonNull ComponentName admin) {
throwIfParentInstance("requestBugreport");
@@ -6631,14 +6632,16 @@
}
/**
- * Called by device owner to control the security logging feature. Logging can only be
- * enabled on single user devices where the sole user is managed by the device owner.
+ * Called by device owner to control the security logging feature.
*
* <p> Security logs contain various information intended for security auditing purposes.
* See {@link SecurityEvent} for details.
*
- * <p>There must be only one user on the device, managed by the device owner.
- * Otherwise a {@link SecurityException} will be thrown.
+ * <p><strong>Note:</strong> The device owner won't be able to retrieve security logs if there
+ * are unaffiliated secondary users or profiles on the device, regardless of whether the
+ * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for
+ * all users to become affiliated. Therefore it's recommended that affiliation ids are set for
+ * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
*
* @param admin Which device owner this request is associated with.
* @param enabled whether security logging should be enabled or not.
@@ -6680,13 +6683,16 @@
* <p> Access to the logs is rate limited and it will only return new logs after the device
* owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}.
*
- * <p>There must be only one user on the device, managed by the device owner.
- * Otherwise a {@link SecurityException} will be thrown.
+ * <p>If there is any other user or profile on the device, it must be affiliated with the
+ * device owner. Otherwise a {@link SecurityException} will be thrown. See
+ * {@link #setAffiliationIds}
*
* @param admin Which device owner this request is associated with.
* @return the new batch of security logs which is a list of {@link SecurityEvent},
* or {@code null} if rate limitation is exceeded or if logging is currently disabled.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
+ * profile or secondary user that is not affiliated with the device owner user.
+ * @see DeviceAdminReceiver#onSecurityLogsAvailable
*/
public @Nullable List<SecurityEvent> retrieveSecurityLogs(@NonNull ComponentName admin) {
throwIfParentInstance("retrieveSecurityLogs");
@@ -6726,14 +6732,17 @@
* will result in {@code null} being returned. The device logs are retrieved from a RAM region
* which is not guaranteed to be corruption-free during power cycles, as a result be cautious
* about data corruption when parsing. </strong>
- * <p>
- * There must be only one user on the device, managed by the device owner. Otherwise a
- * {@link SecurityException} will be thrown.
+ *
+ * <p>If there is any other user or profile on the device, it must be affiliated with the
+ * device owner. Otherwise a {@link SecurityException} will be thrown. See
+ * {@link #setAffiliationIds}
*
* @param admin Which device owner this request is associated with.
* @return Device logs from before the latest reboot of the system, or {@code null} if this API
* is not supported on the device.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
+ * profile or secondary user that is not affiliated with the device owner user.
+ * @see #retrieveSecurityLogs
*/
public @Nullable List<SecurityEvent> retrievePreRebootSecurityLogs(
@NonNull ComponentName admin) {
@@ -6939,6 +6948,12 @@
* Indicates the entity that controls the device or profile owner. Two users/profiles are
* affiliated if the set of ids set by their device or profile owners intersect.
*
+ * <p><strong>Note:</strong> Features that depend on user affiliation (such as security logging
+ * or {@link #bindDeviceAdminServiceAsUser}) won't be available when a secondary user or profile
+ * is created, until it becomes affiliated. Therefore it is recommended that the appropriate
+ * affiliation ids are set by its profile owner as soon as possible after the user/profile is
+ * created.
+ *
* @param admin Which profile or device owner this request is associated with.
* @param ids A list of opaque non-empty affiliation ids. Duplicate elements will be ignored.
*
@@ -7138,15 +7153,19 @@
}
/**
- * Called by a device owner to control the network logging feature. Logging can only be
- * enabled on single user devices where the sole user is managed by the device owner. If a new
- * user is added on the device, logging is disabled.
+ * Called by a device owner to control the network logging feature.
*
* <p> Network logs contain DNS lookup and connect() library call events.
*
+ * <p><strong>Note:</strong> The device owner won't be able to retrieve network logs if there
+ * are unaffiliated secondary users or profiles on the device, regardless of whether the
+ * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for
+ * all users to become affiliated. Therefore it's recommended that affiliation ids are set for
+ * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled whether network logging should be enabled or not.
- * @throws {@link SecurityException} if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not a device owner.
* @see #retrieveNetworkLogs
*/
public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) {
@@ -7164,7 +7183,7 @@
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only
* be {@code null} if the caller has MANAGE_USERS permission.
* @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
- * @throws {@link SecurityException} if {@code admin} is not a device owner and caller has
+ * @throws SecurityException if {@code admin} is not a device owner and caller has
* no MANAGE_USERS permission
*/
public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
@@ -7190,12 +7209,19 @@
* after the device device owner has been notified via
* {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
*
+ * <p>If a secondary user or profile is created, calling this method will throw a
+ * {@link SecurityException} until all users become affiliated again. It will also no longer be
+ * possible to retrieve the network logs batch with the most recent batchToken provided
+ * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See
+ * {@link DevicePolicyManager#setAffiliationIds}.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param batchToken A token of the batch to retrieve
* @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
* {@code null} if the batch represented by batchToken is no longer available or if
* logging is disabled.
- * @throws {@link SecurityException} if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
+ * profile or secondary user that is not affiliated with the device owner user.
* @see DeviceAdminReceiver#onNetworkLogsAvailable
*/
public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b05ceaa..d8358f9 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3843,6 +3843,52 @@
= "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
/**
+ * An {@code ArrayList} of {@code String} annotations describing content for
+ * {@link #ACTION_CHOOSER}.
+ *
+ * <p>If {@link #EXTRA_CONTENT_ANNOTATIONS} is present in an intent used to start a
+ * {@link #ACTION_CHOOSER} activity, the first three annotations will be used to rank apps.</p>
+ *
+ * <p>Annotations should describe the major components or topics of the content. It is up to
+ * apps initiating {@link #ACTION_CHOOSER} to learn and add annotations. Annotations should be
+ * learned in advance, e.g., when creating or saving content, to avoid increasing latency to
+ * start {@link #ACTION_CHOOSER}. Performance on customized annotations can suffer, if they are
+ * rarely used for {@link #ACTION_CHOOSER} in the past 14 days. Therefore, it is recommended to
+ * use the following annotations when applicable:</p>
+ * <ul>
+ * <li>"product": represents that the topic of the content is mainly about products, e.g.,
+ * health & beauty, and office supplies.</li>
+ * <li>"emotion": represents that the topic of the content is mainly about emotions, e.g.,
+ * happy, and sad.</li>
+ * <li>"person": represents that the topic of the content is mainly about persons, e.g.,
+ * face, finger, standing, and walking.</li>
+ * <li>"child": represents that the topic of the content is mainly about children, e.g.,
+ * child, and baby.</li>
+ * <li>"selfie": represents that the topic of the content is mainly about selfies.</li>
+ * <li>"crowd": represents that the topic of the content is mainly about crowds.</li>
+ * <li>"party": represents that the topic of the content is mainly about parties.</li>
+ * <li>"animal": represent that the topic of the content is mainly about animals.</li>
+ * <li>"plant": represents that the topic of the content is mainly about plants, e.g.,
+ * flowers.</li>
+ * <li>"vacation": represents that the topic of the content is mainly about vacations.</li>
+ * <li>"fashion": represents that the topic of the content is mainly about fashion, e.g.
+ * sunglasses, jewelry, handbags and clothing.</li>
+ * <li>"material": represents that the topic of the content is mainly about materials, e.g.,
+ * paper, and silk.</li>
+ * <li>"vehicle": represents that the topic of the content is mainly about vehicles, like
+ * cars, and boats.</li>
+ * <li>"document": represents that the topic of the content is mainly about documents, e.g.
+ * posters.</li>
+ * <li>"design": represents that the topic of the content is mainly about design, e.g. arts
+ * and designs of houses.</li>
+ * <li>"holiday": represents that the topic of the content is mainly about holidays, e.g.,
+ * Christmas and Thanksgiving.</li>
+ * </ul>
+ */
+ public static final String EXTRA_CONTENT_ANNOTATIONS
+ = "android.intent.extra.CONTENT_ANNOTATIONS";
+
+ /**
* A {@link ResultReceiver} used to return data back to the sender.
*
* <p>Used to complete an app-specific
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index a48762b..083e4cc6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -950,8 +950,8 @@
try {
final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
return fromCacheEntry(bytes);
- } catch (IOException ioe) {
- Slog.w(TAG, "Error reading package cache: ", ioe);
+ } catch (Exception e) {
+ Slog.w(TAG, "Error reading package cache: ", e);
// If something went wrong while reading the cache entry, delete the cache file
// so that we regenerate it the next time.
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index d67b6a8..9b72757a 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -438,7 +438,8 @@
* Soft iron - These distortions arise due to the interaction with the earth's magnetic
* field.
* </p>
- * <h4> {@link android.hardware.Sensor#TYPE_GAME_ROTATION_VECTOR}:</h4>
+ * <h4> {@link android.hardware.Sensor#TYPE_GAME_ROTATION_VECTOR
+ * Sensor.TYPE_GAME_ROTATION_VECTOR}:</h4>
* Identical to {@link android.hardware.Sensor#TYPE_ROTATION_VECTOR} except that it
* doesn't use the geomagnetic field. Therefore the Y axis doesn't
* point north, but instead to some other reference, that reference is
@@ -482,22 +483,6 @@
* <p><b>Pro Tip:</b> Always use the length of the values array while performing operations
* on it. In earlier versions, this used to be always 3 which has changed now. </p>
*
- * @see GeomagneticField
- *
- * <h4> {@link android.hardware.Sensor#TYPE_DEVICE_ORIENTATION
- * Sensor.TYPE_DEVICE_ORIENTATION}:</h4>
- * The current device orientation will be available in values[0]. The only
- * available values are:
- * <ul>
- * <li> 0: device is in default orientation (Y axis is vertical and points up)
- * <li> 1: device is rotated 90 degrees counter-clockwise from default
- * orientation (X axis is vertical and points up)
- * <li> 2: device is rotated 180 degrees from default orientation (Y axis is
- * vertical and points down)
- * <li> 3: device is rotated 90 degrees clockwise from default orientation (X axis
- * is vertical and points down)
- * </ul>
- *
* <h4>{@link android.hardware.Sensor#TYPE_POSE_6DOF
* Sensor.TYPE_POSE_6DOF}:</h4>
*
@@ -605,6 +590,8 @@
* temperature compensation is allowed).
* x_bias, y_bias, z_bias are the estimated biases.
* </p>
+ *
+ * @see GeomagneticField
*/
public final float[] values;
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index c7612d1..a265dd0 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -212,7 +212,7 @@
public native final HwBlob readBuffer();
public native final HwBlob readEmbeddedBuffer(
- long parentHandle, long offset);
+ long parentHandle, long offset, boolean nullable);
public native final void writeBuffer(HwBlob blob);
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 1c3d6bd..dbe2f6d 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -312,11 +312,6 @@
*/
void setDnsConfigurationForNetwork(int netId, in String[] servers, String domains);
- /**
- * Bind name servers to a network in the DNS resolver.
- */
- void setDnsServersForNetwork(int netId, in String[] servers, String domains);
-
void setFirewallEnabled(boolean enabled);
boolean isFirewallEnabled();
void setFirewallInterfaceRule(String iface, boolean allow);
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index c5ceecd..1ee83ae 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -25,5 +25,5 @@
boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener);
boolean setupBcb(in String command);
boolean clearBcb();
- void rebootRecoveryWithCommand(in String command);
+ void rebootRecoveryWithCommand(in String command, in boolean update);
}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index d48431a..7f9ea438 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -491,15 +491,10 @@
command += securityArg;
}
+ // RECOVERY_SERVICE writes to BCB (bootloader control block) and triggers the reboot.
RecoverySystem rs = (RecoverySystem) context.getSystemService(
Context.RECOVERY_SERVICE);
- if (!rs.setupBcb(command)) {
- throw new IOException("Setup BCB failed");
- }
-
- // Having set up the BCB (bootloader control block), go ahead and reboot
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);
+ rs.rebootRecoveryWithCommand(command, true /* update */);
throw new IOException("Reboot failed (no permissions?)");
}
@@ -713,7 +708,7 @@
// Write the command into BCB (bootloader control block) and boot from
// there. Will not return unless failed.
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
- rs.rebootRecoveryWithCommand(command.toString());
+ rs.rebootRecoveryWithCommand(command.toString(), false);
throw new IOException("Reboot failed (no permissions?)");
}
@@ -913,9 +908,9 @@
* Talks to RecoverySystemService via Binder to set up the BCB command and
* reboot into recovery accordingly.
*/
- private void rebootRecoveryWithCommand(String command) {
+ private void rebootRecoveryWithCommand(String command, boolean update) {
try {
- mService.rebootRecoveryWithCommand(command);
+ mService.rebootRecoveryWithCommand(command, update);
} catch (RemoteException ignored) {
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 5c8e6dc..b6da1d8 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -57,6 +57,7 @@
* @attr ref android.R.styleable#InputMethod_settingsActivity
* @attr ref android.R.styleable#InputMethod_isDefault
* @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod
+ * @attr ref android.R.styleable#InputMethod_supportsDismissingWindow
*/
public final class InputMethodInfo implements Parcelable {
static final String TAG = "InputMethodInfo";
@@ -104,6 +105,11 @@
private final boolean mSupportsSwitchingToNextInputMethod;
/**
+ * The flag whether this IME supports ways to dismiss its window (e.g. dismiss button.)
+ */
+ private final boolean mSupportsDismissingWindow;
+
+ /**
* Constructor.
*
* @param context The Context in which we are parsing the input method.
@@ -132,6 +138,7 @@
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
boolean isAuxIme = true;
boolean supportsSwitchingToNextInputMethod = false; // false as default
+ boolean supportsDismissingWindow = false; // false as default
mForceDefault = false;
PackageManager pm = context.getPackageManager();
@@ -171,6 +178,8 @@
supportsSwitchingToNextInputMethod = sa.getBoolean(
com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod,
false);
+ supportsDismissingWindow = sa.getBoolean(
+ com.android.internal.R.styleable.InputMethod_supportsDismissingWindow, false);
sa.recycle();
final int depth = parser.getDepth();
@@ -242,6 +251,7 @@
mIsDefaultResId = isDefaultResId;
mIsAuxIme = isAuxIme;
mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
+ mSupportsDismissingWindow = supportsDismissingWindow;
}
InputMethodInfo(Parcel source) {
@@ -250,6 +260,7 @@
mIsDefaultResId = source.readInt();
mIsAuxIme = source.readInt() == 1;
mSupportsSwitchingToNextInputMethod = source.readInt() == 1;
+ mSupportsDismissingWindow = source.readInt() == 1;
mService = ResolveInfo.CREATOR.createFromParcel(source);
mSubtypes = new InputMethodSubtypeArray(source);
mForceDefault = false;
@@ -260,8 +271,10 @@
*/
public InputMethodInfo(String packageName, String className,
CharSequence label, String settingsActivity) {
- this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null,
- 0, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */);
+ this(buildDummyResolveInfo(packageName, className, label), false /* isAuxIme */,
+ settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
+ false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+ true /* supportsDismissingWindow */);
}
/**
@@ -271,17 +284,18 @@
public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
boolean forceDefault) {
- this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId,
- forceDefault, true /* supportsSwitchingToNextInputMethod */);
+ this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
+ true /* supportsSwitchingToNextInputMethod */,
+ true /* supportsDismissingWindow */);
}
/**
* Temporary API for creating a built-in input method for test.
* @hide
*/
- public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
- String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
- boolean forceDefault, boolean supportsSwitchingToNextInputMethod) {
+ public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
+ List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
+ boolean supportsSwitchingToNextInputMethod, boolean supportsDismissingWindow) {
final ServiceInfo si = ri.serviceInfo;
mService = ri;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
@@ -291,6 +305,7 @@
mSubtypes = new InputMethodSubtypeArray(subtypes);
mForceDefault = forceDefault;
mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
+ mSupportsDismissingWindow = supportsDismissingWindow;
}
private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
@@ -431,7 +446,8 @@
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mId=" + mId
+ " mSettingsActivityName=" + mSettingsActivityName
- + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod);
+ + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
+ + " mSupportsDismissingWindow=" + mSupportsDismissingWindow);
pw.println(prefix + "mIsDefaultResId=0x"
+ Integer.toHexString(mIsDefaultResId));
pw.println(prefix + "Service:");
@@ -484,6 +500,14 @@
}
/**
+ * @return true if this input method supports ways to dismiss its window.
+ * @hide
+ */
+ public boolean supportsDismissingWindow() {
+ return mSupportsDismissingWindow;
+ }
+
+ /**
* Used to package this object into a {@link Parcel}.
*
* @param dest The {@link Parcel} to be written.
@@ -496,6 +520,7 @@
dest.writeInt(mIsDefaultResId);
dest.writeInt(mIsAuxIme ? 1 : 0);
dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
+ dest.writeInt(mSupportsDismissingWindow ? 1 : 0);
mService.writeToParcel(dest, flags);
mSubtypes.writeToParcel(dest);
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 7878611..d4baa18 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -418,7 +418,7 @@
}
}
}
- updateChooserCounts(target);
+ updateModelAndChooserCounts(target);
return super.onTargetSelected(target, alwaysCheck);
}
@@ -575,27 +575,18 @@
// Do nothing. We'll send the voice stuff ourselves.
}
- void updateChooserCounts(TargetInfo info) {
+ void updateModelAndChooserCounts(TargetInfo info) {
if (info != null) {
- UsageStatsManager usageStatsManager =
- (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
- if (usageStatsManager == null) {
- if (DEBUG) {
- Log.d(TAG, "Can not start UsageStatsManager");
- }
- return;
- }
final ResolveInfo ri = info.getResolveInfo();
Intent targetIntent = getTargetIntent();
if (ri != null && ri.activityInfo != null && targetIntent != null) {
- usageStatsManager.reportChooserSelection(ri.activityInfo.packageName, getUserId(),
- targetIntent.getType(), null, targetIntent.getAction());
if (mAdapter != null) {
mAdapter.updateModel(info.getResolvedComponentName());
+ mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
+ targetIntent.getAction());
}
if (DEBUG) {
Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
- Log.d(TAG, "Annotation to be updated is " + targetIntent.getType());
Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
}
} else if(DEBUG) {
@@ -618,7 +609,7 @@
} else {
TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
if (super.onTargetSelected(clonedTarget, false)) {
- updateChooserCounts(clonedTarget);
+ updateModelAndChooserCounts(clonedTarget);
finish();
return;
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 0950630..d734d17 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1299,6 +1299,10 @@
mResolverListController.updateModel(componentName);
}
+ public void updateChooserCounts(String packageName, int userId, String action) {
+ mResolverListController.updateChooserCounts(packageName, userId, action);
+ }
+
/**
* Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
* to complete.
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 45fad97f..d9ab47e 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -52,6 +52,8 @@
private static final boolean DEBUG = false;
+ private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
+
// One week
private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;
@@ -74,7 +76,8 @@
private final long mSinceTime;
private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>();
private final String mReferrerPackage;
- public String mContentType;
+ private String mContentType;
+ private String[] mAnnotations;
private String mAction;
private LogisticRegressionAppRanker mRanker;
@@ -91,10 +94,26 @@
mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
mContentType = intent.getType();
+ getContentAnnotations(intent);
mAction = intent.getAction();
mRanker = new LogisticRegressionAppRanker(context);
}
+ public void getContentAnnotations(Intent intent) {
+ ArrayList<String> annotations = intent.getStringArrayListExtra(
+ Intent.EXTRA_CONTENT_ANNOTATIONS);
+ if (annotations != null) {
+ int size = annotations.size();
+ if (size > NUM_OF_TOP_ANNOTATIONS_TO_USE) {
+ size = NUM_OF_TOP_ANNOTATIONS_TO_USE;
+ }
+ mAnnotations = new String[size];
+ for (int i = 0; i < size; i++) {
+ mAnnotations[i] = annotations.get(i);
+ }
+ }
+ }
+
public void compute(List<ResolvedComponentInfo> targets) {
mScoredTargets.clear();
@@ -132,12 +151,18 @@
if (launched > mostLaunched) {
mostLaunched = launched;
}
- // TODO(kanlig): get and combine counts of categories.
int selected = 0;
if (pkStats.mChooserCounts != null && mAction != null
&& pkStats.mChooserCounts.get(mAction) != null) {
selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0);
+ if (mAnnotations != null) {
+ final int size = mAnnotations.length;
+ for (int i = 0; i < size; i++) {
+ selected += pkStats.mChooserCounts.get(mAction)
+ .getOrDefault(mAnnotations[i], 0);
+ }
+ }
}
if (DEBUG) {
if (mAction == null) {
@@ -288,6 +313,12 @@
}
}
+ public void updateChooserCounts(String packageName, int userId, String action) {
+ if (mUsm != null) {
+ mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
+ }
+ }
+
public void updateModel(ComponentName componentName) {
if (mScoredTargets == null || componentName == null ||
!mScoredTargets.containsKey(componentName)) {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index d864a31..f88f6f9 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -224,4 +224,10 @@
mResolverComparator.updateModel(componentName);
}
}
+
+ public void updateChooserCounts(String packageName, int userId, String action) {
+ if (mResolverComparator != null) {
+ mResolverComparator.updateChooserCounts(packageName, userId, action);
+ }
+ }
}
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index aefdc84..1bd2333 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -822,7 +822,8 @@
}
static jobject JHwParcel_native_readEmbeddedBuffer(
- JNIEnv *env, jobject thiz, jlong parentHandle, jlong offset) {
+ JNIEnv *env, jobject thiz, jlong parentHandle, jlong offset,
+ jboolean nullable) {
hardware::Parcel *parcel =
JHwParcel::GetNativeContext(env, thiz)->getParcel();
@@ -830,11 +831,15 @@
const void *ptr;
status_t status =
- parcel->readEmbeddedBuffer(&childHandle, parentHandle, offset, &ptr);
+ parcel->readNullableEmbeddedBuffer(&childHandle, parentHandle, offset,
+ &ptr);
if (status != OK) {
jniThrowException(env, "java/util/NoSuchElementException", NULL);
return 0;
+ } else if (status == OK && !nullable && ptr == nullptr) {
+ jniThrowException(env, "java/lang/NullPointerException", NULL);
+ return 0;
}
return JHwBlob::NewObject(env, ptr, childHandle);
@@ -945,7 +950,7 @@
{ "readBuffer", "()L" PACKAGE_PATH "/HwBlob;",
(void *)JHwParcel_native_readBuffer },
- { "readEmbeddedBuffer", "(JJ)L" PACKAGE_PATH "/HwBlob;",
+ { "readEmbeddedBuffer", "(JJZ)L" PACKAGE_PATH "/HwBlob;",
(void *)JHwParcel_native_readEmbeddedBuffer },
{ "writeBuffer", "(L" PACKAGE_PATH "/HwBlob;)V",
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5458e7c..dd33718 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3178,6 +3178,18 @@
and subtype in order to provide the consistent user experience in switching
between IMEs and subtypes. -->
<attr name="supportsSwitchingToNextInputMethod" format="boolean" />
+ <!-- Set to true if this input method supports ways to dismiss the windows assigned to
+ the input method (e.g. a dismiss button rendered by the input method itself). The
+ System UI may optimize the UI by not showing system-level dismiss button if this
+ value is true.
+ <p> Must be a boolean value, either "true" or "false". The default value is "false".
+ <p> This may also be a reference to a resource (in the form "@[package:]type:name")
+ or theme attribute (in the form "?[package:]type:name") containing a value of this
+ type.
+ <p> A UI element that dismisses the input method window should report
+ {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_DISMISS} action, so
+ that accessibility services can handle it accordingly. -->
+ <attr name="supportsDismissingWindow" format="boolean" />
</declare-styleable>
<!-- This is the subtype of InputMethod. Subtype can describe locales (e.g. en_US, fr_FR...)
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 73cba89..064d31e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2784,6 +2784,7 @@
<public name="focusedByDefault" />
<public name="appCategory" />
<public name="autoSizeMaxTextSize" />
+ <public name="supportsDismissingWindow" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/coretests/res/xml/ime_meta.xml
new file mode 100644
index 0000000..a975718
--- /dev/null
+++ b/core/tests/coretests/res/xml/ime_meta.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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.
+-->
+
+<input-method
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
+>
+ <subtype
+ android:label="subtype1"
+ android:imeSubtypeLocale="en_US"
+ android:imeSubtypeMode="keyboard" />
+</input-method>
diff --git a/core/tests/coretests/res/xml/ime_meta_dismiss.xml b/core/tests/coretests/res/xml/ime_meta_dismiss.xml
new file mode 100644
index 0000000..59f8ecc
--- /dev/null
+++ b/core/tests/coretests/res/xml/ime_meta_dismiss.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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.
+-->
+
+<input-method
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
+ android:supportsDismissingWindow="true"
+>
+ <subtype
+ android:label="subtype1"
+ android:imeSubtypeLocale="en_US"
+ android:imeSubtypeMode="keyboard" />
+</input-method>
diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/coretests/res/xml/ime_meta_sw_next.xml
new file mode 100644
index 0000000..2e2ee33
--- /dev/null
+++ b/core/tests/coretests/res/xml/ime_meta_sw_next.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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.
+-->
+
+<input-method
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
+ android:supportsSwitchingToNextInputMethod="true"
+>
+ <subtype
+ android:label="subtype1"
+ android:imeSubtypeLocale="en_US"
+ android:imeSubtypeMode="keyboard" />
+</input-method>
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
new file mode 100644
index 0000000..23dc80f
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.view.inputmethod;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.annotation.XmlRes;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodInfoTest {
+
+ @Test
+ public void testEqualsAndHashCode() throws Exception {
+ final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta);
+ final InputMethodInfo clone = cloneViaParcel(imi);
+
+ assertThat(clone.equals(imi), is(true));
+ assertThat(clone.hashCode(), equalTo(imi.hashCode()));
+ }
+
+ @Test
+ public void testBooleanAttributes_DefaultValues() throws Exception {
+ final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta);
+
+ assertThat(imi.supportsSwitchingToNextInputMethod(), is(false));
+ assertThat(imi.supportsDismissingWindow(), is(false));
+
+ final InputMethodInfo clone = cloneViaParcel(imi);
+
+ assertThat(clone.supportsSwitchingToNextInputMethod(), is(false));
+ assertThat(clone.supportsDismissingWindow(), is(false));
+ }
+
+ @Test
+ public void testSupportsSwitchingToNextInputMethod() throws Exception {
+ final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_sw_next);
+
+ assertThat(imi.supportsSwitchingToNextInputMethod(), is(true));
+
+ final InputMethodInfo clone = cloneViaParcel(imi);
+
+ assertThat(clone.supportsSwitchingToNextInputMethod(), is(true));
+ }
+
+ @Test
+ public void testSupportsDismissingWindow() throws Exception {
+ final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_dismiss);
+
+ assertThat(imi.supportsDismissingWindow(), is(true));
+
+ final InputMethodInfo clone = cloneViaParcel(imi);
+
+ assertThat(clone.supportsDismissingWindow(), is(true));
+ }
+
+ private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes)
+ throws Exception {
+ final Context context = InstrumentationRegistry.getContext();
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.applicationInfo = context.getApplicationInfo();
+ serviceInfo.packageName = context.getPackageName();
+ serviceInfo.name = "DummyImeForTest";
+ serviceInfo.metaData = new Bundle();
+ serviceInfo.metaData.putInt(InputMethod.SERVICE_META_DATA, metaDataRes);
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ return new InputMethodInfo(context, resolveInfo, null /* additionalSubtypesMap */);
+ }
+
+ private InputMethodInfo cloneViaParcel(final InputMethodInfo original) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return InputMethodInfo.CREATOR.createFromParcel(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 8385b69..aab4698 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -124,7 +124,7 @@
}
@Test
- public void updateChooserCountsAfterUserSelection() throws InterruptedException {
+ public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -142,19 +142,15 @@
sOverrides.onSafelyStartCallback = targetInfo -> {
return true;
};
- String action = sendIntent.getAction();
- String annotation = sendIntent.getType();
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
- String packageName = toChoose.activityInfo.packageName;
- long toChooseCount = getCount(usm, packageName, action, annotation);
onView(withText(toChoose.activityInfo.name))
.perform(click());
waitForIdle();
verify(sOverrides.resolverListController, times(1))
+ .updateChooserCounts(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString());
+ verify(sOverrides.resolverListController, times(1))
.updateModel(toChoose.activityInfo.getComponentName());
assertThat(activity.getIsSelected(), is(true));
- long updatedCount = getCount(usm, packageName, action, annotation);
- assertThat(updatedCount, is(toChooseCount + 1l));
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index ba5206a..34c34d7 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -74,7 +74,8 @@
}
final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
- DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod);
+ DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod,
+ false /* supportsDismissingWindow */);
if (subtypes == null) {
items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
index f33362f..4df199c 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
@@ -159,14 +159,14 @@
for (String pkg : defaultImes) {
final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
final InputMethodInfo inputMethodInfo = new InputMethodInfo(
- ri, false, null, null, 0, true, true);
+ ri, false, null, null, 0, true, true, false);
inputMethods.add(inputMethodInfo);
addInstalledApp(ri);
}
for (String pkg : otherImes) {
final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
final InputMethodInfo inputMethodInfo = new InputMethodInfo(
- ri, false, null, null, 0, false, true);
+ ri, false, null, null, 0, false, true, false);
inputMethods.add(inputMethodInfo);
addInstalledApp(ri);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index f271b08..0a088e9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -883,7 +883,7 @@
private void handleMobileDataAlwaysOn() {
final boolean enable = (Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 0) == 1);
+ mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1) == 1);
final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
if (enable == isEnabled) {
return; // Nothing to do.
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 5654096..74e44d5 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1912,31 +1912,6 @@
}
@Override
- public void setDnsServersForNetwork(int netId, String[] servers, String domains) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
- Command cmd;
- if (servers.length > 0) {
- cmd = new Command("resolver", "setnetdns", netId,
- (domains == null ? "" : domains));
- for (String s : servers) {
- InetAddress a = NetworkUtils.numericToInetAddress(s);
- if (a.isAnyLocalAddress() == false) {
- cmd.appendArg(a.getHostAddress());
- }
- }
- } else {
- cmd = new Command("resolver", "clearnetdns", netId);
- }
-
- try {
- mConnector.execute(cmd);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
public void addVpnUidRanges(int netId, UidRange[] ranges) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND];
diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java
index 3c8c699..2010e64 100644
--- a/services/core/java/com/android/server/RecoverySystemService.java
+++ b/services/core/java/com/android/server/RecoverySystemService.java
@@ -181,7 +181,7 @@
}
@Override // Binder call
- public void rebootRecoveryWithCommand(String command) {
+ public void rebootRecoveryWithCommand(String command, boolean update) {
if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
synchronized (sRequestLock) {
if (!setupOrClearBcb(true, command)) {
@@ -190,7 +190,10 @@
// Having set up the BCB, go ahead and reboot.
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- pm.reboot(PowerManager.REBOOT_RECOVERY);
+ // PowerManagerService may additionally request uncrypting the package when it's
+ // to install an update (REBOOT_RECOVERY_UPDATE).
+ pm.reboot(update ? PowerManager.REBOOT_RECOVERY_UPDATE :
+ PowerManager.REBOOT_RECOVERY);
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 70faa5a..68fe505 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -182,7 +182,7 @@
details = r.getString(R.string.network_switch_metered_detail, toTransport,
fromTransport);
} else {
- Slog.wtf(TAG, "Unknown notification type " + notifyType + "on network transport "
+ Slog.wtf(TAG, "Unknown notification type " + notifyType + " on network transport "
+ getTransportName(transportType));
return;
}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 57381f2..0c80166 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -32,7 +32,6 @@
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.LinkProperties;
@@ -73,6 +72,7 @@
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
+import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
import com.android.server.net.BaseNetworkObserver;
import java.io.FileDescriptor;
@@ -1031,249 +1031,6 @@
}
}
- /**
- * A class to centralize all the network and link properties information
- * pertaining to the current and any potential upstream network.
- *
- * Calling #start() registers two callbacks: one to track the system default
- * network and a second to specifically observe TYPE_MOBILE_DUN networks.
- *
- * The methods and data members of this class are only to be accessed and
- * modified from the tethering master state machine thread. Any other
- * access semantics would necessitate the addition of locking.
- *
- * TODO: Investigate whether more "upstream-specific" logic/functionality
- * could/should be moved here.
- */
- public class UpstreamNetworkMonitor {
- public static final int EVENT_ON_AVAILABLE = 1;
- public static final int EVENT_ON_CAPABILITIES = 2;
- public static final int EVENT_ON_LINKPROPERTIES = 3;
- public static final int EVENT_ON_LOST = 4;
-
- private final Context mContext;
- private final StateMachine mTarget;
- private final int mWhat;
- private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
- private ConnectivityManager mCM;
- private NetworkCallback mDefaultNetworkCallback;
- private NetworkCallback mDunTetheringCallback;
- private NetworkCallback mMobileNetworkCallback;
- private boolean mDunRequired;
-
- public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
- mContext = ctx;
- mTarget = tgt;
- mWhat = what;
- }
-
- public void start() {
- stop();
-
- mDefaultNetworkCallback = new UpstreamNetworkCallback();
- cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
-
- final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
- .build();
- mDunTetheringCallback = new UpstreamNetworkCallback();
- cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
- }
-
- public void stop() {
- releaseMobileNetworkRequest();
-
- releaseCallback(mDefaultNetworkCallback);
- mDefaultNetworkCallback = null;
-
- releaseCallback(mDunTetheringCallback);
- mDunTetheringCallback = null;
-
- mNetworkMap.clear();
- }
-
- public void mobileUpstreamRequiresDun(boolean dunRequired) {
- final boolean valueChanged = (mDunRequired != dunRequired);
- mDunRequired = dunRequired;
- if (valueChanged && mobileNetworkRequested()) {
- releaseMobileNetworkRequest();
- registerMobileNetworkRequest();
- }
- }
-
- public boolean mobileNetworkRequested() {
- return (mMobileNetworkCallback != null);
- }
-
- public void registerMobileNetworkRequest() {
- if (mMobileNetworkCallback != null) return;
-
- final NetworkRequest.Builder builder = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
- if (mDunRequired) {
- builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
- } else {
- builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
- }
- final NetworkRequest mobileUpstreamRequest = builder.build();
-
- // The existing default network and DUN callbacks will be notified.
- // Therefore, to avoid duplicate notifications, we only register a no-op.
- mMobileNetworkCallback = new NetworkCallback();
-
- // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
- // moderate callback time (once timeout callbacks are implemented). This might
- // be useful for updating some UI. Additionally, we should definitely log a
- // message to aid in any subsequent debugging
- if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
-
- cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback);
- }
-
- public void releaseMobileNetworkRequest() {
- if (mMobileNetworkCallback == null) return;
-
- cm().unregisterNetworkCallback(mMobileNetworkCallback);
- mMobileNetworkCallback = null;
- }
-
- public NetworkState lookup(Network network) {
- return (network != null) ? mNetworkMap.get(network) : null;
- }
-
- private void handleAvailable(Network network) {
- if (VDBG) {
- Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
- }
- if (!mNetworkMap.containsKey(network)) {
- mNetworkMap.put(network,
- new NetworkState(null, null, null, network, null, null));
- }
-
- final ConnectivityManager cm = cm();
-
- if (mDefaultNetworkCallback != null) {
- cm.requestNetworkCapabilities(mDefaultNetworkCallback);
- cm.requestLinkProperties(mDefaultNetworkCallback);
- }
-
- // Requesting updates for mDunTetheringCallback is not
- // necessary. Because it's a listen, it will already have
- // heard all NetworkCapabilities and LinkProperties updates
- // since UpstreamNetworkMonitor was started. Because we
- // start UpstreamNetworkMonitor before chooseUpstreamType()
- // is ever invoked (it can register a DUN request) this is
- // mostly safe. However, if a DUN network is already up for
- // some reason (unlikely, because DUN is restricted and,
- // unless the DUN network is shared with another APN, only
- // the system can request it and this is the only part of
- // the system that requests it) we won't know its
- // LinkProperties or NetworkCapabilities.
-
- notifyTarget(EVENT_ON_AVAILABLE, network);
- }
-
- private void handleNetCap(Network network, NetworkCapabilities newNc) {
- if (!mNetworkMap.containsKey(network)) {
- // Ignore updates for networks for which we have not yet
- // received onAvailable() - which should never happen -
- // or for which we have already received onLost().
- return;
- }
- if (VDBG) {
- Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
- network, newNc));
- }
-
- final NetworkState prev = mNetworkMap.get(network);
- mNetworkMap.put(network,
- new NetworkState(null, prev.linkProperties, newNc,
- network, null, null));
- notifyTarget(EVENT_ON_CAPABILITIES, network);
- }
-
- private void handleLinkProp(Network network, LinkProperties newLp) {
- if (!mNetworkMap.containsKey(network)) {
- // Ignore updates for networks for which we have not yet
- // received onAvailable() - which should never happen -
- // or for which we have already received onLost().
- return;
- }
- if (VDBG) {
- Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
- network, newLp));
- }
-
- final NetworkState prev = mNetworkMap.get(network);
- mNetworkMap.put(network,
- new NetworkState(null, newLp, prev.networkCapabilities,
- network, null, null));
- notifyTarget(EVENT_ON_LINKPROPERTIES, network);
- }
-
- private void handleLost(Network network) {
- if (!mNetworkMap.containsKey(network)) {
- // Ignore updates for networks for which we have not yet
- // received onAvailable() - which should never happen -
- // or for which we have already received onLost().
- return;
- }
- if (VDBG) {
- Log.d(TAG, "EVENT_ON_LOST for " + network);
- }
- notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
- }
-
- // Fetch (and cache) a ConnectivityManager only if and when we need one.
- private ConnectivityManager cm() {
- if (mCM == null) {
- mCM = mContext.getSystemService(ConnectivityManager.class);
- }
- return mCM;
- }
-
- /**
- * A NetworkCallback class that relays information of interest to the
- * tethering master state machine thread for subsequent processing.
- */
- private class UpstreamNetworkCallback extends NetworkCallback {
- @Override
- public void onAvailable(Network network) {
- mTarget.getHandler().post(() -> handleAvailable(network));
- }
-
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
- mTarget.getHandler().post(() -> handleNetCap(network, newNc));
- }
-
- @Override
- public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
- mTarget.getHandler().post(() -> handleLinkProp(network, newLp));
- }
-
- @Override
- public void onLost(Network network) {
- mTarget.getHandler().post(() -> handleLost(network));
- }
- }
-
- private void releaseCallback(NetworkCallback cb) {
- if (cb != null) cm().unregisterNetworkCallback(cb);
- }
-
- private void notifyTarget(int which, Network network) {
- notifyTarget(which, mNetworkMap.get(network));
- }
-
- private void notifyTarget(int which, NetworkState netstate) {
- mTarget.sendMessage(mWhat, which, 0, netstate);
- }
- }
-
// Needed because the canonical source of upstream truth is just the
// upstream interface name, |mCurrentUpstreamIface|. This is ripe for
// future simplification, once the upstream Network is canonical.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index afc6247..a5876dd 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1586,9 +1586,6 @@
public void exit() {
// We assume that everything is reset after stopping the daemons.
interrupt();
- for (LocalSocket socket : mSockets) {
- IoUtils.closeQuietly(socket);
- }
agentDisconnect();
try {
mContext.unregisterReceiver(mBroadcastReceiver);
@@ -1601,8 +1598,26 @@
Log.v(TAG, "Waiting");
synchronized (TAG) {
Log.v(TAG, "Executing");
- execute();
- monitorDaemons();
+ try {
+ execute();
+ monitorDaemons();
+ interrupted(); // Clear interrupt flag if execute called exit.
+ } catch (InterruptedException e) {
+ } finally {
+ for (LocalSocket socket : mSockets) {
+ IoUtils.closeQuietly(socket);
+ }
+ // This sleep is necessary for racoon to successfully complete sending delete
+ // message to server.
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ }
+ for (String daemon : mDaemons) {
+ SystemService.stop(daemon);
+ }
+ }
+ agentDisconnect();
}
}
@@ -1801,18 +1816,6 @@
Log.i(TAG, "Aborting", e);
updateState(DetailedState.FAILED, e.getMessage());
exit();
- } finally {
- // Kill the daemons if they fail to stop.
- if (!initFinished) {
- for (String daemon : mDaemons) {
- SystemService.stop(daemon);
- }
- }
-
- // Do not leave an unstable state.
- if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
- agentDisconnect();
- }
}
}
@@ -1820,28 +1823,17 @@
* Monitor the daemons we started, moving to disconnected state if the
* underlying services fail.
*/
- private void monitorDaemons() {
+ private void monitorDaemons() throws InterruptedException{
if (!mNetworkInfo.isConnected()) {
return;
}
-
- try {
- while (true) {
- Thread.sleep(2000);
- for (int i = 0; i < mDaemons.length; i++) {
- if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
- return;
- }
+ while (true) {
+ Thread.sleep(2000);
+ for (int i = 0; i < mDaemons.length; i++) {
+ if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
+ return;
}
}
- } catch (InterruptedException e) {
- Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
- } finally {
- for (String daemon : mDaemons) {
- SystemService.stop(daemon);
- }
-
- agentDisconnect();
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
new file mode 100644
index 0000000..927dfd5
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -0,0 +1,287 @@
+/*
+ * 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.connectivity.tethering;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.NetworkState;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.StateMachine;
+
+import java.util.HashMap;
+
+
+/**
+ * A class to centralize all the network and link properties information
+ * pertaining to the current and any potential upstream network.
+ *
+ * Calling #start() registers two callbacks: one to track the system default
+ * network and a second to specifically observe TYPE_MOBILE_DUN networks.
+ *
+ * The methods and data members of this class are only to be accessed and
+ * modified from the tethering master state machine thread. Any other
+ * access semantics would necessitate the addition of locking.
+ *
+ * TODO: Move upstream selection logic here.
+ *
+ * @hide
+ */
+public class UpstreamNetworkMonitor {
+ private static final String TAG = UpstreamNetworkMonitor.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final boolean VDBG = false;
+
+ public static final int EVENT_ON_AVAILABLE = 1;
+ public static final int EVENT_ON_CAPABILITIES = 2;
+ public static final int EVENT_ON_LINKPROPERTIES = 3;
+ public static final int EVENT_ON_LOST = 4;
+
+ private final Context mContext;
+ private final StateMachine mTarget;
+ private final int mWhat;
+ private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
+ private ConnectivityManager mCM;
+ private NetworkCallback mDefaultNetworkCallback;
+ private NetworkCallback mDunTetheringCallback;
+ private NetworkCallback mMobileNetworkCallback;
+ private boolean mDunRequired;
+
+ public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
+ mContext = ctx;
+ mTarget = tgt;
+ mWhat = what;
+ }
+
+ @VisibleForTesting
+ public UpstreamNetworkMonitor(StateMachine tgt, int what, ConnectivityManager cm) {
+ this(null, tgt, what);
+ mCM = cm;
+ }
+
+ public void start() {
+ stop();
+
+ mDefaultNetworkCallback = new UpstreamNetworkCallback();
+ cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
+
+ final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+ .build();
+ mDunTetheringCallback = new UpstreamNetworkCallback();
+ cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
+ }
+
+ public void stop() {
+ releaseMobileNetworkRequest();
+
+ releaseCallback(mDefaultNetworkCallback);
+ mDefaultNetworkCallback = null;
+
+ releaseCallback(mDunTetheringCallback);
+ mDunTetheringCallback = null;
+
+ mNetworkMap.clear();
+ }
+
+ public void mobileUpstreamRequiresDun(boolean dunRequired) {
+ final boolean valueChanged = (mDunRequired != dunRequired);
+ mDunRequired = dunRequired;
+ if (valueChanged && mobileNetworkRequested()) {
+ releaseMobileNetworkRequest();
+ registerMobileNetworkRequest();
+ }
+ }
+
+ public boolean mobileNetworkRequested() {
+ return (mMobileNetworkCallback != null);
+ }
+
+ public void registerMobileNetworkRequest() {
+ if (mMobileNetworkCallback != null) return;
+
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ if (mDunRequired) {
+ builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
+ } else {
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+ final NetworkRequest mobileUpstreamRequest = builder.build();
+
+ // The existing default network and DUN callbacks will be notified.
+ // Therefore, to avoid duplicate notifications, we only register a no-op.
+ mMobileNetworkCallback = new NetworkCallback();
+
+ // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
+ // moderate callback time (once timeout callbacks are implemented). This might
+ // be useful for updating some UI. Additionally, we should definitely log a
+ // message to aid in any subsequent debugging
+ if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
+
+ cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback);
+ }
+
+ public void releaseMobileNetworkRequest() {
+ if (mMobileNetworkCallback == null) return;
+
+ cm().unregisterNetworkCallback(mMobileNetworkCallback);
+ mMobileNetworkCallback = null;
+ }
+
+ public NetworkState lookup(Network network) {
+ return (network != null) ? mNetworkMap.get(network) : null;
+ }
+
+ private void handleAvailable(Network network) {
+ if (VDBG) {
+ Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
+ }
+ if (!mNetworkMap.containsKey(network)) {
+ mNetworkMap.put(network,
+ new NetworkState(null, null, null, network, null, null));
+ }
+
+ final ConnectivityManager cm = cm();
+
+ if (mDefaultNetworkCallback != null) {
+ cm.requestNetworkCapabilities(mDefaultNetworkCallback);
+ cm.requestLinkProperties(mDefaultNetworkCallback);
+ }
+
+ // Requesting updates for mDunTetheringCallback is not
+ // necessary. Because it's a listen, it will already have
+ // heard all NetworkCapabilities and LinkProperties updates
+ // since UpstreamNetworkMonitor was started. Because we
+ // start UpstreamNetworkMonitor before chooseUpstreamType()
+ // is ever invoked (it can register a DUN request) this is
+ // mostly safe. However, if a DUN network is already up for
+ // some reason (unlikely, because DUN is restricted and,
+ // unless the DUN network is shared with another APN, only
+ // the system can request it and this is the only part of
+ // the system that requests it) we won't know its
+ // LinkProperties or NetworkCapabilities.
+
+ notifyTarget(EVENT_ON_AVAILABLE, network);
+ }
+
+ private void handleNetCap(Network network, NetworkCapabilities newNc) {
+ if (!mNetworkMap.containsKey(network)) {
+ // Ignore updates for networks for which we have not yet
+ // received onAvailable() - which should never happen -
+ // or for which we have already received onLost().
+ return;
+ }
+ if (VDBG) {
+ Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
+ network, newNc));
+ }
+
+ final NetworkState prev = mNetworkMap.get(network);
+ mNetworkMap.put(network,
+ new NetworkState(null, prev.linkProperties, newNc,
+ network, null, null));
+ notifyTarget(EVENT_ON_CAPABILITIES, network);
+ }
+
+ private void handleLinkProp(Network network, LinkProperties newLp) {
+ if (!mNetworkMap.containsKey(network)) {
+ // Ignore updates for networks for which we have not yet
+ // received onAvailable() - which should never happen -
+ // or for which we have already received onLost().
+ return;
+ }
+ if (VDBG) {
+ Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
+ network, newLp));
+ }
+
+ final NetworkState prev = mNetworkMap.get(network);
+ mNetworkMap.put(network,
+ new NetworkState(null, newLp, prev.networkCapabilities,
+ network, null, null));
+ notifyTarget(EVENT_ON_LINKPROPERTIES, network);
+ }
+
+ private void handleLost(Network network) {
+ if (!mNetworkMap.containsKey(network)) {
+ // Ignore updates for networks for which we have not yet
+ // received onAvailable() - which should never happen -
+ // or for which we have already received onLost().
+ return;
+ }
+ if (VDBG) {
+ Log.d(TAG, "EVENT_ON_LOST for " + network);
+ }
+ notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
+ }
+
+ // Fetch (and cache) a ConnectivityManager only if and when we need one.
+ private ConnectivityManager cm() {
+ if (mCM == null) {
+ mCM = mContext.getSystemService(ConnectivityManager.class);
+ }
+ return mCM;
+ }
+
+ /**
+ * A NetworkCallback class that relays information of interest to the
+ * tethering master state machine thread for subsequent processing.
+ */
+ private class UpstreamNetworkCallback extends NetworkCallback {
+ @Override
+ public void onAvailable(Network network) {
+ mTarget.getHandler().post(() -> handleAvailable(network));
+ }
+
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
+ mTarget.getHandler().post(() -> handleNetCap(network, newNc));
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
+ mTarget.getHandler().post(() -> handleLinkProp(network, newLp));
+ }
+
+ @Override
+ public void onLost(Network network) {
+ mTarget.getHandler().post(() -> handleLost(network));
+ }
+ }
+
+ private void releaseCallback(NetworkCallback cb) {
+ if (cb != null) cm().unregisterNetworkCallback(cb);
+ }
+
+ private void notifyTarget(int which, Network network) {
+ notifyTarget(which, mNetworkMap.get(network));
+ }
+
+ private void notifyTarget(int which, NetworkState netstate) {
+ mTarget.sendMessage(mWhat, which, 0, netstate);
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 7c5550a..d81e092 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -796,7 +796,7 @@
void onNewAvrAdded(HdmiDeviceInfo avr) {
assertRunOnServiceThread();
addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
- if (isArcFeatureEnabled(avr.getPortId())
+ if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
&& !hasAction(SetArcTransmissionStateAction.class)) {
startArcAction(true);
}
@@ -900,29 +900,6 @@
}
@ServiceThreadOnly
- private void updateArcFeatureStatus(int portId, boolean isConnected) {
- assertRunOnServiceThread();
- HdmiPortInfo portInfo = mService.getPortInfo(portId);
- if (!portInfo.isArcSupported()) {
- return;
- }
- HdmiDeviceInfo avr = getAvrDeviceInfo();
- if (avr == null) {
- if (isConnected) {
- // Update the status (since TV may not have seen AVR yet) so
- // that ARC can be initiated after discovery.
- mArcFeatureEnabled.put(portId, isConnected);
- }
- return;
- }
- // HEAC 2.4, HEACT 5-15
- // Should not activate ARC if +5V status is false.
- if (avr.getPortId() == portId) {
- changeArcFeatureEnabled(portId, isConnected);
- }
- }
-
- @ServiceThreadOnly
boolean isConnected(int portId) {
assertRunOnServiceThread();
return mService.isConnected(portId);
@@ -952,18 +929,18 @@
@ServiceThreadOnly
void changeArcFeatureEnabled(int portId, boolean enabled) {
assertRunOnServiceThread();
-
- if (mArcFeatureEnabled.get(portId) != enabled) {
- mArcFeatureEnabled.put(portId, enabled);
- if (enabled) {
- if (!mArcEstablished) {
- startArcAction(true);
- }
- } else {
- if (mArcEstablished) {
- startArcAction(false);
- }
- }
+ if (mArcFeatureEnabled.get(portId) == enabled) {
+ return;
+ }
+ mArcFeatureEnabled.put(portId, enabled);
+ HdmiDeviceInfo avr = getAvrDeviceInfo();
+ if (avr == null || avr.getPortId() != portId) {
+ return;
+ }
+ if (enabled && !mArcEstablished) {
+ startArcAction(true);
+ } else if (!enabled && mArcEstablished) {
+ startArcAction(false);
}
}
@@ -1097,14 +1074,14 @@
return true;
}
- private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) {
+ private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
HdmiDeviceInfo avr = getAvrDeviceInfo();
if (avr != null
&& (avrAddress == avr.getLogicalAddress())
&& isConnectedToArcPort(avr.getPhysicalAddress())
&& isDirectConnectAddress(avr.getPhysicalAddress())) {
- if (shouldCheckArcFeatureEnabled) {
- return isArcFeatureEnabled(avr.getPortId());
+ if (enabled) {
+ return isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId());
} else {
return true;
}
@@ -1566,7 +1543,6 @@
// It covers seq #40, #43.
hotplugActions.get(0).pollAllDevicesNow();
}
- updateArcFeatureStatus(portId, connected);
}
private void removeCecSwitches(int portId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7e47a11..7362a51 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -576,7 +576,7 @@
/**
* Whether the package parser cache is enabled.
*/
- private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = false;
+ private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true;
final ServiceThread mHandlerThread;
@@ -2842,7 +2842,12 @@
return null;
}
- if (SystemProperties.getBoolean("ro.boot.disable_package_cache", false)) {
+ // Disable package parsing on eng builds to allow for faster incremental development.
+ if ("eng".equals(Build.TYPE)) {
+ return null;
+ }
+
+ if (SystemProperties.getBoolean("pm.boot.disable_package_cache", false)) {
Slog.i(TAG, "Disabling package parser cache due to system property.");
return null;
}
@@ -2861,9 +2866,33 @@
FileUtils.deleteContents(cacheBaseDir);
}
+
// Return the versioned package cache directory. This is something like
// "/data/system/package_cache/1"
- return FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
+ File cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
+
+ // The following is a workaround to aid development on non-numbered userdebug
+ // builds or cases where "adb sync" is used on userdebug builds. If we detect that
+ // the system partition is newer.
+ //
+ // NOTE: When no BUILD_NUMBER is set by the build system, it defaults to a build
+ // that starts with "eng." to signify that this is an engineering build and not
+ // destined for release.
+ if ("userdebug".equals(Build.TYPE) && Build.VERSION.INCREMENTAL.startsWith("eng.")) {
+ Slog.w(TAG, "Wiping cache directory because the system partition changed.");
+
+ // Heuristic: If the /system directory has been modified recently due to an "adb sync"
+ // or a regular make, then blow away the cache. Note that mtimes are *NOT* reliable
+ // in general and should not be used for production changes. In this specific case,
+ // we know that they will work.
+ File frameworkDir = new File(Environment.getRootDirectory(), "framework");
+ if (cacheDir.lastModified() < frameworkDir.lastModified()) {
+ FileUtils.deleteContents(cacheBaseDir);
+ cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
+ }
+ }
+
+ return cacheDir;
}
@Override
@@ -10610,12 +10639,30 @@
int flags = permissionState != null
? permissionState.getFlags() : 0;
if (origPermissions.hasRuntimePermission(bp.name, userId)) {
- if (permissionsState.grantRuntimePermission(bp, userId) ==
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- // If we cannot put the permission as it was, we have to write.
+ // Don't propagate the permission in a permission review mode if
+ // the former was revoked, i.e. marked to not propagate on upgrade.
+ // Note that in a permission review mode install permissions are
+ // represented as constantly granted runtime ones since we need to
+ // keep a per user state associated with the permission. Also the
+ // revoke on upgrade flag is no longer applicable and is reset.
+ final boolean revokeOnUpgrade = (flags & PackageManager
+ .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+ if (revokeOnUpgrade) {
+ flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ // Since we changed the flags, we have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
+ if (!mPermissionReviewRequired || !revokeOnUpgrade) {
+ if (permissionsState.grantRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // If we cannot put the permission as it was,
+ // we have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
+ }
+
// If the app supports runtime permissions no need for a review.
if (mPermissionReviewRequired
&& appSupportsRuntimePermissions
@@ -12989,8 +13036,10 @@
+ " is not installer for " + packageName);
}
- ps.categoryHint = categoryHint;
- scheduleWriteSettingsLocked();
+ if (ps.categoryHint != categoryHint) {
+ ps.categoryHint = categoryHint;
+ scheduleWriteSettingsLocked();
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 9b2d3c6b..a900702 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -617,7 +617,7 @@
}
return dc.screenshotApplications(mToken.asBinder(), width, height,
false /* includeFullDisplay */, frameScale, Bitmap.Config.RGB_565,
- false /* wallpaperOnly */, false /* includeDecor */, true /* toAshmem */);
+ false /* wallpaperOnly */, false /* includeDecor */);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 66267bd..73bf3dc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -94,6 +94,7 @@
import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -2105,12 +2106,55 @@
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
* @param includeDecor whether to include window decors, like the status or navigation bar
* background of the window
- * @param toAshmem whether to convert the resulting bitmap to ashmem; this should be set to
- * true if the Bitmap is sent over binder, and false otherwise
*/
Bitmap screenshotApplications(IBinder appToken, int width, int height,
boolean includeFullDisplay, float frameScale, Bitmap.Config config,
- boolean wallpaperOnly, boolean includeDecor, boolean toAshmem) {
+ boolean wallpaperOnly, boolean includeDecor) {
+ Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay,
+ frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot);
+
+ if (DEBUG_SCREENSHOT) {
+ // TEST IF IT's ALL BLACK
+ int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()];
+ bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
+ bitmap.getHeight());
+ boolean allBlack = true;
+ final int firstColor = buffer[0];
+ for (int i = 0; i < buffer.length; i++) {
+ if (buffer[i] != firstColor) {
+ allBlack = false;
+ break;
+ }
+ }
+ if (allBlack) {
+ final WindowState appWin = mScreenshotApplicationState.appWin;
+ final int maxLayer = mScreenshotApplicationState.maxLayer;
+ final int minLayer = mScreenshotApplicationState.minLayer;
+ Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
+ Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
+ (appWin != null ?
+ appWin.mWinAnimator.mSurfaceController.getLayer() : "null") +
+ " minLayer=" + minLayer + " maxLayer=" + maxLayer);
+ }
+ }
+
+ // Create a copy of the screenshot that is immutable and backed in ashmem.
+ // This greatly reduces the overhead of passing the bitmap between processes.
+ Bitmap ret = bitmap.createAshmemBitmap(config);
+ bitmap.recycle();
+ return ret;
+ }
+
+ GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height,
+ boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
+ boolean includeDecor) {
+ return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale,
+ wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer);
+ }
+
+ private <E> E screenshotApplications(IBinder appToken, int width, int height,
+ boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
+ boolean includeDecor, Screenshoter<E> screenshoter) {
int dw = mDisplayInfo.logicalWidth;
int dh = mDisplayInfo.logicalHeight;
if (dw == 0 || dh == 0) {
@@ -2119,7 +2163,7 @@
return null;
}
- Bitmap bm = null;
+ E bitmap;
mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
final Rect frame = new Rect();
@@ -2327,48 +2371,15 @@
SurfaceControl.openTransaction();
SurfaceControl.closeTransactionSync();
- bm = SurfaceControl.screenshot(crop, width, height, minLayer, maxLayer,
+ bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer,
inRotation, rot);
- if (bm == null) {
+ if (bitmap == null) {
Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh
+ ") to layer " + maxLayer);
return null;
}
}
-
- if (DEBUG_SCREENSHOT) {
- // TEST IF IT's ALL BLACK
- int[] buffer = new int[bm.getWidth() * bm.getHeight()];
- bm.getPixels(buffer, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight());
- boolean allBlack = true;
- final int firstColor = buffer[0];
- for (int i = 0; i < buffer.length; i++) {
- if (buffer[i] != firstColor) {
- allBlack = false;
- break;
- }
- }
- if (allBlack) {
- final WindowState appWin = mScreenshotApplicationState.appWin;
- final int maxLayer = mScreenshotApplicationState.maxLayer;
- final int minLayer = mScreenshotApplicationState.minLayer;
- Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
- Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
- (appWin != null ?
- appWin.mWinAnimator.mSurfaceController.getLayer() : "null") +
- " minLayer=" + minLayer + " maxLayer=" + maxLayer);
- }
- }
-
- // Create a copy of the screenshot that is immutable and backed in ashmem.
- // This greatly reduces the overhead of passing the bitmap between processes.
- if (toAshmem) {
- Bitmap ret = bm.createAshmemBitmap(config);
- bm.recycle();
- return ret;
- } else {
- return bm;
- }
+ return bitmap;
}
// TODO: Can this use createRotationMatrix()?
@@ -2721,4 +2732,13 @@
return mName;
}
}
+
+ /**
+ * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}.
+ */
+ @FunctionalInterface
+ private interface Screenshoter<E> {
+ E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean useIdentityTransform, int rotation);
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 4421d61..68aceae 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -96,17 +96,11 @@
if (top == null) {
return null;
}
- final Bitmap bmp = top.mDisplayContent.screenshotApplications(top.token, -1, -1, false,
- 1.0f, ARGB_8888, false, true, false);
- if (bmp == null) {
+ final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
+ -1, -1, false, 1.0f, false, true);
+ if (buffer == null) {
return null;
}
- // TODO: Already use a GraphicBuffer when snapshotting the content.
- final GraphicBuffer buffer = GraphicBuffer.create(bmp.getWidth(), bmp.getHeight(),
- RGBA_8888, USAGE_HW_TEXTURE | USAGE_SW_WRITE_NEVER | USAGE_SW_READ_NEVER);
- final Canvas c = buffer.lockCanvas();
- c.drawBitmap(bmp, 0, 0, null);
- buffer.unlockCanvasAndPost(c);
return new TaskSnapshot(buffer, top.getConfiguration().orientation,
top.findMainWindow().mStableInsets);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 195d4c3..7cc77de 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3862,8 +3862,7 @@
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
-1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
- Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */,
- true /* toAshmem */);
+ Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
@@ -3885,7 +3884,7 @@
Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY,
-1 /* width */, -1 /* height */, true /* includeFullDisplay */,
1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
- false /* includeDecor */, true /* toAshmem */);
+ false /* includeDecor */);
try {
receiver.send(bm);
} catch (RemoteException e) {
@@ -3908,12 +3907,10 @@
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
* @param includeDecor whether to include window decors, like the status or navigation bar
* background of the window
- * @param toAshmem whether to convert the resulting bitmap to ashmem; this should be set to
- * true if the Bitmap is sent over binder, and false otherwise
*/
private Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config,
- boolean wallpaperOnly, boolean includeDecor, boolean toAshmem) {
+ boolean wallpaperOnly, boolean includeDecor) {
final DisplayContent displayContent;
synchronized(mWindowMap) {
displayContent = mRoot.getDisplayContentOrCreate(displayId);
@@ -3924,7 +3921,7 @@
}
}
return displayContent.screenshotApplications(appToken, width, height,
- includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor, toAshmem);
+ includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor);
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bdd1a0f51..040188d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.devicepolicy;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
-import static android.app.admin.DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG;
import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
@@ -49,7 +48,6 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accounts.Account;
import android.accounts.AccountManager;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -180,8 +178,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -337,7 +333,8 @@
private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h
/**
- * Strings logged with {@link #PROVISIONING_ENTRY_POINT_ADB}.
+ * Strings logged with {@link
+ * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}.
*/
private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
@@ -552,11 +549,25 @@
}
if (Intent.ACTION_USER_ADDED.equals(action)) {
sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle);
- disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
+ synchronized (DevicePolicyManagerService.this) {
+ // It might take a while for the user to become affiliated. Make security
+ // and network logging unavailable in the meantime.
+ maybePauseDeviceWideLoggingLocked();
+ }
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_REMOVED, userHandle);
- disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
- removeUserData(userHandle);
+ synchronized (DevicePolicyManagerService.this) {
+ // Check whether the user is affiliated, *before* removing its data.
+ boolean isRemovedUserAffiliated = isUserAffiliatedWithDeviceLocked(userHandle);
+ removeUserData(userHandle);
+ if (!isRemovedUserAffiliated) {
+ // We discard the logs when unaffiliated users are deleted (so that the
+ // device owner cannot retrieve data about that user after it's gone).
+ discardDeviceWideLogsLocked();
+ // Resume logging if all remaining users are affiliated.
+ maybeResumeDeviceWideLoggingLocked();
+ }
+ }
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
synchronized (DevicePolicyManagerService.this) {
// Reset the policy data
@@ -1858,9 +1869,10 @@
if (mOwners.hasDeviceOwner()) {
mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true");
Slog.i(LOG_TAG, "Set ro.device_owner property to true");
- disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
+
if (mInjector.securityLogGetLoggingEnabledProperty()) {
mSecurityLogMonitor.start();
+ maybePauseDeviceWideLoggingLocked();
}
} else {
mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "false");
@@ -3155,7 +3167,7 @@
}
// It's temporary solution to clear DISALLOW_ADD_USER after CTS
- // TODO: b/31952368 when the restriction is moved from system to the device owner,
+ // STOPSHIP(b/31952368) when the restriction is moved from system to the device owner,
// it can be removed.
private void clearDeviceOwnerUserRestrictionLocked(UserHandle userHandle) {
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
@@ -5650,34 +5662,12 @@
}
}
- private boolean isDeviceOwnerManagedSingleUserDevice() {
- synchronized (this) {
- if (!mOwners.hasDeviceOwner()) {
- return false;
- }
- }
- final long callingIdentity = mInjector.binderClearCallingIdentity();
- try {
- if (mInjector.userManagerIsSplitSystemUser()) {
- // In split system user mode, only allow the case where the device owner is managing
- // the only non-system user of the device
- return (mUserManager.getUserCount() == 2
- && mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM);
- } else {
- return mUserManager.getUserCount() == 1;
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(callingIdentity);
- }
- }
-
- private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException {
+ private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException {
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
- }
- if (!isDeviceOwnerManagedSingleUserDevice()) {
- throw new SecurityException(
- "There should only be one user, managed by Device Owner");
+ if (!areAllUsersAffiliatedWithDeviceLocked()) {
+ throw new SecurityException("Not all users are affiliated.");
+ }
}
}
@@ -5687,7 +5677,11 @@
return false;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- ensureDeviceOwnerManagingSingleUser(who);
+
+ // TODO: If an unaffiliated user is removed, the admin will be able to request a bugreport
+ // which could still contain data related to that user. Should we disallow that, e.g. until
+ // next boot? Might not be needed given that this still requires user consent.
+ ensureDeviceOwnerAndAllUsersAffiliated(who);
if (mRemoteBugreportServiceIsActive.get()
|| (getDeviceOwnerRemoteBugreportUri() != null)) {
@@ -5712,7 +5706,8 @@
mRemoteBugreportServiceIsActive.set(true);
mRemoteBugreportSharingAccepted.set(false);
registerRemoteBugreportReceivers();
- mInjector.getNotificationManager().notifyAsUser(LOG_TAG, RemoteBugreportUtils.NOTIFICATION_ID,
+ mInjector.getNotificationManager().notifyAsUser(LOG_TAG,
+ RemoteBugreportUtils.NOTIFICATION_ID,
RemoteBugreportUtils.buildNotification(mContext,
DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL);
mHandler.postDelayed(mRemoteBugreportTimeoutRunnable,
@@ -6259,6 +6254,7 @@
admin.userRestrictions = null;
admin.defaultEnabledRestrictionsAlreadySet.clear();
admin.forceEphemeralUsers = false;
+ admin.isNetworkLoggingEnabled = false;
mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers);
final DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
policyData.mLastSecurityLogRetrievalTime = -1;
@@ -6271,7 +6267,11 @@
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
updateDeviceOwnerLocked();
- disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
+
+ mInjector.securityLogSetLoggingEnabledProperty(false);
+ mSecurityLogMonitor.stop();
+ setNetworkLoggingActiveInternal(false);
+
try {
if (mInjector.getIBackupManager() != null) {
// Reactivate backup service.
@@ -8261,7 +8261,7 @@
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
final int userHandle = mInjector.userHandleGetCallingUserId();
- if (isUserAffiliatedWithDevice(userHandle)) {
+ if (isUserAffiliatedWithDeviceLocked(userHandle)) {
setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
} else {
throw new SecurityException("Admin " + who +
@@ -9442,6 +9442,12 @@
getUserData(UserHandle.USER_SYSTEM).mAffiliationIds = affiliationIds;
saveSettingsLocked(UserHandle.USER_SYSTEM);
}
+
+ // Affiliation status for any user, not just the calling user, might have changed.
+ // The device owner user will still be affiliated after changing its affiliation ids,
+ // but as a result of that other users might become affiliated or un-affiliated.
+ maybePauseDeviceWideLoggingLocked();
+ maybeResumeDeviceWideLoggingLocked();
}
}
@@ -9461,84 +9467,78 @@
@Override
public boolean isAffiliatedUser() {
- return isUserAffiliatedWithDevice(mInjector.userHandleGetCallingUserId());
+ if (!mHasFeature) {
+ return false;
+ }
+
+ synchronized (this) {
+ return isUserAffiliatedWithDeviceLocked(mInjector.userHandleGetCallingUserId());
+ }
}
- private boolean isUserAffiliatedWithDevice(int userId) {
- synchronized (this) {
- if (!mOwners.hasDeviceOwner()) {
- return false;
- }
- if (userId == mOwners.getDeviceOwnerUserId()) {
- // The user that the DO is installed on is always affiliated with the device.
+ private boolean isUserAffiliatedWithDeviceLocked(int userId) {
+ if (!mOwners.hasDeviceOwner()) {
+ return false;
+ }
+ if (userId == mOwners.getDeviceOwnerUserId()) {
+ // The user that the DO is installed on is always affiliated with the device.
+ return true;
+ }
+ if (userId == UserHandle.USER_SYSTEM) {
+ // The system user is always affiliated in a DO device, even if the DO is set on a
+ // different user. This could be the case if the DO is set in the primary user
+ // of a split user device.
+ return true;
+ }
+ final ComponentName profileOwner = getProfileOwner(userId);
+ if (profileOwner == null) {
+ return false;
+ }
+ final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds;
+ final Set<String> deviceAffiliationIds =
+ getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
+ for (String id : userAffiliationIds) {
+ if (deviceAffiliationIds.contains(id)) {
return true;
}
- if (userId == UserHandle.USER_SYSTEM) {
- // The system user is always affiliated in a DO device, even if the DO is set on a
- // different user. This could be the case if the DO is set in the primary user
- // of a split user device.
- return true;
- }
- final ComponentName profileOwner = getProfileOwner(userId);
- if (profileOwner == null) {
- return false;
- }
- final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds;
- final Set<String> deviceAffiliationIds =
- getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
- for (String id : userAffiliationIds) {
- if (deviceAffiliationIds.contains(id)) {
- return true;
- }
- }
}
return false;
}
- private synchronized void disableDeviceOwnerManagedSingleUserFeaturesIfNeeded() {
- final boolean isSingleUserManagedDevice = isDeviceOwnerManagedSingleUserDevice();
-
- // disable security logging if needed
- if (!isSingleUserManagedDevice) {
- mInjector.securityLogSetLoggingEnabledProperty(false);
- Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user managed"
- + " device.");
- }
-
- // disable backup service if needed
- // note: when clearing DO, the backup service shouldn't be disabled if it was enabled by
- // the device owner
- if (mOwners.hasDeviceOwner() && !isSingleUserManagedDevice) {
- setBackupServiceEnabledInternal(false);
- Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user.");
- }
-
- // disable network logging if needed
- if (!isSingleUserManagedDevice) {
- setNetworkLoggingActiveInternal(false);
- Slog.w(LOG_TAG, "Network logging turned off as it's no longer a single user managed"
- + " device.");
- // if there still is a device owner, disable logging policy, otherwise the admin
- // has been nuked
- if (mOwners.hasDeviceOwner()) {
- getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false;
- saveSettingsLocked(mOwners.getDeviceOwnerUserId());
+ private boolean areAllUsersAffiliatedWithDeviceLocked() {
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ final List<UserInfo> userInfos = mUserManager.getUsers();
+ for (int i = 0; i < userInfos.size(); i++) {
+ int userId = userInfos.get(i).id;
+ if (!isUserAffiliatedWithDeviceLocked(userId)) {
+ Slog.d(LOG_TAG, "User id " + userId + " not affiliated.");
+ return false;
+ }
}
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
}
+
+ return true;
}
@Override
public void setSecurityLoggingEnabled(ComponentName admin, boolean enabled) {
+ if (!mHasFeature) {
+ return;
+ }
Preconditions.checkNotNull(admin);
- ensureDeviceOwnerManagingSingleUser(admin);
synchronized (this) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
return;
}
mInjector.securityLogSetLoggingEnabledProperty(enabled);
if (enabled) {
mSecurityLogMonitor.start();
+ maybePauseDeviceWideLoggingLocked();
} else {
mSecurityLogMonitor.stop();
}
@@ -9547,6 +9547,10 @@
@Override
public boolean isSecurityLoggingEnabled(ComponentName admin) {
+ if (!mHasFeature) {
+ return false;
+ }
+
Preconditions.checkNotNull(admin);
synchronized (this) {
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
@@ -9565,10 +9569,15 @@
@Override
public ParceledListSlice<SecurityEvent> retrievePreRebootSecurityLogs(ComponentName admin) {
- Preconditions.checkNotNull(admin);
- ensureDeviceOwnerManagingSingleUser(admin);
+ if (!mHasFeature) {
+ return null;
+ }
- if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs)) {
+ Preconditions.checkNotNull(admin);
+ ensureDeviceOwnerAndAllUsersAffiliated(admin);
+
+ if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs)
+ || !mInjector.securityLogGetLoggingEnabledProperty()) {
return null;
}
@@ -9586,8 +9595,16 @@
@Override
public ParceledListSlice<SecurityEvent> retrieveSecurityLogs(ComponentName admin) {
+ if (!mHasFeature) {
+ return null;
+ }
+
Preconditions.checkNotNull(admin);
- ensureDeviceOwnerManagingSingleUser(admin);
+ ensureDeviceOwnerAndAllUsersAffiliated(admin);
+
+ if (!mInjector.securityLogGetLoggingEnabledProperty()) {
+ return null;
+ }
recordSecurityLogRetrievalTime();
@@ -9794,18 +9811,21 @@
}
}
+ // TODO(b/22388012): When backup is available for secondary users and profiles, consider
+ // whether there are any privacy/security implications of enabling the backup service here
+ // if there are other users or profiles unmanaged or managed by a different entity (i.e. not
+ // affiliated).
@Override
public void setBackupServiceEnabled(ComponentName admin, boolean enabled) {
- Preconditions.checkNotNull(admin);
if (!mHasFeature) {
return;
}
- ensureDeviceOwnerManagingSingleUser(admin);
- setBackupServiceEnabledInternal(enabled);
- }
+ Preconditions.checkNotNull(admin);
+ synchronized (this) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
- private synchronized void setBackupServiceEnabledInternal(boolean enabled) {
- long ident = mInjector.binderClearCallingIdentity();
+ final long ident = mInjector.binderClearCallingIdentity();
try {
IBackupManager ibm = mInjector.getIBackupManager();
if (ibm != null) {
@@ -9906,7 +9926,7 @@
final boolean isCallerDeviceOwner = isDeviceOwner(callingOwner);
final boolean isCallerManagedProfile = isManagedProfile(callingUserId);
if ((!isCallerDeviceOwner && !isCallerManagedProfile)
- || !isUserAffiliatedWithDevice(callingUserId)) {
+ || !isUserAffiliatedWithDeviceLocked(callingUserId)) {
return targetUsers;
}
@@ -9926,7 +9946,7 @@
// Both must be the same package and be affiliated in order to bind.
if (callingOwnerPackage.equals(targetOwnerPackage)
- && isUserAffiliatedWithDevice(userId)) {
+ && isUserAffiliatedWithDeviceLocked(userId)) {
targetUsers.add(UserHandle.of(userId));
}
}
@@ -10024,7 +10044,7 @@
return;
}
Preconditions.checkNotNull(admin);
- ensureDeviceOwnerManagingSingleUser(admin);
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
if (enabled == isNetworkLoggingEnabledInternalLocked()) {
// already in the requested state
@@ -10051,10 +10071,10 @@
Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging"
+ " service not being available yet.");
}
+ maybePauseDeviceWideLoggingLocked();
sendNetworkLoggingNotificationLocked();
} else {
if (mNetworkLogger != null && !mNetworkLogger.stopNetworkLogging()) {
- mNetworkLogger = null;
Slog.wtf(LOG_TAG, "Network logging could not be stopped due to the logging"
+ " service not being available yet.");
}
@@ -10066,6 +10086,44 @@
}
}
+ /** Pauses security and network logging if there are unaffiliated users on the device */
+ private void maybePauseDeviceWideLoggingLocked() {
+ if (!areAllUsersAffiliatedWithDeviceLocked()) {
+ Slog.i(LOG_TAG, "There are unaffiliated users, security and network logging will be "
+ + "paused if enabled.");
+ mSecurityLogMonitor.pause();
+ if (mNetworkLogger != null) {
+ mNetworkLogger.pause();
+ }
+ }
+ }
+
+ /** Resumes security and network logging (if they are enabled) if all users are affiliated */
+ private void maybeResumeDeviceWideLoggingLocked() {
+ if (areAllUsersAffiliatedWithDeviceLocked()) {
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ mSecurityLogMonitor.resume();
+ if (mNetworkLogger != null) {
+ mNetworkLogger.resume();
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ /** Deletes any security and network logs that might have been collected so far */
+ private void discardDeviceWideLogsLocked() {
+ mSecurityLogMonitor.discardLogs();
+ if (mNetworkLogger != null) {
+ mNetworkLogger.discardLogs();
+ }
+ // TODO: We should discard pre-boot security logs here too, as otherwise those
+ // logs (which might contain data from the user just removed) will be
+ // available after next boot.
+ }
+
@Override
public boolean isNetworkLoggingEnabled(ComponentName admin) {
if (!mHasFeature) {
@@ -10090,32 +10148,27 @@
* @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH
*/
@Override
- public synchronized List<NetworkEvent> retrieveNetworkLogs(ComponentName admin,
- long batchToken) {
+ public List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, long batchToken) {
if (!mHasFeature) {
return null;
}
Preconditions.checkNotNull(admin);
- ensureDeviceOwnerManagingSingleUser(admin);
+ ensureDeviceOwnerAndAllUsersAffiliated(admin);
- if (mNetworkLogger == null) {
- return null;
- }
-
- if (!isNetworkLoggingEnabledInternalLocked()) {
- return null;
- }
-
- final long currentTime = System.currentTimeMillis();
synchronized (this) {
+ if (mNetworkLogger == null
+ || !isNetworkLoggingEnabledInternalLocked()) {
+ return null;
+ }
+
+ final long currentTime = System.currentTimeMillis();
DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
if (currentTime > policyData.mLastNetworkLogsRetrievalTime) {
policyData.mLastNetworkLogsRetrievalTime = currentTime;
saveSettingsLocked(UserHandle.USER_SYSTEM);
}
+ return mNetworkLogger.retrieveLogs(batchToken);
}
-
- return mNetworkLogger.retrieveLogs(batchToken);
}
private void sendNetworkLoggingNotificationLocked() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index b82cb3c..0085931 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -31,7 +31,6 @@
import com.android.server.ServiceThread;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -130,6 +129,8 @@
Log.d(TAG, "Stopping network logging");
// stop the logging regardless of whether we fail to unregister listener
mIsLoggingEnabled.set(false);
+ discardLogs();
+
try {
if (!checkIpConnectivityMetricsService()) {
// the IIpConnectivityMetrics service should have been present at this point
@@ -140,9 +141,43 @@
return mIpConnectivityMetrics.unregisterNetdEventCallback();
} catch (RemoteException re) {
Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re);
- } finally {
- mHandlerThread.quitSafely();
return true;
+ } finally {
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ }
+ }
+ }
+
+ /**
+ * If logs are being collected, keep collecting them but stop notifying the device owner that
+ * new logs are available (since they cannot be retrieved)
+ */
+ void pause() {
+ if (mNetworkLoggingHandler != null) {
+ mNetworkLoggingHandler.pause();
+ }
+ }
+
+ /**
+ * If logs are being collected, start notifying the device owner when logs are ready to be
+ * collected again (if it was paused).
+ * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt
+ * to notify the device owner. Therefore calling identity should be cleared before calling it
+ * (in case the method is called from a user other than the DO's user).
+ */
+ void resume() {
+ if (mNetworkLoggingHandler != null) {
+ mNetworkLoggingHandler.resume();
+ }
+ }
+
+ /**
+ * Discard all collected logs.
+ */
+ void discardLogs() {
+ if (mNetworkLoggingHandler != null) {
+ mNetworkLoggingHandler.discardLogs();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index baa4c13..7d68412 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -55,10 +55,16 @@
@GuardedBy("this")
private ArrayList<NetworkEvent> mFullBatch;
- // each full batch is represented by its token, which the DPC has to provide back to revieve it
+ @GuardedBy("this")
+ private boolean mPaused = false;
+
+ // each full batch is represented by its token, which the DPC has to provide back to retrieve it
@GuardedBy("this")
private long mCurrentFullBatchToken;
+ @GuardedBy("this")
+ private long mLastRetrievedFullBatchToken;
+
NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
super(looper);
mDpm = dpm;
@@ -70,15 +76,19 @@
case LOG_NETWORK_EVENT_MSG: {
NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY);
if (networkEvent != null) {
- mNetworkEvents.add(networkEvent);
- if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
- finalizeBatchAndNotifyDeviceOwnerIfNotEmpty();
+ synchronized (NetworkLoggingHandler.this) {
+ mNetworkEvents.add(networkEvent);
+ if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
+ finalizeBatchAndNotifyDeviceOwnerLocked();
+ }
}
}
break;
}
case FINALIZE_BATCH_MSG: {
- finalizeBatchAndNotifyDeviceOwnerIfNotEmpty();
+ synchronized (NetworkLoggingHandler.this) {
+ finalizeBatchAndNotifyDeviceOwnerLocked();
+ }
break;
}
}
@@ -91,22 +101,49 @@
+ "ms from now.");
}
- private synchronized void finalizeBatchAndNotifyDeviceOwnerIfNotEmpty() {
+ synchronized void pause() {
+ Log.d(TAG, "Paused network logging");
+ mPaused = true;
+ }
+
+ synchronized void resume() {
+ if (!mPaused) {
+ Log.d(TAG, "Attempted to resume network logging, but logging is not paused.");
+ return;
+ }
+
+ Log.d(TAG, "Resumed network logging. Current batch="
+ + mCurrentFullBatchToken + ", LastRetrievedBatch=" + mLastRetrievedFullBatchToken);
+ mPaused = false;
+
+ // If there is a full batch ready that the device owner hasn't been notified about, do it
+ // now.
+ if (mFullBatch != null && mFullBatch.size() > 0
+ && mLastRetrievedFullBatchToken != mCurrentFullBatchToken) {
+ scheduleBatchFinalization();
+ notifyDeviceOwnerLocked();
+ }
+ }
+
+ synchronized void discardLogs() {
+ mFullBatch = null;
+ mNetworkEvents = new ArrayList<NetworkEvent>();
+ Log.d(TAG, "Discarded all network logs");
+ }
+
+ @GuardedBy("this")
+ private void finalizeBatchAndNotifyDeviceOwnerLocked() {
if (mNetworkEvents.size() > 0) {
// finalize the batch and start a new one from scratch
mFullBatch = mNetworkEvents;
mCurrentFullBatchToken++;
mNetworkEvents = new ArrayList<NetworkEvent>();
- // notify DO that there's a new non-empty batch waiting
- Bundle extras = new Bundle();
- extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
- extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
- Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
- + mCurrentFullBatchToken);
- mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
+ if (!mPaused) {
+ notifyDeviceOwnerLocked();
+ }
} else {
// don't notify the DO, since there are no events; DPC can still retrieve
- // the last full batch
+ // the last full batch if not paused.
Log.d(TAG, "Was about to finalize the batch, but there were no events to send to"
+ " the DPC, the batchToken of last available batch: "
+ mCurrentFullBatchToken);
@@ -115,10 +152,21 @@
scheduleBatchFinalization();
}
+ @GuardedBy("this")
+ private void notifyDeviceOwnerLocked() {
+ Bundle extras = new Bundle();
+ extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
+ extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
+ Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
+ + mCurrentFullBatchToken);
+ mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
+ }
+
synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) {
if (batchToken != mCurrentFullBatchToken) {
return null;
}
+ mLastRetrievedFullBatchToken = mCurrentFullBatchToken;
return mFullBatch;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 79702a8..18f06be 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -19,6 +19,7 @@
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
@@ -50,7 +51,7 @@
mService = service;
}
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = false; // STOPSHIP if true.
private static final String TAG = "SecurityLogMonitor";
/**
* Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N}
@@ -78,17 +79,25 @@
private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>();
@GuardedBy("mLock")
private boolean mAllowedToRetrieve = false;
- // When DO will be allowed to retrieves the log, in milliseconds.
+
+ /**
+ * When DO will be allowed to retrieve the log, in milliseconds since boot (as per
+ * {@link SystemClock#elapsedRealtime()})
+ */
@GuardedBy("mLock")
- private long mNextAllowedRetrivalTimeMillis = -1;
+ private long mNextAllowedRetrievalTimeMillis = -1;
+ @GuardedBy("mLock")
+ private boolean mPaused = false;
void start() {
+ Slog.i(TAG, "Starting security logging.");
mLock.lock();
try {
if (mMonitorThread == null) {
mPendingLogs = new ArrayList<SecurityEvent>();
mAllowedToRetrieve = false;
- mNextAllowedRetrivalTimeMillis = -1;
+ mNextAllowedRetrievalTimeMillis = -1;
+ mPaused = false;
mMonitorThread = new Thread(this);
mMonitorThread.start();
@@ -99,6 +108,7 @@
}
void stop() {
+ Slog.i(TAG, "Stopping security logging.");
mLock.lock();
try {
if (mMonitorThread != null) {
@@ -111,7 +121,8 @@
// Reset state and clear buffer
mPendingLogs = new ArrayList<SecurityEvent>();
mAllowedToRetrieve = false;
- mNextAllowedRetrivalTimeMillis = -1;
+ mNextAllowedRetrievalTimeMillis = -1;
+ mPaused = false;
mMonitorThread = null;
}
} finally {
@@ -120,6 +131,58 @@
}
/**
+ * If logs are being collected, keep collecting them but stop notifying the device owner that
+ * new logs are available (since they cannot be retrieved).
+ */
+ void pause() {
+ Slog.i(TAG, "Paused.");
+
+ mLock.lock();
+ mPaused = true;
+ mAllowedToRetrieve = false;
+ mLock.unlock();
+ }
+
+ /**
+ * If logs are being collected, start notifying the device owner when logs are ready to be
+ * retrieved again (if it was paused).
+ * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt
+ * to notify the device owner. Therefore calling identity should be cleared before calling it
+ * (in case the method is called from a user other than the DO's user).
+ */
+ void resume() {
+ mLock.lock();
+ try {
+ if (!mPaused) {
+ Log.d(TAG, "Attempted to resume, but logging is not paused.");
+ return;
+ }
+ mPaused = false;
+ mAllowedToRetrieve = false;
+ } finally {
+ mLock.unlock();
+ }
+
+ Slog.i(TAG, "Resumed.");
+ try {
+ notifyDeviceOwnerIfNeeded();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Thread interrupted.", e);
+ }
+ }
+
+ /**
+ * Discard all collected logs.
+ */
+ void discardLogs() {
+ mLock.lock();
+ mAllowedToRetrieve = false;
+ mPendingLogs = new ArrayList<SecurityEvent>();
+ mLock.unlock();
+ Slog.i(TAG, "Discarded all logs.");
+ }
+
+ /**
* Returns the new batch of logs since the last call to this method. Returns null if
* rate limit is exceeded.
*/
@@ -128,7 +191,7 @@
try {
if (mAllowedToRetrieve) {
mAllowedToRetrieve = false;
- mNextAllowedRetrivalTimeMillis = System.currentTimeMillis()
+ mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime()
+ RATE_LIMIT_INTERVAL_MILLISECONDS;
List<SecurityEvent> result = mPendingLogs;
mPendingLogs = new ArrayList<SecurityEvent>();
@@ -163,7 +226,7 @@
SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs);
}
if (!logs.isEmpty()) {
- if (DEBUG) Slog.d(TAG, "processing new logs");
+ if (DEBUG) Slog.d(TAG, "processing new logs. Events: " + logs.size());
mLock.lockInterruptibly();
try {
mPendingLogs.addAll(logs);
@@ -172,6 +235,7 @@
mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList(
mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
mPendingLogs.size()));
+ Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
}
} finally {
mLock.unlock();
@@ -188,7 +252,7 @@
break;
}
}
- if (DEBUG) Slog.d(TAG, "MonitorThread exit.");
+ Slog.i(TAG, "MonitorThread exit.");
}
private void notifyDeviceOwnerIfNeeded() throws InterruptedException {
@@ -196,15 +260,24 @@
boolean allowToRetrieveNow = false;
mLock.lockInterruptibly();
try {
+ if (mPaused) {
+ return;
+ }
+
+ // STOPSHIP(b/34186771): If the previous notification didn't reach the DO and logs were
+ // not retrieved (e.g. the broadcast was sent before the user was unlocked), no more
+ // subsequent callbacks will be sent. We should make sure that the DO gets notified
+ // before logs are lost.
int logSize = mPendingLogs.size();
if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL) {
// Allow DO to retrieve logs if too many pending logs
allowToRetrieveNow = true;
+ if (DEBUG) Slog.d(TAG, "Number of log entries over threshold: " + logSize);
} else if (logSize > 0) {
- if (mNextAllowedRetrivalTimeMillis == -1 ||
- System.currentTimeMillis() >= mNextAllowedRetrivalTimeMillis) {
+ if (SystemClock.elapsedRealtime() >= mNextAllowedRetrievalTimeMillis) {
// Rate limit reset
allowToRetrieveNow = true;
+ if (DEBUG) Slog.d(TAG, "Timeout reached");
}
}
shouldNotifyDO = (!mAllowedToRetrieve) && allowToRetrieveNow;
@@ -213,7 +286,7 @@
mLock.unlock();
}
if (shouldNotifyDO) {
- if (DEBUG) Slog.d(TAG, "notify DO");
+ Slog.i(TAG, "notify DO");
mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE,
null);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 68cb0c5..08fb591 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -175,6 +175,8 @@
"com.google.android.clockwork.bluetooth.WearBluetoothService";
private static final String WEAR_WIFI_MEDIATOR_SERVICE_CLASS =
"com.google.android.clockwork.wifi.WearWifiMediatorService";
+ private static final String WEAR_CELLULAR_MEDIATOR_SERVICE_CLASS =
+ "com.google.android.clockwork.cellular.WearCellularMediatorService";
private static final String WEAR_TIME_SERVICE_CLASS =
"com.google.android.clockwork.time.WearTimeService";
private static final String ACCOUNT_SERVICE_CLASS =
@@ -1381,6 +1383,13 @@
traceBeginAndSlog("StartWearWifiMediator");
mSystemServiceManager.startService(WEAR_WIFI_MEDIATOR_SERVICE_CLASS);
traceEnd();
+
+ if (SystemProperties.getBoolean("config.enable_cellmediator", false)) {
+ traceBeginAndSlog("StartWearCellularMediator");
+ mSystemServiceManager.startService(WEAR_CELLULAR_MEDIATOR_SERVICE_CLASS);
+ traceEnd();
+ }
+
if (!disableNonCoreServices) {
traceBeginAndSlog("StartWearTimeService");
mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index e68895e0..60f4360 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2845,7 +2845,10 @@
public void testGetLastSecurityLogRetrievalTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
- when(mContext.userManager.getUserCount()).thenReturn(1);
+
+ // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+ // feature is disabled because there are non-affiliated secondary users.
+ mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
when(mContext.resources.getBoolean(R.bool.config_supportPreRebootSecurityLogs))
.thenReturn(true);
@@ -2854,6 +2857,10 @@
// Enabling logging should not change the timestamp.
dpm.setSecurityLoggingEnabled(admin1, true);
+ verify(mContext.settings)
+ .securityLogSetLoggingEnabledProperty(true);
+ when(mContext.settings.securityLogGetLoggingEnabledProperty())
+ .thenReturn(true);
assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
// Retrieving the logs should update the timestamp.
@@ -2906,7 +2913,7 @@
public void testGetLastBugReportRequestTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
- when(mContext.userManager.getUserCount()).thenReturn(1);
+
mContext.packageName = admin1.getPackageName();
mContext.applicationInfo = new ApplicationInfo();
when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject()))
@@ -2914,6 +2921,10 @@
when(mContext.resources.getColor(eq(R.color.notification_material_background_color),
anyObject())).thenReturn(Color.WHITE);
+ // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+ // feature is disabled because there are non-affiliated secondary users.
+ mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
+
// No bug reports were requested so far.
assertEquals(-1, dpm.getLastBugReportRequestTime());
@@ -2951,7 +2962,16 @@
public void testGetLastNetworkLogRetrievalTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
- when(mContext.userManager.getUserCount()).thenReturn(1);
+ mContext.packageName = admin1.getPackageName();
+ mContext.applicationInfo = new ApplicationInfo();
+ when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject()))
+ .thenReturn(Color.WHITE);
+ when(mContext.resources.getColor(eq(R.color.notification_material_background_color),
+ anyObject())).thenReturn(Color.WHITE);
+
+ // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+ // feature is disabled because there are non-affiliated secondary users.
+ mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
when(mContext.iipConnectivityMetrics.registerNetdEventCallback(anyObject()))
.thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index a1b6769..44bf547 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -393,6 +393,18 @@
return dir;
}
+ public void removeUser(int userId) {
+ for (int i = 0; i < mUserInfos.size(); i++) {
+ if (mUserInfos.get(i).id == userId) {
+ mUserInfos.remove(i);
+ break;
+ }
+ }
+ when(userManager.getUserInfo(eq(userId))).thenReturn(null);
+
+ when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(false);
+ }
+
private UserInfo getUserInfo(int userId) {
for (UserInfo ui : mUserInfos) {
if (ui.id == userId) {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index ba770ef..d9f352c 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -188,7 +188,6 @@
String[] annotations = event.mContentAnnotations;
if (annotations != null) {
for (String annotation : annotations) {
- // TODO(kanlig): update with confidences of annotations.
stats.updateChooserCounts(event.mPackage, annotation, event.mAction);
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 861a1eb..eb838fa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -936,8 +936,10 @@
*
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
+ * @hide
*/
- /** {@hide} */
+ @SystemApi
public String getImei() {
return getImei(getDefaultSim());
}
@@ -949,8 +951,10 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*
* @param slotId of which deviceID is returned
+ *
+ * @hide
*/
- /** {@hide} */
+ @SystemApi
public String getImei(int slotId) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
diff --git a/services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java b/tests/net/java/android/net/util/BlockingSocketReaderTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java
rename to tests/net/java/android/net/util/BlockingSocketReaderTest.java
diff --git a/services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java
rename to tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index f9cac43..415911e 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -58,8 +58,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" default_network_event <",
" network_id <",
" network_id: 102",
@@ -89,8 +89,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" dhcp_event <",
" duration_ms: 192",
" if_name: \"wlan0\"",
@@ -112,8 +112,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" dhcp_event <",
" duration_ms: 0",
" if_name: \"wlan0\"",
@@ -137,8 +137,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" dns_lookup_batch <",
" event_types: 1",
" event_types: 1",
@@ -185,8 +185,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" ip_provisioning_event <",
" event_type: 1",
" if_name: \"wlan0\"",
@@ -208,8 +208,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" ip_reachability_event <",
" event_type: 512",
" if_name: \"wlan0\"",
@@ -231,8 +231,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" network_event <",
" event_type: 5",
" latency_ms: 20410",
@@ -258,8 +258,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" validation_probe_event <",
" latency_ms: 40730",
" network_id <",
@@ -287,8 +287,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" apf_program_event <",
" current_ras: 9",
" drop_multicast: true",
@@ -319,8 +319,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" apf_statistics <",
" dropped_ras: 2",
" duration_ms: 45000",
@@ -351,8 +351,8 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 1",
- " transport: 0",
" ra_event <",
" dnssl_lifetime: -1",
" prefix_preferred_lifetime: 300",
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index e9257fa..f56f3f8 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -158,16 +158,16 @@
String want = joinLines(
"dropped_events: 0",
"events <",
+ " link_layer: 0",
" time_ms: 100",
- " transport: 0",
" ip_reachability_event <",
" event_type: 512",
" if_name: \"wlan0\"",
" >",
">",
"events <",
+ " link_layer: 0",
" time_ms: 200",
- " transport: 0",
" dhcp_event <",
" duration_ms: 192",
" if_name: \"wlan0\"",
@@ -175,8 +175,8 @@
" >",
">",
"events <",
+ " link_layer: 0",
" time_ms: 300",
- " transport: 0",
" default_network_event <",
" network_id <",
" network_id: 102",
@@ -191,8 +191,8 @@
" >",
">",
"events <",
+ " link_layer: 0",
" time_ms: 400",
- " transport: 0",
" ip_provisioning_event <",
" event_type: 1",
" if_name: \"wlan0\"",
@@ -200,8 +200,8 @@
" >",
">",
"events <",
+ " link_layer: 0",
" time_ms: 500",
- " transport: 0",
" validation_probe_event <",
" latency_ms: 40730",
" network_id <",
@@ -212,8 +212,8 @@
" >",
">",
"events <",
+ " link_layer: 0",
" time_ms: 600",
- " transport: 0",
" apf_statistics <",
" dropped_ras: 2",
" duration_ms: 45000",
@@ -226,8 +226,8 @@
" >",
">",
"events <",
+ " link_layer: 0",
" time_ms: 700",
- " transport: 0",
" ra_event <",
" dnssl_lifetime: -1",
" prefix_preferred_lifetime: 300",
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 75d2f1a..cfd5598 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -213,8 +213,8 @@
IpConnectivityEvent got = events.get(0);
String want = String.join("\n",
+ "link_layer: 0",
"time_ms: 0",
- "transport: 0",
"connect_statistics <",
" connect_count: 12",
" errnos_counters <",
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 98073ce..21c2de7 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -80,6 +80,7 @@
when(mCtx.getResources()).thenReturn(mResources);
when(mCtx.getPackageManager()).thenReturn(mPm);
when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ when(mNetworkInfo.getExtraInfo()).thenReturn("extra");
when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
mManager = new NetworkNotificationManager(mCtx, mTelephonyManager, mNotificationManager);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
new file mode 100644
index 0000000..b2a9a49
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.connectivity.tethering;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.reset;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UpstreamNetworkMonitorTest {
+ private static final int EVENT_UNM_UPDATE = 1;
+
+ @Mock private Context mContext;
+ @Mock private IConnectivityManager mCS;
+
+ private TestConnectivityManager mCM;
+ private UpstreamNetworkMonitor mUNM;
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ reset(mContext);
+ reset(mCS);
+
+ mCM = new TestConnectivityManager(mContext, mCS);
+ mUNM = new UpstreamNetworkMonitor(null, EVENT_UNM_UPDATE, (ConnectivityManager) mCM);
+ }
+
+ @Test
+ public void testDoesNothingBeforeStarted() {
+ UpstreamNetworkMonitor unm = new UpstreamNetworkMonitor(null, null, EVENT_UNM_UPDATE);
+ assertFalse(unm.mobileNetworkRequested());
+ // Given a null Context, and therefore a null ConnectivityManager,
+ // these would cause an exception, if they actually attempted anything.
+ unm.mobileUpstreamRequiresDun(true);
+ unm.mobileUpstreamRequiresDun(false);
+ }
+
+ @Test
+ public void testDefaultNetworkIsTracked() throws Exception {
+ assertEquals(0, mCM.trackingDefault.size());
+
+ mUNM.start();
+ assertEquals(1, mCM.trackingDefault.size());
+
+ mUNM.stop();
+ assertTrue(mCM.isEmpty());
+ }
+
+ @Test
+ public void testListensForDunNetworks() throws Exception {
+ assertTrue(mCM.listening.isEmpty());
+
+ mUNM.start();
+ assertFalse(mCM.listening.isEmpty());
+ assertTrue(mCM.isListeningForDun());
+
+ mUNM.stop();
+ assertTrue(mCM.isEmpty());
+ }
+
+ @Test
+ public void testCanRequestMobileNetwork() throws Exception {
+ assertFalse(mUNM.mobileNetworkRequested());
+ assertEquals(0, mCM.requested.size());
+
+ mUNM.start();
+ assertFalse(mUNM.mobileNetworkRequested());
+ assertEquals(0, mCM.requested.size());
+
+ mUNM.mobileUpstreamRequiresDun(false);
+ assertFalse(mUNM.mobileNetworkRequested());
+ assertEquals(0, mCM.requested.size());
+
+ mUNM.registerMobileNetworkRequest();
+ assertTrue(mUNM.mobileNetworkRequested());
+ assertEquals(1, mCM.requested.size());
+ assertFalse(mCM.isDunRequested());
+
+ mUNM.stop();
+ assertFalse(mUNM.mobileNetworkRequested());
+ assertTrue(mCM.isEmpty());
+ }
+
+ @Test
+ public void testCanRequestDunNetwork() throws Exception {
+ assertFalse(mUNM.mobileNetworkRequested());
+ assertEquals(0, mCM.requested.size());
+
+ mUNM.start();
+ assertFalse(mUNM.mobileNetworkRequested());
+ assertEquals(0, mCM.requested.size());
+
+ mUNM.mobileUpstreamRequiresDun(true);
+ assertFalse(mUNM.mobileNetworkRequested());
+ assertEquals(0, mCM.requested.size());
+
+ mUNM.registerMobileNetworkRequest();
+ assertTrue(mUNM.mobileNetworkRequested());
+ assertEquals(1, mCM.requested.size());
+ assertTrue(mCM.isDunRequested());
+
+ mUNM.stop();
+ assertFalse(mUNM.mobileNetworkRequested());
+ assertTrue(mCM.isEmpty());
+ }
+
+ private static class TestConnectivityManager extends ConnectivityManager {
+ public Set<NetworkCallback> trackingDefault = new HashSet<>();
+ public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
+ public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
+
+ public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
+ super(ctx, svc);
+ }
+
+ boolean isEmpty() {
+ return trackingDefault.isEmpty() &&
+ listening.isEmpty() &&
+ requested.isEmpty();
+ }
+
+ boolean isListeningForDun() {
+ for (NetworkRequest req : listening.values()) {
+ if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean isDunRequested() {
+ for (NetworkRequest req : requested.values()) {
+ if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
+ assertFalse(requested.containsKey(cb));
+ requested.put(cb, req);
+ }
+
+ @Override
+ public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
+ assertFalse(listening.containsKey(cb));
+ listening.put(cb, req);
+ }
+
+ @Override
+ public void registerDefaultNetworkCallback(NetworkCallback cb) {
+ assertFalse(trackingDefault.contains(cb));
+ trackingDefault.add(cb);
+ }
+
+ @Override
+ public void unregisterNetworkCallback(NetworkCallback cb) {
+ if (trackingDefault.contains(cb)) {
+ trackingDefault.remove(cb);
+ } else if (listening.containsKey(cb)) {
+ listening.remove(cb);
+ } else if (requested.containsKey(cb)) {
+ requested.remove(cb);
+ }
+
+ assertFalse(trackingDefault.contains(cb));
+ assertFalse(listening.containsKey(cb));
+ assertFalse(requested.containsKey(cb));
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
index b10ec9f..ce2aec79 100644
--- a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
+++ b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
@@ -25,7 +25,6 @@
import android.os.Handler;
import android.os.Handler_Delegate;
-import android.os.Handler_Delegate.IHandlerCallback;
import android.os.Message;
import java.util.PriorityQueue;
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index c3d4cef6..e0f8e1c 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -21,6 +21,7 @@
import com.android.ide.common.rendering.api.DensityBasedResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.PluralsResourceValue;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
@@ -43,6 +44,7 @@
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
+import android.icu.text.PluralRules;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LruCache;
@@ -405,9 +407,6 @@
rv = resources.mContext.getRenderResources().resolveResValue(rv);
if (rv != null) {
return rv.getValue();
- } else {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
- "Unable to resolve resource " + ref, null);
}
}
// Not a reference.
@@ -738,6 +737,48 @@
}
@LayoutlibDelegate
+ static String getQuantityString(Resources resources, int id, int quantity) throws
+ NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ if (value != null) {
+ if (value.getSecond() instanceof PluralsResourceValue) {
+ PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond();
+ PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales()
+ .get(0));
+ String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity));
+ if (strValue == null) {
+ strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER);
+ }
+
+ return strValue;
+ }
+ else {
+ return value.getSecond().getValue();
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)
+ throws NotFoundException {
+ String raw = getQuantityString(resources, id, quantity);
+ return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs);
+ }
+
+ @LayoutlibDelegate
+ static CharSequence getQuantityText(Resources resources, int id, int quantity) throws
+ NotFoundException {
+ return getQuantityString(resources, id, quantity);
+ }
+
+ @LayoutlibDelegate
static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
index 21f36ce..c6827a3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
@@ -30,6 +30,7 @@
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -41,6 +42,7 @@
*/
@SuppressWarnings("deprecation")
public class BidiRenderer {
+ private static String JAVA_VENDOR = System.getProperty("java.vendor");
private static class ScriptRun {
int start;
@@ -221,9 +223,16 @@
frc = mGraphics.getFontRenderContext();
} else {
frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext();
+
// Metrics obtained this way don't have anti-aliasing set. So,
// we create a new FontRenderContext with anti-aliasing set.
- frc = new FontRenderContext(font.getTransform(), mPaint.isAntiAliased(), frc.usesFractionalMetrics());
+ AffineTransform transform = font.getTransform();
+ if (mPaint.isAntiAliased() &&
+ // Workaround for http://b.android.com/211659
+ (transform.getScaleX() <= 9.9 ||
+ !"JetBrains s.r.o".equals(JAVA_VENDOR))) {
+ frc = new FontRenderContext(transform, true, frc.usesFractionalMetrics());
+ }
}
GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag);
int ng = gv.getNumGlyphs();
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 43a0ff5..c599e9d 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -20,24 +20,14 @@
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.GcSnapshot;
-import com.android.layoutlib.bridge.impl.PorterDuffUtility;
-import com.android.ninepatch.NinePatchChunk;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.Nullable;
import android.graphics.Bitmap.Config;
-import android.text.TextUtils;
-import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.Shape;
import java.awt.geom.AffineTransform;
-import java.awt.geom.Arc2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
import libcore.util.NativeAllocationRegistry_Delegate;
@@ -401,23 +391,6 @@
}
@LayoutlibDelegate
- public static boolean nClipRegion(long nativeCanvas,
- long nativeRegion,
- int regionOp) {
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return true;
- }
-
- Region_Delegate region = Region_Delegate.getDelegate(nativeRegion);
- if (region == null) {
- return true;
- }
-
- return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp);
- }
-
- @LayoutlibDelegate
public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index 50efc7f..d326935 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -248,14 +248,17 @@
// ---- delegate methods ----
@LayoutlibDelegate
/*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) {
- final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr);
+ if (thisFontFamily.mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFont after freezing.");
+ }
+ final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr);
return delegate != null && delegate.addFont(path, ttcIndex);
}
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nCreateFamily(String lang, int variant) {
+ /*package*/ static long nInitBuilder(String lang, int variant) {
// TODO: support lang. This is required for japanese locale.
FontFamily_Delegate delegate = new FontFamily_Delegate();
// variant can be 0, 1 or 2.
@@ -270,6 +273,11 @@
}
@LayoutlibDelegate
+ /*package*/ static long nCreateFamily(long builderPtr) {
+ return builderPtr;
+ }
+
+ @LayoutlibDelegate
/*package*/ static void nUnrefFamily(long nativePtr) {
// Removing the java reference for the object doesn't mean that it's freed for garbage
// collection. Typeface_Delegate may still hold a reference for it.
@@ -277,22 +285,22 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) {
+ /*package*/ static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) {
assert false : "The only client of this method has been overriden.";
return false;
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
+ /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
int ttcIndex, List<FontListParser.Axis> listOfAxis,
int weight, boolean isItalic) {
assert false : "The only client of this method has been overriden.";
return false;
}
- static boolean addFont(long nativeFamily, final String path, final int weight,
+ static boolean addFont(long builderPtr, final String path, final int weight,
final boolean isItalic) {
- final FontFamily_Delegate delegate = getDelegate(nativeFamily);
+ final FontFamily_Delegate delegate = getDelegate(builderPtr);
if (delegate != null) {
if (sFontLocation == null) {
delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic));
@@ -304,8 +312,8 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
- FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily);
+ /*package*/ static boolean nAddFontFromAsset(long builderPtr, AssetManager mgr, String path) {
+ FontFamily_Delegate ffd = sManager.getDelegate(builderPtr);
if (ffd == null) {
return false;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 5cd34f6..a554b6d 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -212,9 +212,10 @@
Map<String, ByteBuffer> bufferForPath) {
FontFamily fontFamily = new FontFamily(family.lang, family.variant);
for (FontListParser.Font font : family.fonts) {
- FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.fontName, font.weight,
+ FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, font.fontName, font.weight,
font.isItalic);
}
+ fontFamily.freeze();
return fontFamily;
}
diff --git a/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java b/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java
new file mode 100644
index 0000000..4a5ea9b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+public class PointerIcon_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void loadResource(PointerIcon icon, Context context, Resources resources,
+ int resourceId) {
+ // HACK: This bypasses the problem of having an enum resolved as a resourceId.
+ // PointerIcon would not be displayed by layoutlib anyway, so we always return the null
+ // icon.
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index 3ac1889..77b131f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -76,6 +76,23 @@
}
@Override
+ public Result measure(long timeout) {
+ try {
+ Bridge.prepareThread();
+ mLastResult = mSession.acquire(timeout);
+ if (mLastResult.isSuccess()) {
+ mSession.invalidateRenderingSize();
+ mLastResult = mSession.measure();
+ }
+ } finally {
+ mSession.release();
+ Bridge.cleanupThread();
+ }
+
+ return mLastResult;
+ }
+
+ @Override
public Result render(long timeout, boolean forceMeasure) {
try {
Bridge.prepareThread();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 1b3b563..663e56d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -66,7 +66,6 @@
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.net.Uri;
-import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -104,6 +103,7 @@
import java.util.List;
import java.util.Map;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE;
/**
@@ -113,6 +113,28 @@
public final class BridgeContext extends Context {
private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
+ private static final Map<String, ResourceValue> FRAMEWORK_PATCHED_VALUES = new HashMap<>(2);
+ private static final Map<String, ResourceValue> FRAMEWORK_REPLACE_VALUES = new HashMap<>(3);
+
+ static {
+ FRAMEWORK_PATCHED_VALUES.put("animateFirstView", new ResourceValue(
+ ResourceType.BOOL, "animateFirstView", "false", false));
+ FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges",
+ new ResourceValue(ResourceType.BOOL, "animateLayoutChanges", "false", false));
+
+
+ FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout",
+ new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionItemLayout",
+ "text_edit_suggestion_item", true));
+ FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout",
+ new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionContainerLayout",
+ "text_edit_suggestion_container", true));
+ FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle",
+ new ResourceValue(ResourceType.STYLE, "textEditSuggestionHighlightStyle",
+ "TextAppearance.Holo.SuggestionHighlight", true));
+
+ }
+
/** The map adds cookies to each view so that IDE can link xml tags to views. */
private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
/**
@@ -312,7 +334,7 @@
* Returns the current parser at the top the of the stack.
* @return a parser or null.
*/
- public BridgeXmlBlockParser getCurrentParser() {
+ private BridgeXmlBlockParser getCurrentParser() {
return mParserStack.peek();
}
@@ -406,7 +428,8 @@
}
public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent,
- boolean attachToRoot, boolean skipCallbackParser) {
+ @SuppressWarnings("SameParameterValue") boolean attachToRoot,
+ boolean skipCallbackParser) {
boolean isPlatformLayout = resource.isFramework();
if (!isPlatformLayout && !skipCallbackParser) {
@@ -711,11 +734,7 @@
Object key = parser.getViewCookie();
if (key != null) {
- defaultPropMap = mDefaultPropMaps.get(key);
- if (defaultPropMap == null) {
- defaultPropMap = new PropertiesMap();
- mDefaultPropMaps.put(key, defaultPropMap);
- }
+ defaultPropMap = mDefaultPropMaps.computeIfAbsent(key, k -> new PropertiesMap());
}
} else if (set instanceof BridgeLayoutParamsMapAttributes) {
@@ -909,6 +928,16 @@
// if there's no direct value for this attribute in the XML, we look for default
// values in the widget defStyle, and then in the theme.
if (value == null) {
+ if (frameworkAttr) {
+ // For some framework values, layoutlib patches the actual value in the
+ // theme when it helps to improve the final preview. In most cases
+ // we just disable animations.
+ ResourceValue patchedValue = FRAMEWORK_PATCHED_VALUES.get(attrName);
+ if (patchedValue != null) {
+ defaultValue = patchedValue;
+ }
+ }
+
// if we found a value, we make sure this doesn't reference another value.
// So we resolve it.
if (defaultValue != null) {
@@ -916,16 +945,21 @@
// exist, we should log a warning and omit it.
String val = defaultValue.getValue();
if (val != null && val.startsWith(SdkConstants.PREFIX_THEME_REF)) {
- if (!attrName.equals(RTL_ATTRS.get(val)) ||
- getApplicationInfo().targetSdkVersion <
- VERSION_CODES.JELLY_BEAN_MR1) {
+ // Because we always use the latest framework code, some resources might
+ // fail to resolve when using old themes (they haven't been backported).
+ // Since this is an artifact caused by us using always the latest
+ // code, we check for some of those values and replace them here.
+ defaultValue = FRAMEWORK_REPLACE_VALUES.get(attrName);
+
+ if (defaultValue == null &&
+ (getApplicationInfo().targetSdkVersion < JELLY_BEAN_MR1 ||
+ !attrName.equals(RTL_ATTRS.get(val)))) {
// Only log a warning if the referenced value isn't one of the RTL
// attributes, or the app targets old API.
Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
String.format("Failed to find '%s' in current theme.", val),
val);
}
- defaultValue = null;
}
}
@@ -1944,7 +1978,7 @@
Map<List<StyleResourceValue>,
Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>> mCache;
- public TypedArrayCache() {
+ private TypedArrayCache() {
mCache = new IdentityHashMap<>();
}
@@ -1965,17 +1999,9 @@
public void put(int[] attrs, List<StyleResourceValue> themes, int resId,
Pair<BridgeTypedArray, PropertiesMap> value) {
Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>
- cacheFromThemes = mCache.get(attrs);
- if (cacheFromThemes == null) {
- cacheFromThemes = new HashMap<>();
- mCache.put(attrs, cacheFromThemes);
- }
+ cacheFromThemes = mCache.computeIfAbsent(attrs, k -> new HashMap<>());
Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId =
- cacheFromThemes.get(themes);
- if (cacheFromResId == null) {
- cacheFromResId = new HashMap<>();
- cacheFromThemes.put(themes, cacheFromResId);
- }
+ cacheFromThemes.computeIfAbsent(themes, k -> new HashMap<>());
cacheFromResId.put(resId, value);
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
index 3031701..d7d16cf 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
@@ -17,6 +17,7 @@
import android.util.DisplayMetrics;
import android.view.Display;
+import android.view.Display.Mode;
import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.View;
@@ -33,6 +34,9 @@
DisplayInfo info = new DisplayInfo();
info.logicalHeight = mMetrics.heightPixels;
info.logicalWidth = mMetrics.widthPixels;
+ info.supportedModes = new Mode[] {
+ new Mode(0, mMetrics.widthPixels, mMetrics.heightPixels, 60f)
+ };
mDisplay = new Display(null, Display.DEFAULT_DISPLAY, info,
DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 91a783a..85fe2a4 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -208,7 +208,7 @@
/**
* Measures the the current layout if needed (see {@link #invalidateRenderingSize}).
*/
- private void measure(@NonNull SessionParams params) {
+ private void measureLayout(@NonNull SessionParams params) {
// only do the screen measure when needed.
if (mMeasuredScreenWidth != -1) {
return;
@@ -353,7 +353,7 @@
setActiveToolbar(view, context, params);
- measure(params);
+ measureLayout(params);
measureView(mViewRoot, null /*measuredView*/,
mMeasuredScreenWidth, MeasureSpec.EXACTLY,
mMeasuredScreenHeight, MeasureSpec.EXACTLY);
@@ -385,13 +385,10 @@
}
/**
- * Renders the given view hierarchy to the passed canvas and returns the result of the render
- * operation.
- * @param canvas an optional canvas to render the views to. If null, only the measure and
- * layout steps will be executed.
+ * Runs a layout pass for the given view root
*/
- private static Result render(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
- @Nullable Canvas canvas, int width, int height) {
+ private static void doLayout(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
+ int width, int height) {
// measure again with the size we need
// This must always be done before the call to layout
measureView(viewRoot, null /*measuredView*/,
@@ -401,7 +398,16 @@
// now do the layout.
viewRoot.layout(0, 0, width, height);
handleScrolling(context, viewRoot);
+ }
+ /**
+ * Renders the given view hierarchy to the passed canvas and returns the result of the render
+ * operation.
+ * @param canvas an optional canvas to render the views to. If null, only the measure and
+ * layout steps will be executed.
+ */
+ private static Result renderAndBuildResult(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
+ @Nullable Canvas canvas, int width, int height) {
if (canvas == null) {
return SUCCESS.createResult();
}
@@ -428,6 +434,40 @@
* @see RenderSession#render(long)
*/
public Result render(boolean freshRender) {
+ return renderAndBuildResult(freshRender, false);
+ }
+
+ /**
+ * Measures the layout
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ *
+ * @see SessionParams#getRenderingMode()
+ * @see RenderSession#render(long)
+ */
+ public Result measure() {
+ return renderAndBuildResult(false, true);
+ }
+
+ /**
+ * Renders the scene.
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @param freshRender whether the render is a new one and should erase the existing bitmap (in
+ * the case where bitmaps are reused). This is typically needed when not playing
+ * animations.)
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ *
+ * @see SessionParams#getRenderingMode()
+ * @see RenderSession#render(long)
+ */
+ private Result renderAndBuildResult(boolean freshRender, boolean onlyMeasure) {
checkLock();
SessionParams params = getParams();
@@ -437,14 +477,15 @@
return ERROR_NOT_INFLATED.createResult();
}
- measure(params);
+ measureLayout(params);
HardwareConfig hardwareConfig = params.getHardwareConfig();
Result renderResult = SUCCESS.createResult();
- if (params.isLayoutOnly()) {
+ if (onlyMeasure) {
// delete the canvas and image to reset them on the next full rendering
mImage = null;
mCanvas = null;
+ doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight);
} else {
// draw the views
// create the BufferedImage into which the layout will be rendered.
@@ -505,11 +546,12 @@
gc.dispose();
}
+ doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight);
if (mElapsedFrameTimeNanos >= 0) {
long initialTime = System_Delegate.nanoTime();
if (!mFirstFrameExecuted) {
// We need to run an initial draw call to initialize the animations
- render(getContext(), mViewRoot, NOP_CANVAS, mMeasuredScreenWidth, mMeasuredScreenHeight);
+ renderAndBuildResult(getContext(), mViewRoot, NOP_CANVAS, mMeasuredScreenWidth, mMeasuredScreenHeight);
// The first frame will initialize the animations
Choreographer_Delegate.doFrame(initialTime);
@@ -518,7 +560,7 @@
// Second frame will move the animations
Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
}
- renderResult = render(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth,
+ renderResult = renderAndBuildResult(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth,
mMeasuredScreenHeight);
}
diff --git a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
index 6246ec1b8..04fabc2 100644
--- a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
@@ -52,9 +52,15 @@
@LayoutlibDelegate
/*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) {
- NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction);
- if (delegate != null) {
- delegate.mFinalizer.free(nativePtr);
+ // This method MIGHT run in the context of the finalizer thread. If the delegate method
+ // crashes, it could bring down the VM. That's why we catch all the exceptions and ignore
+ // them.
+ try {
+ NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction);
+ if (delegate != null) {
+ delegate.mFinalizer.free(nativePtr);
+ }
+ } catch (Throwable ignore) {
}
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
new file mode 100644
index 0000000..eb431b0
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
index adb58a3..05a3665 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
@@ -28,7 +28,8 @@
android:layout_alignParentStart="true"
android:layout_below="@id/frameLayout"
android:text="Large Text"
- android:textAppearance="?android:attr/textAppearanceLarge" />
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:pointerIcon="hand" />
<TextView
android:id="@id/textView3"
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
index f6e14d2..5f58d39 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
@@ -17,6 +17,7 @@
<!-- theme ref in android NS. value = @string/candidates_style = <u>candidates</u> -->
<item>?android:attr/candidatesTextStyleSpans</item>
<item>@android:string/unknownName</item> <!-- value = Unknown -->
+ <item>?EC</item>
</string-array>
<!-- resources that the above array can refer to -->
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
index c8a5fec..debe33b 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
@@ -3,7 +3,6 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<item name="myattr">@integer/ten</item>
- <item name="android:animateFirstView">false</item>
</style>
</resources>
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index cdcae89..d0c04d7 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -69,6 +69,7 @@
import com.google.android.collect.Lists;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -313,6 +314,12 @@
sBridge = new Bridge();
sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
ConfigGenerator.getEnumMap(attrs), getLayoutLog());
+ Bridge.getLock().lock();
+ try {
+ Bridge.setLog(getLayoutLog());
+ } finally {
+ Bridge.getLock().unlock();
+ }
}
@Before
@@ -327,6 +334,20 @@
}
@Test
+ public void testActivityOnOldTheme() throws ClassNotFoundException {
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.NoTitleBar", false,
+ RenderingMode.NORMAL, 22);
+
+ renderAndVerify(params, "simple_activity-old-theme.png");
+ }
+
+ @Test
public void testTranslucentBars() throws ClassNotFoundException {
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
@@ -555,7 +576,7 @@
/** Test activity.xml */
@Test
- public void testScrolling() throws ClassNotFoundException {
+ public void testScrollingAndMeasure() throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("scrolled.xml");
// Create LayoutLibCallback.
@@ -569,7 +590,10 @@
params.setForceNoDecor();
params.setExtendedViewInfoMode(true);
- RenderResult result = renderAndVerify(params, "scrolled.png");
+ // Do an only-measure pass
+ RenderSession session = sBridge.createSession(params);
+ session.measure();
+ RenderResult result = RenderResult.getFromSession(session);
assertNotNull(result);
assertNotNull(result.getResult());
assertTrue(result.getResult().isSuccess());
@@ -586,6 +610,20 @@
assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
+
+ // Do a full render pass
+ parser = createLayoutPullParser("scrolled.xml");
+
+ params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+ params.setForceNoDecor();
+ params.setExtendedViewInfoMode(true);
+
+ result = renderAndVerify(params, "scrolled.png");
+ assertNotNull(result);
+ assertNotNull(result.getResult());
+ assertTrue(result.getResult().isSuccess());
}
@Test
@@ -624,6 +662,36 @@
assertEquals("app_name", resources.getResourceEntryName(id));
}
+ @Test
+ public void testStringEscaping() throws Exception {
+ // Setup
+ // Create the layout pull parser for our resources (empty.xml can not be part of the test
+ // app as it won't compile).
+ LayoutPullParser parser = new LayoutPullParser("/empty.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
+ layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+ AssetManager assetManager = AssetManager.getSystem();
+ DisplayMetrics metrics = new DisplayMetrics();
+ Configuration configuration = RenderAction.getConfiguration(params);
+ Resources resources = new Resources(assetManager, metrics, configuration);
+ resources.mLayoutlibCallback = params.getLayoutlibCallback();
+ resources.mContext =
+ new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+ params.getAssets(), params.getLayoutlibCallback(), configuration,
+ params.getTargetSdkVersion(), params.isRtlSupported());
+
+ int id = resources.mLayoutlibCallback.getResourceId(ResourceType.ARRAY, "string_array");
+ String[] strings = resources.getStringArray(id);
+ assertArrayEquals(
+ new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"},
+ strings);
+ assertTrue(sRenderMessages.isEmpty());
+ }
+
@NonNull
private LayoutPullParser createLayoutPullParser(String layoutPath) {
return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath);
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 92fd75c..741eb27 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -154,6 +154,8 @@
"android.content.res.Resources#getIntArray",
"android.content.res.Resources#getInteger",
"android.content.res.Resources#getLayout",
+ "android.content.res.Resources#getQuantityString",
+ "android.content.res.Resources#getQuantityText",
"android.content.res.Resources#getResourceEntryName",
"android.content.res.Resources#getResourceName",
"android.content.res.Resources#getResourcePackageName",
@@ -232,6 +234,7 @@
"android.view.RenderNode#nSetScaleY",
"android.view.RenderNode#nGetScaleY",
"android.view.RenderNode#nIsPivotExplicitlySet",
+ "android.view.PointerIcon#loadResource",
"android.view.ViewGroup#drawChild",
"com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
@@ -336,7 +339,8 @@
*/
private final static String[] PROMOTED_FIELDS = new String[] {
"android.graphics.drawable.VectorDrawable#mVectorState",
- "android.view.Choreographer#mLastFrameTimeNanos"
+ "android.view.Choreographer#mLastFrameTimeNanos",
+ "android.graphics.FontFamily#mBuilderPtr"
};
/**