Merge "Clean up Predicate related code in android.test" into oc-dev-plus-aosp
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1b25e65..3ea0c83 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2138,6 +2138,14 @@
}
@Override
+ public boolean canLoadUnsafeResources() {
+ if (getPackageName().equals(getOpPackageName())) {
+ return true;
+ }
+ return (mFlags & Context.CONTEXT_IGNORE_SECURITY) != 0;
+ }
+
+ @Override
public Display getDisplay() {
if (mDisplay == null) {
return mResourcesManager.getAdjustedDisplay(Display.DEFAULT_DISPLAY,
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
index 33f4e80..7d6ad21 100644
--- a/core/java/android/app/timezone/RulesState.java
+++ b/core/java/android/app/timezone/RulesState.java
@@ -174,29 +174,14 @@
}
/**
- * Returns true if the distro IANA rules version supplied is newer or the same as the version in
- * the system image data files.
+ * Returns true if the system image data files contain IANA rules data that are newer than the
+ * distro IANA rules version supplied, i.e. true when the version specified would be "worse"
+ * than the one that is in the system image. Returns false if the system image version is the
+ * same or older, i.e. false when the version specified would be "better" than the one that is
+ * in the system image.
*/
- public boolean isSystemVersionOlderThan(DistroRulesVersion distroRulesVersion) {
- return mSystemRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) < 0;
- }
-
- public boolean isDistroInstalled() {
- return mDistroStatus == DISTRO_STATUS_INSTALLED;
- }
-
- /**
- * Returns true if the rules version supplied is newer than the one currently installed. If
- * there is no installed distro this method throws IllegalStateException.
- */
- public boolean isInstalledDistroOlderThan(DistroRulesVersion distroRulesVersion) {
- if (mOperationInProgress) {
- throw new IllegalStateException("Distro state not known: operation in progress.");
- }
- if (!isDistroInstalled()) {
- throw new IllegalStateException("No distro installed.");
- }
- return mInstalledDistroRulesVersion.isOlderThan(distroRulesVersion);
+ public boolean isSystemVersionNewerThan(DistroRulesVersion distroRulesVersion) {
+ return mSystemRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) > 0;
}
public static final Parcelable.Creator<RulesState> CREATOR =
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 64c0f31..fc3a724 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -70,9 +70,10 @@
* devices, and start a scan for Bluetooth LE devices.
*
* <p>To get a {@link BluetoothAdapter} representing the local Bluetooth
- * adapter, when running on JELLY_BEAN_MR1 and below, call the
- * static {@link #getDefaultAdapter} method; when running on JELLY_BEAN_MR2 and
- * higher, call {@link BluetoothManager#getAdapter}.
+ * adapter, call the {@link BluetoothManager#getAdapter} function on {@link BluetoothManager}.
+ * On JELLY_BEAN_MR1 and below you will need to use the static {@link #getDefaultAdapter}
+ * method instead.
+ * </p><p>
* Fundamentally, this is your starting point for all
* Bluetooth actions. Once you have the local adapter, you can get a set of
* {@link BluetoothDevice} objects representing all paired devices with
@@ -81,14 +82,13 @@
* listen for incoming connection requests with
* {@link #listenUsingRfcommWithServiceRecord(String,UUID)}; or start a scan for
* Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}.
- *
- * <p>This class is thread safe.
- *
+ * </p>
+ * <p>This class is thread safe.</p>
* <p class="note"><strong>Note:</strong>
* Most methods require the {@link android.Manifest.permission#BLUETOOTH}
* permission and some also require the
* {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
+ * </p>
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>
@@ -565,6 +565,7 @@
* <p>Currently Android only supports one Bluetooth adapter, but the API
* could be extended to support more. This will always return the default
* adapter.
+ * </p>
* @return the default local adapter, or null if Bluetooth is not supported
* on this hardware platform
*/
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index e2fa38a..bacce80 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -33,10 +33,7 @@
* Use {@link android.content.Context#getSystemService(java.lang.String)}
* with {@link Context#BLUETOOTH_SERVICE} to create an {@link BluetoothManager},
* then call {@link #getAdapter} to obtain the {@link BluetoothAdapter}.
- * <p>
- * Alternately, you can just call the static helper
- * {@link BluetoothAdapter#getDefaultAdapter()}.
- *
+ * </p>
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index be065be..6c62f5e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4668,6 +4668,12 @@
public abstract boolean isCredentialProtectedStorage();
/**
+ * Returns true if the context can load unsafe resources, e.g. fonts.
+ * @hide
+ */
+ public abstract boolean canLoadUnsafeResources();
+
+ /**
* @hide
*/
public IBinder getActivityToken() {
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 3b27905..df59f0e 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -920,6 +920,12 @@
return mBase.isCredentialProtectedStorage();
}
+ /** {@hide} */
+ @Override
+ public boolean canLoadUnsafeResources() {
+ return mBase.canLoadUnsafeResources();
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index ff0bc69..44e6f1b 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -623,7 +623,7 @@
protected void finalize() throws Throwable {
try {
- destroy();
+ destroyBinder();
} finally {
super.finalize();
}
@@ -653,7 +653,7 @@
}
private native final void init();
- private native final void destroy();
+ private native final void destroyBinder();
// Entry point from android_util_Binder.cpp's onTransact
private boolean execTransact(int code, long dataObj, long replyObj,
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 394bd0a..a80ef03 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -19,24 +19,230 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.provider.Settings;
+
import com.android.internal.os.HandlerCaller;
import android.annotation.SdkConstant;
-import android.app.Activity;
-import android.app.Service;
-import android.content.Intent;
+import android.app.Service;import android.content.Intent;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.Looper;
import android.util.Log;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
import com.android.internal.os.SomeArgs;
/**
- * Top-level service of the current autofill service for a given user.
+ * An {@code AutofillService} is a service used to automatically fill the contents of the screen
+ * on behalf of a given user - for more information about autofill, read
+ * <a href="{@docRoot}preview/features/autofill.html">Autofill Framework</a>.
*
- * <p>Apps providing autofill capabilities must extend this service.
+ * <p>An {@code AutofillService} is only bound to the Android System for autofill purposes if:
+ * <ol>
+ * <li>It requires the {@code android.permission.BIND_AUTOFILL_SERVICE} permission in its
+ * manifest.
+ * <li>The user explicitly enables it using Android Settings (the
+ * {@link Settings#ACTION_REQUEST_SET_AUTOFILL_SERVICE} intent can be used to launch such
+ * Settings screen).
+ * </ol>
+ *
+ * <h3>Basic usage</h3>
+ *
+ * <p>The basic autofill process is defined by the workflow below:
+ * <ol>
+ * <li>User focus an editable {@link View}.
+ * <li>View calls {@link AutofillManager#notifyViewEntered(android.view.View)}.
+ * <li>A {@link ViewStructure} representing all views in the screen is created.
+ * <li>The Android System binds to the service and calls {@link #onConnected()}.
+ * <li>The service receives the view structure through the
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)}.
+ * <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}.
+ * <li>The Android System calls {@link #onDisconnected()} and unbinds from the
+ * {@code AutofillService}.
+ * <li>The Android System displays an UI affordance with the options sent by the service.
+ * <li>The user picks an option.
+ * <li>The proper views are autofilled.
+ * </ol>
+ *
+ * <p>This workflow was designed to minimize the time the Android System is bound to the service;
+ * for each call, it: binds to service, waits for the reply, and unbinds right away. Furthermore,
+ * those calls are considered stateless: if the service needs to keep state between calls, it must
+ * do its own state management (keeping in mind that the service's process might be killed by the
+ * Android System when unbound; for example, if the device is running low in memory).
+ *
+ * <p>Typically, the
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} will:
+ * <ol>
+ * <li>Parse the view structure looking for autofillable views (for example, using
+ * {@link android.app.assist.AssistStructure.ViewNode#getAutofillHints()}.
+ * <li>Match the autofillable views with the user's data.
+ * <li>Create a {@link Dataset} for each set of user's data that match those fields.
+ * <li>Fill the dataset(s) with the proper {@link AutofillId}s and {@link AutofillValue}s.
+ * <li>Add the dataset(s) to the {@link FillResponse} passed to
+ * {@link FillCallback#onSuccess(FillResponse)}.
+ * </ol>
+ *
+ * <p>For example, for a login screen with username and password views where the user only has one
+ * account in the service, the response could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder()
+ * .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ * .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>But if the user had 2 accounts instead, the response could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder()
+ * .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ * .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ * .build())
+ * .addDataset(new Dataset.Builder()
+ * .setValue(id1, AutofillValue.forText("flanders"), createPresentation("flanders"))
+ * .setValue(id2, AutofillValue.forText("OkelyDokelyDo"), createPresentation("password for flanders"))
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>If the service does not find any autofillable view in the view structure, it should pass
+ * {@code null} to {@link FillCallback#onSuccess(FillResponse)}; if the service encountered an error
+ * processing the request, it should call {@link FillCallback#onFailure(CharSequence)}. For
+ * performance reasons, it's paramount that the service calls either
+ * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)} for
+ * each {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} received - if it
+ * doesn't, the request will eventually time out and be discarded by the Android System.
+ *
+ * <h3>Saving user data</h3>
+ *
+ * <p>If the service is also interested on saving the data filled by the user, it must set a
+ * {@link SaveInfo} object in the {@link FillResponse}. See {@link SaveInfo} for more details and
+ * examples.
+ *
+ * <h3>User authentication</h3>
+ *
+ * <p>The service can provide an extra degree of security by requiring the user to authenticate
+ * before an app can be autofilled. The authentication is typically required in 2 scenarios:
+ * <ul>
+ * <li>To unlock the user data (for example, using a master password or fingerprint
+ * authentication) - see
+ * {@link FillResponse.Builder#setAuthentication(AutofillId[], android.content.IntentSender, android.widget.RemoteViews)}.
+ * <li>To unlock a specific dataset (for example, by providing a CVC for a credit card) - see
+ * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}.
+ * </ul>
+ *
+ * <p>When using authentication, it is recommended to encrypt only the sensitive data and leave
+ * labels unencrypted, so they can be used on presentation views. For example, if the user has a
+ * home and a work address, the {@code Home} and {@code Work} labels should be stored unencrypted
+ * (since they don't have any sensitive data) while the address data per se could be stored in an
+ * encrypted storage. Then when the user chooses the {@code Home} dataset, the platform starts
+ * the authentication flow, and the service can decrypt the sensitive data.
+ *
+ * <p>The authentication mechanism can also be used in scenarios where the service needs multiple
+ * steps to determine the datasets that can fill a screen. For example, when autofilling a financial
+ * app where the user has accounts for multiple banks, the workflow could be:
+ *
+ * <ol>
+ * <li>The first {@link FillResponse} contains datasets with the credentials for the financial
+ * app, plus a "fake" dataset whose presentation says "Tap here for banking apps credentials".
+ * <li>When the user selects the fake dataset, the service displays a dialog with available
+ * banking apps.
+ * <li>When the user select a banking app, the service replies with a new {@link FillResponse}
+ * containing the datasets for that bank.
+ * </ol>
+ *
+ * <p>Another example of multiple-steps dataset selection is when the service stores the user
+ * credentials in "vaults": the first response would contain fake datasets with the vault names,
+ * and the subsequent response would contain the app credentials stored in that vault.
+ *
+ * <h3>Data partitioning</h3>
+ *
+ * <p>The autofillable views in a screen should be grouped in logical groups called "partitions".
+ * Typical partitions are:
+ * <ul>
+ * <li>Credentials (username/email address, password).
+ * <li>Address (street, city, state, zip code, etc).
+ * <li>Payment info (credit card number, expiration date, and verification code).
+ * </ul>
+ * <p>For security reasons, when a screen has more than one partition, it's paramount that the
+ * contents of a dataset do not spawn multiple partitions, specially when one of the partitions
+ * contains data that is not specific to the application being autofilled. For example, a dataset
+ * should not contain fields for username, password, and credit card information. The reason for
+ * this rule is that a malicious app could draft a view structure where the credit card fields
+ * are not visible, so when the user selects a dataset from the username UI, the credit card info is
+ * released to the application without the user knowledge. Similar, it's recommended to always
+ * protect a dataset that contains sensitive information by requiring dataset authentication
+ * (see {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}).
+ *
+ * <p>When the service detects that a screen have multiple partitions, it should return a
+ * {@link FillResponse} with just the datasets for the partition that originated the request (i.e.,
+ * the partition that has the {@link android.app.assist.AssistStructure.ViewNode} whose
+ * {@link android.app.assist.AssistStructure.ViewNode#isFocused()} returns {@code true}); then if
+ * the user selects a field from a different partition, the Android System will make another
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} call for that partition,
+ * and so on.
+ *
+ * <p>Notice that when the user autofill a partition with the data provided by the service and the
+ * user did not change these fields, the autofilled value is sent back to the service in the
+ * subsequent calls (and can be obtained by calling
+ * {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}). This is useful in the
+ * cases where the service must create datasets for a partition based on the choice made in a
+ * previous partition. For example, the 1st response for a screen that have credentials and address
+ * partitions could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder() // partition 1 (credentials)
+ * .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ * .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ * .build())
+ * .addDataset(new Dataset.Builder() // partition 1 (credentials)
+ * .setValue(id1, AutofillValue.forText("flanders"), createPresentation("flanders"))
+ * .setValue(id2, AutofillValue.forText("OkelyDokelyDo"), createPresentation("password for flanders"))
+ * .build())
+ * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ * new AutofillId[] { id1, id2 })
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>Then if the user selected {@code flanders}, the service would get a new
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} call, with the values of
+ * the fields {@code id1} and {@code id2} prepopulated, so the service could then fetch the address
+ * for the Flanders account and return the following {@link FillResponse} for the address partition:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder() // partition 2 (address)
+ * .setValue(id3, AutofillValue.forText("744 Evergreen Terrace"), createPresentation("744 Evergreen Terrace")) // street
+ * .setValue(id4, AutofillValue.forText("Springfield"), createPresentation("Springfield")) // city
+ * .build())
+ * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD | SaveInfo.SAVE_DATA_TYPE_ADDRESS,
+ * new AutofillId[] { id1, id2 }) // username and password
+ * .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>When the service returns multiple {@link FillResponse}, the last one overrides the previous;
+ * that's why the {@link SaveInfo} in the 2nd request above has the info for both partitions.
+ *
+ * <h3>Ignoring views</h3>
+ *
+ * <p>If the service find views that cannot be autofilled (for example, a text field representing
+ * the response to a Captcha challenge), it should mark those views as ignored by
+ * calling {@link FillResponse.Builder#setIgnoredIds(AutofillId...)} so the system does not trigger
+ * a new {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} when these views are
+ * focused.
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
@@ -132,11 +338,6 @@
private HandlerCaller mHandlerCaller;
- /**
- * {@inheritDoc}
- *
- * <strong>NOTE: </strong>if overridden, it must call {@code super.onCreate()}.
- */
@CallSuper
@Override
public void onCreate() {
@@ -162,8 +363,7 @@
}
/**
- * Called by the Android system do decide if an {@link Activity} can be autofilled by the
- * service.
+ * Called by the Android system do decide if a screen can be autofilled by the service.
*
* <p>Service must call one of the {@link FillCallback} methods (like
* {@link FillCallback#onSuccess(FillResponse)}
@@ -181,7 +381,7 @@
@NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
/**
- * Called when user requests service to save the fields of an {@link Activity}.
+ * Called when user requests service to save the fields of a screen.
*
* <p>Service must call one of the {@link SaveCallback} methods (like
* {@link SaveCallback#onSuccess()} or {@link SaveCallback#onFailure(CharSequence)})
@@ -226,7 +426,7 @@
* @return The history or {@code null} if there are no events.
*/
@Nullable public final FillEventHistory getFillEventHistory() {
- AutofillManager afm = getSystemService(AutofillManager.class);
+ final AutofillManager afm = getSystemService(AutofillManager.class);
if (afm == null) {
return null;
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index af2eb34..a2ec099 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -31,17 +31,23 @@
import java.util.ArrayList;
/**
- * A set of data that can be used to autofill an {@link android.app.Activity}.
+ * A dataset object represents a group of key/value pairs used to autofill parts of a screen.
*
- * <p>It contains:
+ * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of
+ * {@link AutofillId} and {@link AutofillValue} respectively); and one or more
+ * {@link RemoteViews presentation} for these pairs (a pair could have its own
+ * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated
+ * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse}
+ * and the screen input is focused in a view that is present in at least one of these datasets,
+ * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of
+ * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
+ * dataset from the affordance, all views in that dataset are autofilled.
*
- * <ol>
- * <li>A list of values for input fields.
- * <li>A presentation view to visualize.
- * <li>An optional intent to authenticate.
- * </ol>
+ * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates
+ * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}.
*
- * @see android.service.autofill.FillResponse for examples.
+ * @see android.service.autofill.AutofillService for more information and examples about the
+ * role of datasets in the autofill workflow.
*/
public final class Dataset implements Parcelable {
@@ -113,7 +119,7 @@
}
/**
- * A builder for {@link Dataset} objects. You must to provide at least
+ * A builder for {@link Dataset} objects. You must provide at least
* one value for a field or set an authentication intent.
*/
public static final class Builder {
@@ -175,9 +181,9 @@
* credit card information without the CVV for the data set in the {@link FillResponse
* response} then the returned data set should contain the CVV entry.
*
- * <p></><strong>Note:</strong> Do not make the provided pending intent
+ * <p><b>NOTE:</b> Do not make the provided pending intent
* immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
- * platform needs to fill in the authentication arguments.</p>
+ * platform needs to fill in the authentication arguments.
*
* @param authentication Intent to an activity with your authentication flow.
* @return This builder.
@@ -191,7 +197,7 @@
}
/**
- * Sets the id for the dataset.
+ * Sets the id for the dataset so its usage history can be retrieved later.
*
* <p>The id of the last selected dataset can be read from
* {@link AutofillService#getFillEventHistory()}. If the id is not set it will not be clear
@@ -214,13 +220,12 @@
*
* @param id id returned by {@link
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
- * @param value value to be auto filled. Pass {@code null} if you do not have the value
+ * @param value value to be autofilled. Pass {@code null} if you do not have the value
* but the target view is a logical part of the dataset. For example, if
* the dataset needs an authentication and you have no access to the value.
- * Filtering matches any user typed string to {@code null} values.
* @return This builder.
- * @throws IllegalStateException if the builder was constructed without a presentation
- * ({@link RemoteViews}).
+ * @throws IllegalStateException if the builder was constructed without a
+ * {@link RemoteViews presentation}.
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
throwIfDestroyed();
@@ -232,7 +237,8 @@
}
/**
- * Sets the value of a field, using a custom presentation to visualize it.
+ * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+ * visualize it.
*
* @param id id returned by {@link
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
@@ -272,10 +278,12 @@
}
/**
- * Creates a new {@link Dataset} instance. You should not interact
- * with this builder once this method is called. It is required
- * that you specified at least one field. Also it is mandatory to
- * provide a presentation view to visualize the data set in the UI.
+ * Creates a new {@link Dataset} instance.
+ *
+ * <p>You should not interact with this builder once this method is called.
+ *
+ * <p>It is required that you specify at least one field before calling this method. It's
+ * also mandatory to provide a presentation view to visualize the data set in the UI.
*
* @return The built dataset.
*/
diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java
index f8a8751..cda2f4a 100644
--- a/core/java/android/service/autofill/FillContext.java
+++ b/core/java/android/service/autofill/FillContext.java
@@ -30,7 +30,6 @@
import android.util.SparseIntArray;
import android.view.autofill.AutofillId;
-import java.util.ArrayList;
import java.util.LinkedList;
/**
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index b1145ee..fd6da05 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -23,6 +23,7 @@
import android.os.CancellationSignal;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.View;
import com.android.internal.util.Preconditions;
@@ -32,7 +33,7 @@
import java.util.List;
/**
- * This class represents a request to an {@link AutofillService autofill provider}
+ * This class represents a request to an autofill service
* to interpret the screen and provide information to the system which views are
* interesting for saving and what are the possible ways to fill the inputs on
* the screen if applicable.
@@ -40,8 +41,29 @@
* @see AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)
*/
public final class FillRequest implements Parcelable {
+
/**
* Indicates autofill was explicitly requested by the user.
+ *
+ * <p>Users typically make an explicit request to autofill a screen in two situations:
+ * <ul>
+ * <li>The app disabled autofill (using {@link View#setImportantForAutofill(int)}.
+ * <li>The service could not figure out how to autofill a screen (but the user knows the
+ * service has data for that app).
+ * </ul>
+ *
+ * <p>This flag is particularly useful for the second case. For example, the service could offer
+ * a complex UI where the user can map which screen views belong to each user data, or it could
+ * offer a simpler UI where the user picks the data for just the view used to trigger the
+ * request (that would be the view whose
+ * {@link android.app.assist.AssistStructure.ViewNode#isFocused()} method returns {@code true}).
+ *
+ * <p>An explicit autofill request is triggered when the
+ * {@link android.view.autofill.AutofillManager#requestAutofill(View)} or
+ * {@link android.view.autofill.AutofillManager#requestAutofill(View, int, android.graphics.Rect)}
+ * is called. For example, standard {@link android.widget.TextView} views that use
+ * an {@link android.widget.Editor} shows an {@code AUTOFILL} option in the overflow menu that
+ * triggers such request.
*/
public static final int FLAG_MANUAL_REQUEST = 0x1;
@@ -79,14 +101,14 @@
}
/**
- * @return The unique id of this request.
+ * Gets the unique id of this request.
*/
public int getId() {
return mId;
}
/**
- * @return The flags associated with this request.
+ * Gets the flags associated with this request.
*
* @see #FLAG_MANUAL_REQUEST
*/
@@ -95,7 +117,7 @@
}
/**
- * @return The contexts associated with each previous fill request.
+ * Gets the contexts associated with each previous fill request.
*/
public @NonNull List<FillContext> getFillContexts() {
return mContexts;
@@ -104,10 +126,10 @@
/**
* Gets the extra client state returned from the last {@link
* AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)
- * fill request}.
- * <p>
- * Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)
- * save request} is made the client state is cleared.
+ * fill request}, so the service can use it for state management.
+ *
+ * <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)
+ * save request} is made, the client state is cleared.
*
* @return The client state.
*/
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index fcf18eb..e13fdf6 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
@@ -36,100 +37,7 @@
* Response for a {@link
* AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}.
*
- * <p>The response typically contains one or more {@link Dataset}s, each representing a set of
- * fields that can be autofilled together, and the Android system displays a dataset picker UI
- * affordance that the user must use before the {@link android.app.Activity} is filled with
- * the dataset.
- *
- * <p>For example, for a login page with username/password where the user only has one account in
- * the response could be:
- *
- * <pre class="prettyprint">
- * new FillResponse.Builder()
- * .add(new Dataset.Builder(createPresentation())
- * .setValue(id1, AutofillValue.forText("homer"))
- * .setValue(id2, AutofillValue.forText("D'OH!"))
- * .build())
- * .build();
- * </pre>
- *
- * <p>If the user had 2 accounts, each with its own user-provided names, the response could be:
- *
- * <pre class="prettyprint">
- * new FillResponse.Builder()
- * .add(new Dataset.Builder(createFirstPresentation())
- * .setValue(id1, AutofillValue.forText("homer"))
- * .setValue(id2, AutofillValue.forText("D'OH!"))
- * .build())
- * .add(new Dataset.Builder(createSecondPresentation())
- * .setValue(id1, AutofillValue.forText("elbarto")
- * .setValue(id2, AutofillValue.forText("cowabonga")
- * .build())
- * .build();
- * </pre>
- *
- * If the service is interested on saving the user-edited data back, it must set a {@link SaveInfo}
- * in the {@link FillResponse}. Typically, the {@link SaveInfo} contains the same ids as the
- * {@link Dataset}, but other combinations are possible - see {@link SaveInfo} for more details
- *
- * <p>If the service has multiple {@link Dataset}s for different sections of the activity,
- * for example, a user section for which there are two datasets followed by an address
- * section for which there are two datasets for each user user, then it should "partition"
- * the activity in sections and populate the response with just a subset of the data that would
- * fulfill the first section (the name in our example); then once the user fills the first
- * section and taps a field from the next section (the address in our example), the Android
- * system would issue another request for that section, and so on. Note that if the user
- * chooses to populate the first section with a service provided dataset, the subsequent request
- * would contain the populated values so you don't try to provide suggestions for the first
- * section but ony for the second one based on the context of what was already filled. For
- * example, the first response could be:
- *
- * <pre class="prettyprint">
- * new FillResponse.Builder()
- * .add(new Dataset.Builder(createFirstPresentation())
- * .setValue(id1, AutofillValue.forText("Homer"))
- * .setValue(id2, AutofillValue.forText("Simpson"))
- * .build())
- * .add(new Dataset.Builder(createSecondPresentation())
- * .setValue(id1, AutofillValue.forText("Bart"))
- * .setValue(id2, AutofillValue.forText("Simpson"))
- * .build())
- * .build();
- * </pre>
- *
- * <p>Then after the user picks the second dataset and taps the street field to
- * trigger another autofill request, the second response could be:
- *
- * <pre class="prettyprint">
- * new FillResponse.Builder()
- * .add(new Dataset.Builder(createThirdPresentation())
- * .setValue(id3, AutofillValue.forText("742 Evergreen Terrace"))
- * .setValue(id4, AutofillValue.forText("Springfield"))
- * .build())
- * .add(new Dataset.Builder(createFourthPresentation())
- * .setValue(id3, AutofillValue.forText("Springfield Power Plant"))
- * .setValue(id4, AutofillValue.forText("Springfield"))
- * .build())
- * .build();
- * </pre>
- *
- * <p>The service could require user authentication at the {@link FillResponse} or the
- * {@link Dataset} level, prior to autofilling an activity - see
- * {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)} and
- * {@link Dataset.Builder#setAuthentication(IntentSender)}.
- *
- * <p>It is recommended that you encrypt only the sensitive data but leave the labels unencrypted
- * which would allow you to provide a dataset presentation views with labels and if the user
- * chooses one of them challenge the user to authenticate. For example, if the user has a
- * home and a work address the Home and Work labels could be stored unencrypted as they don't
- * have any sensitive data while the address data is in an encrypted storage. If the user
- * chooses Home, then the platform will start your authentication flow. If you encrypt all
- * data and require auth at the response level the user will have to interact with the fill
- * UI to trigger a request for the datasets (as they don't see the presentation views for the
- * possible options) which will start your auth flow and after successfully authenticating
- * the user will be presented with the Home and Work options to pick one. Hence, you have
- * flexibility how to implement your auth while storing labels non-encrypted and data
- * encrypted provides a better user experience.
+ * <p>See the main {@link AutofillService} documentation for more details and examples.
*/
public final class FillResponse implements Parcelable {
@@ -221,7 +129,7 @@
private boolean mDestroyed;
/**
- * Requires a fill response authentication before autofilling the activity with
+ * Requires a fill response authentication before autofilling the screen with
* any data set in this response.
*
* <p>This is typically useful when a user interaction is required to unlock their
@@ -230,16 +138,16 @@
* auth on the data set level leading to a better user experience. Note that if you
* use sensitive data as a label, for example an email address, then it should also
* be encrypted. The provided {@link android.app.PendingIntent intent} must be an
- * activity which implements your authentication flow. Also if you provide an auth
+ * {@link Activity} which implements your authentication flow. Also if you provide an auth
* intent you also need to specify the presentation view to be shown in the fill UI
* for the user to trigger your authentication flow.
*
* <p>When a user triggers autofill, the system launches the provided intent
* whose extras will have the {@link AutofillManager#EXTRA_ASSIST_STRUCTURE screen
* content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
- * client state}. Once you complete your authentication flow you should set the activity
- * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
- * {@link FillResponse response} by setting it to the {@link
+ * client state}. Once you complete your authentication flow you should set the
+ * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and provide the fully
+ * populated {@link FillResponse response} by setting it to the {@link
* AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra.
* For example, if you provided an empty {@link FillResponse resppnse} because the
* user's data was locked and marked that the response needs an authentication then
@@ -286,8 +194,8 @@
* {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
* FillCallback)} requests.
*
- * <p>This is typically used when the service cannot autofill the view; for example, an
- * {@code EditText} representing a captcha.
+ * <p>This is typically used when the service cannot autofill the view; for example, a
+ * text field representing the result of a Captcha challenge.
*/
public Builder setIgnoredIds(AutofillId...ids) {
mIgnoredIds = ids;
@@ -316,8 +224,6 @@
/**
* Sets the {@link SaveInfo} associated with this response.
*
- * <p>See {@link FillResponse} for more info.
- *
* @return This builder.
*/
public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
@@ -335,7 +241,7 @@
* fill requests and the subsequent save request.
*
* <p>If this method is called on multiple {@link FillResponse} objects for the same
- * activity, just the latest bundle is passed back to the service.
+ * screen, just the latest bundle is passed back to the service.
*
* <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)
* save request} is made the client state is cleared.
@@ -350,9 +256,10 @@
}
/**
- * Builds a new {@link FillResponse} instance. You must provide at least
- * one dataset or some savable ids or an authentication with a presentation
- * view.
+ * Builds a new {@link FillResponse} instance.
+ *
+ * <p>You must provide at least one dataset or some savable ids or an authentication with a
+ * presentation view.
*
* @return A built response.
*/
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 6ea7d5e..95d393b 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
@@ -45,70 +46,91 @@
* two pieces of information:
*
* <ol>
- * <li>The type of user data that would be saved (like passoword or credit card info).
+ * <li>The type(s) of user data (like password or credit card info) that would be saved.
* <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed
* to trigger a save request.
* </ol>
*
- * Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
+ * <p>Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
*
* <pre class="prettyprint">
- * new FillResponse.Builder()
- * .add(new Dataset.Builder(createPresentation())
- * .setValue(id1, AutofillValue.forText("homer"))
- * .setValue(id2, AutofillValue.forText("D'OH!"))
- * .build())
- * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
- * .build())
- * .build();
+ * new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder()
+ * .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer")) // username
+ * .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer")) // password
+ * .build())
+ * .setSaveInfo(new SaveInfo.Builder(
+ * SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ * new AutofillId[] { id1, id2 }).build())
+ * .build();
* </pre>
*
- * There might be cases where the {@link AutofillService} knows how to fill the
- * {@link android.app.Activity}, but the user has no data for it. In that case, the
- * {@link FillResponse} should contain just the {@link SaveInfo}, but no {@link Dataset}s:
+ * <p>The save type flags are used to display the appropriate strings in the save UI affordance.
+ * You can pass multiple values, but try to keep it short if possible. In the above example, just
+ * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
+ *
+ * <p>There might be cases where the {@link AutofillService} knows how to fill the screen,
+ * but the user has no data for it. In that case, the {@link FillResponse} should contain just the
+ * {@link SaveInfo}, but no {@link Dataset Datasets}:
*
* <pre class="prettyprint">
- * new FillResponse.Builder()
- * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
- * .build())
- * .build();
+ * new FillResponse.Builder()
+ * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ * new AutofillId[] { id1, id2 }).build())
+ * .build();
* </pre>
*
* <p>There might be cases where the user data in the {@link AutofillService} is enough
* to populate some fields but not all, and the service would still be interested on saving the
- * other fields. In this scenario, the service could set the
+ * other fields. In that case, the service could set the
* {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
- * .add(new Dataset.Builder(createPresentation())
- * .setValue(id1, AutofillValue.forText("742 Evergreen Terrace")) // street
- * .setValue(id2, AutofillValue.forText("Springfield")) // city
- * .build())
- * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS, new int[] {id1, id2})
- * .setOptionalIds(new int[] {id3, id4}) // state and zipcode
- * .build())
+ * .addDataset(new Dataset.Builder()
+ * .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"),
+ * createPresentation("742 Evergreen Terrace")) // street
+ * .setValue(id2, AutofillValue.forText("Springfield"),
+ * createPresentation("Springfield")) // city
+ * .build())
+ * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS,
+ * new AutofillId[] { id1, id2 }) // street and city
+ * .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
+ * .build())
* .build();
* </pre>
*
- * The
- * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
- * is triggered after a call to {@link AutofillManager#commit()}, but only when all conditions
- * below are met:
+ * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
+ * any of the following events:
+ * <ul>
+ * <li>The {@link Activity} finishes.
+ * <li>The app explicitly called {@link AutofillManager#commit()}.
+ * <li>All required views became invisible (if the {@link SaveInfo} was created with the
+ * {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
+ * </ul>
*
- * <ol>
+ * <p>But it is only triggered when all conditions below are met:
+ * <ul>
* <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}.
- * <li>The {@link AutofillValue} of all required views (as set by the {@code requiredIds} passed
- * to {@link SaveInfo.Builder} constructor are not empty.
+ * <li>The {@link AutofillValue}s of all required views (as set by the {@code requiredIds} passed
+ * to the {@link SaveInfo.Builder} constructor are not empty.
* <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed
- * (i.e., it's not the same value passed in a {@link Dataset}).
- * <li>The user explicitly tapped the affordance asking to save data for autofill.
- * </ol>
+ * (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value
+ * presented in the view).
+ * <li>The user explicitly tapped the UI affordance asking to save data for autofill.
+ * </ul>
+ *
+ * <p>The service can also customize some aspects of the save UI affordance:
+ * <ul>
+ * <li>Add a subtitle by calling {@link Builder#setDescription(CharSequence)}.
+ * <li>Customize the button used to reject the save request by calling
+ * {@link Builder#setNegativeAction(int, IntentSender)}.
+ * </ul>
*/
public final class SaveInfo implements Parcelable {
/**
- * Type used on when the service can save the contents of an activity, but cannot describe what
+ * Type used when the service can save the contents of a screen, but cannot describe what
* the content is for.
*/
public static final int SAVE_DATA_TYPE_GENERIC = 0x0;
@@ -181,8 +203,8 @@
/**
* Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
- * is called once the activity finishes. If this flag is set it is called once all saved views
- * become invisible.
+ * is called once the {@link Activity} finishes. If this flag is set it is called once all
+ * saved views become invisible.
*/
public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
@@ -294,9 +316,9 @@
}
/**
- * Set flags changing the save behavior.
+ * Sets flags changing the save behavior.
*
- * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or 0.
+ * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
* @return This builder.
*/
public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 2e8faee..0d67615 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -25,6 +25,7 @@
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
@@ -532,8 +533,14 @@
public void setHeight(int height) {
if (height < 0 && ViewGroup.LayoutParams.WRAP_CONTENT != height
&& ViewGroup.LayoutParams.MATCH_PARENT != height) {
- throw new IllegalArgumentException(
- "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT.");
+ if (mContext.getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ Log.e(TAG, "Negative value " + height + " passed to ListPopupWindow#setHeight"
+ + " produces undefined results");
+ } else {
+ throw new IllegalArgumentException(
+ "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT.");
+ }
}
mDropDownHeight = height;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6b328ea..9a92489 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -913,7 +913,7 @@
break;
case com.android.internal.R.styleable.TextAppearance_fontFamily:
- if (!context.isRestricted()) {
+ if (!context.isRestricted() && context.canLoadUnsafeResources()) {
try {
fontTypeface = appearance.getFont(attr);
} catch (UnsupportedOperationException
@@ -1233,7 +1233,7 @@
break;
case com.android.internal.R.styleable.TextView_fontFamily:
- if (!context.isRestricted()) {
+ if (!context.isRestricted() && context.canLoadUnsafeResources()) {
try {
fontTypeface = a.getFont(attr);
} catch (UnsupportedOperationException | Resources.NotFoundException e) {
@@ -3417,7 +3417,7 @@
Typeface fontTypeface = null;
String fontFamily = null;
- if (!context.isRestricted()) {
+ if (!context.isRestricted() && context.canLoadUnsafeResources()) {
try {
fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily);
} catch (UnsupportedOperationException | Resources.NotFoundException e) {
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index b245678..608bc9f 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -163,6 +163,8 @@
argv = removedArgs;
}
+ // Perform the same initialization that would happen after the Zygote forks.
+ Zygote.nativePreApplicationInit();
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index e065843..91d9d1e 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -121,6 +121,11 @@
int[] fdsToIgnore, String instructionSet, String appDataDir);
/**
+ * Called to do any initialization before starting an application.
+ */
+ native static void nativePreApplicationInit();
+
+ /**
* Special method to start the system server process. In addition to the
* common actions performed in forkAndSpecialize, the pid of the child
* process is recorded such that the death of the child process will cause
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java
index 1ba92bf..0c046a9 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -534,7 +534,7 @@
}
public static boolean satisfiesTextContrast(int backgroundColor, int foregroundColor) {
- return NotificationColorUtil.calculateContrast(backgroundColor, foregroundColor) >= 4.5;
+ return NotificationColorUtil.calculateContrast(foregroundColor, backgroundColor) >= 4.5;
}
/**
@@ -613,7 +613,7 @@
*/
public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) {
if (Color.alpha(background) != 255) {
- throw new IllegalArgumentException("background can not be translucent: #"
+ Log.wtf(TAG, "background can not be translucent: #"
+ Integer.toHexString(background));
}
if (Color.alpha(foreground) < 255) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f7bb7b4..abd6278 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -215,7 +215,6 @@
"libgif",
"libseccomp_policy",
"libselinux",
- "libcrypto",
"libgrallocusage",
],
@@ -224,6 +223,7 @@
"libandroidfw",
"libappfuse",
"libbase",
+ "libcrypto",
"libnativehelper",
"liblog",
"libcutils",
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index d4735ec..41d9111 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -49,27 +49,6 @@
pDest[3] = pM[3 + 4 * 0] * x + pM[3 + 4 * 1] * y + pM[3 + 4 * 2] * z + pM[3 + 4 * 3] * w;
}
-class MallocHelper {
-public:
- MallocHelper() {
- mData = 0;
- }
-
- ~MallocHelper() {
- if (mData != 0) {
- free(mData);
- }
- }
-
- void* alloc(size_t size) {
- mData = malloc(size);
- return mData;
- }
-
-private:
- void* mData;
-};
-
#if 0
static
void
@@ -85,10 +64,7 @@
static
int visibilityTest(float* pWS, float* pPositions, int positionsLength,
unsigned short* pIndices, int indexCount) {
- MallocHelper mallocHelper;
int result = POLY_CLIP_OUT;
- float* pTransformed = 0;
- int transformedIndexCount = 0;
if ( indexCount < 3 ) {
return POLY_CLIP_OUT;
@@ -116,8 +92,9 @@
return -1;
}
- transformedIndexCount = maxIndex - minIndex + 1;
- pTransformed = (float*) mallocHelper.alloc(transformedIndexCount * 4 * sizeof(float));
+ int transformedIndexCount = maxIndex - minIndex + 1;
+ std::unique_ptr<float[]> holder{new float[transformedIndexCount * 4]};
+ float* pTransformed = holder.get();
if (pTransformed == 0 ) {
return -2;
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 26b0034..e2aa17b 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -836,7 +836,7 @@
env->SetLongField(obj, gBinderOffsets.mObject, (jlong)jbh);
}
-static void android_os_Binder_destroy(JNIEnv* env, jobject obj)
+static void android_os_Binder_destroyBinder(JNIEnv* env, jobject obj)
{
JavaBBinderHolder* jbh = (JavaBBinderHolder*)
env->GetLongField(obj, gBinderOffsets.mObject);
@@ -847,7 +847,7 @@
} else {
// Encountering an uninitialized binder is harmless. All it means is that
// the Binder was only partially initialized when its finalizer ran and called
- // destroy(). The Binder could be partially initialized for several reasons.
+ // destroyBinder(). The Binder could be partially initialized for several reasons.
// For example, a Binder subclass constructor might have thrown an exception before
// it could delegate to its superclass's constructor. Consequently init() would
// not have been called and the holder pointer would remain NULL.
@@ -872,7 +872,7 @@
{ "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
{ "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
{ "init", "()V", (void*)android_os_Binder_init },
- { "destroy", "()V", (void*)android_os_Binder_destroy },
+ { "destroyBinder", "()V", (void*)android_os_Binder_destroyBinder },
{ "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
};
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d73e7dd..cb53106 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -221,6 +221,14 @@
// The debug malloc library needs to know whether it's the zygote or a child.
extern "C" int gMallocLeakZygoteChild;
+static void PreApplicationInit() {
+ // The child process sets this to indicate it's not the zygote.
+ gMallocLeakZygoteChild = 1;
+
+ // Set the jemalloc decay time to 1.
+ mallopt(M_DECAY_TIME, 1);
+}
+
static void EnableKeepCapabilities(JNIEnv* env) {
int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
if (rc == -1) {
@@ -517,11 +525,7 @@
pid_t pid = fork();
if (pid == 0) {
- // The child process.
- gMallocLeakZygoteChild = 1;
-
- // Set the jemalloc decay time to 1.
- mallopt(M_DECAY_TIME, 1);
+ PreApplicationInit();
// Clean up any descriptors which must be closed immediately
DetachDescriptors(env, fdsToClose);
@@ -678,6 +682,10 @@
namespace android {
+static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jclass) {
+ PreApplicationInit();
+}
+
static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint debug_flags, jobjectArray rlimits,
@@ -807,7 +815,9 @@
{ "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V",
(void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork },
{ "nativeUnmountStorageOnInit", "()V",
- (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit }
+ (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit },
+ { "nativePreApplicationInit", "()V",
+ (void *) com_android_internal_os_Zygote_nativePreApplicationInit }
};
int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
index a9357c9..7f4819b 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -107,7 +107,7 @@
"2016a", formatVersion(1, 1), true /* operationInProgress */,
RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
- checkParcelableRoundTrip(rulesStateWithNulls);
+ checkParcelableRoundTrip(rulesStateWithUnknowns);
}
private static void checkParcelableRoundTrip(RulesState rulesState) {
@@ -121,55 +121,14 @@
}
@Test
- public void isSystemVersionOlderThan() {
+ public void isSystemVersionNewerThan() {
RulesState rulesState = new RulesState(
"2016b", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
- assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016a", 1)));
- assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016b", 1)));
- assertTrue(rulesState.isSystemVersionOlderThan(rulesVersion("2016c", 1)));
- }
-
- @Test
- public void isInstalledDistroOlderThan() {
- RulesState operationInProgress = new RulesState(
- "2016b", formatVersion(1, 1), true /* operationInProgress */,
- RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
- RulesState.STAGED_OPERATION_UNKNOWN, null /* installedDistroRulesVersion */);
- try {
- operationInProgress.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
- fail();
- } catch (IllegalStateException expected) {
- }
-
- RulesState nothingInstalled = new RulesState(
- "2016b", formatVersion(1, 1), false /* operationInProgress */,
- RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
- RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
- try {
- nothingInstalled.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
- fail();
- } catch (IllegalStateException expected) {
- }
-
- DistroRulesVersion installedVersion = rulesVersion("2016b", 3);
- RulesState rulesStateWithInstalledVersion = new RulesState(
- "2016b", formatVersion(1, 1), false /* operationInProgress */,
- RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
- RulesState.DISTRO_STATUS_INSTALLED, installedVersion);
-
- DistroRulesVersion olderRules = rulesVersion("2016a", 1);
- assertEquals(installedVersion.isOlderThan(olderRules),
- rulesStateWithInstalledVersion.isInstalledDistroOlderThan(olderRules));
-
- DistroRulesVersion sameRules = rulesVersion("2016b", 1);
- assertEquals(installedVersion.isOlderThan(sameRules),
- rulesStateWithInstalledVersion.isInstalledDistroOlderThan(sameRules));
-
- DistroRulesVersion newerRules = rulesVersion("2016c", 1);
- assertEquals(installedVersion.isOlderThan(newerRules),
- rulesStateWithInstalledVersion.isInstalledDistroOlderThan(newerRules));
+ assertTrue(rulesState.isSystemVersionNewerThan(rulesVersion("2016a", 1)));
+ assertFalse(rulesState.isSystemVersionNewerThan(rulesVersion("2016b", 1)));
+ assertFalse(rulesState.isSystemVersionNewerThan(rulesVersion("2016c", 1)));
}
private static void assertEqualsContract(RulesState one, RulesState two) {
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index b8e1111..8d64c1d 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -364,6 +364,9 @@
return;
} else if (mPagesLoaded == 2) {
// Prevent going back to empty first page.
+ // Fix for missing focus, see b/62449959 for details. Remove it once we get a
+ // newer version of WebView (60.x.y).
+ view.requestFocus();
view.clearHistory();
}
testForCaptivePortal();
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
index a8f6f02..8fc9fa6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
@@ -54,6 +54,10 @@
mStorageStatsManager.queryStatsForPackage(volumeUuid, packageName, user));
}
+ public long getCacheQuotaBytes(String volumeUuid, int uid) {
+ return mStorageStatsManager.getCacheQuotaBytes(volumeUuid, uid);
+ }
+
/**
* Static class that provides methods for querying the amount of external storage available as
* well as breaking it up into several media types.
diff --git a/packages/SystemUI/res/drawable/pip_icon.xml b/packages/SystemUI/res/drawable/pip_icon.xml
new file mode 100644
index 0000000..bd92ccd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_icon.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="25"
+ android:viewportHeight="25">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M19,7h-8v6h8L19,7zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,1.98 2,1.98h18c1.1,0 2,-0.88 2,-1.98L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.98h18v14.03z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index f0745a0..ac41b75 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -174,7 +174,7 @@
void onConfigurationChanged(Context context) {
Resources res = context.getResources();
mDefaultTitle = res.getString(R.string.pip_notification_unknown_title);
- mDefaultIconResId = R.drawable.pip_expand;
+ mDefaultIconResId = R.drawable.pip_icon;
if (mNotified) {
// update notification
notifyPipNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 93033ea..80c44a3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -71,6 +71,7 @@
Context mContext;
+ int mPreloadedUserId;
List<ActivityManager.RecentTaskInfo> mRawTasks;
TaskStack mStack;
ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
@@ -83,9 +84,6 @@
private void updateCurrentQuietProfilesCache(int currentUserId) {
mCurrentQuietProfiles.clear();
- if (currentUserId == UserHandle.USER_CURRENT) {
- currentUserId = SystemServicesProxy.getInstance(mContext).getCurrentUser();
- }
UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
List<UserInfo> profiles = userManager.getProfiles(currentUserId);
if (profiles != null) {
@@ -105,9 +103,10 @@
* Note: Do not lock, callers should synchronize on the loader before making this call.
*/
void preloadRawTasks(boolean includeFrontMostExcludedTask) {
- int currentUserId = UserHandle.USER_CURRENT;
- updateCurrentQuietProfilesCache(currentUserId);
SystemServicesProxy ssp = Recents.getSystemServices();
+ int currentUserId = ssp.getCurrentUser();
+ updateCurrentQuietProfilesCache(currentUserId);
+ mPreloadedUserId = currentUserId;
mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
@@ -135,7 +134,6 @@
preloadRawTasks(includeFrontMostExcludedTask);
}
- SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
SparseIntArray affiliatedTaskCounts = new SparseIntArray();
SparseBooleanArray lockedUsers = new SparseBooleanArray();
@@ -143,7 +141,7 @@
R.string.accessibility_recents_item_will_be_dismissed);
String appInfoDescFormat = mContext.getString(
R.string.accessibility_recents_item_open_app_info);
- int currentUserId = ssp.getCurrentUser();
+ int currentUserId = mPreloadedUserId;
long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId);
long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(),
Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index c0691c1..e5b1afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -568,8 +568,6 @@
mTmpRanking.getImportance());
pw.print(indent);
pw.println(" notification=" + n.getNotification());
- pw.print(indent);
- pw.println(" tickerText=\"" + n.getNotification().tickerText + "\"");
}
private static boolean isSystemNotification(StatusBarNotification sbn) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index bbcbfd6..1a99636 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -454,7 +454,8 @@
|| row.getTranslationZ() > mAmbientState.getBaseZHeight()))) {
iconState.hidden = true;
}
- int shelfColor = icon.getStaticDrawableColor();
+ int backgroundColor = getBackgroundColorWithoutTint();
+ int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor);
if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor();
shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 3c7ddb5..89694b33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -35,6 +35,7 @@
import android.os.Parcelable;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
+import android.support.v4.graphics.ColorUtils;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
@@ -46,6 +47,7 @@
import android.view.animation.Interpolator;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
@@ -127,6 +129,8 @@
setColorInternal(newColor);
};
private final NotificationIconDozeHelper mDozer;
+ private int mContrastedDrawableColor;
+ private int mCachedContrastBackgroundColor = NO_COLOR;
public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
this(context, slot, sbn, false);
@@ -528,6 +532,7 @@
public void setStaticDrawableColor(int color) {
mDrawableColor = color;
setColorInternal(color);
+ updateContrastedStaticColor();
mIconColor = color;
mDozer.setColor(color);
}
@@ -580,6 +585,43 @@
return mDrawableColor;
}
+ /**
+ * A drawable color that passes GAR on a specific background.
+ * This value is cached.
+ *
+ * @param backgroundColor Background to test against.
+ * @return GAR safe version of {@link StatusBarIconView#getStaticDrawableColor()}.
+ */
+ int getContrastedStaticDrawableColor(int backgroundColor) {
+ if (mCachedContrastBackgroundColor != backgroundColor) {
+ mCachedContrastBackgroundColor = backgroundColor;
+ updateContrastedStaticColor();
+ }
+ return mContrastedDrawableColor;
+ }
+
+ private void updateContrastedStaticColor() {
+ if (Color.alpha(mCachedContrastBackgroundColor) != 255) {
+ mContrastedDrawableColor = mDrawableColor;
+ return;
+ }
+ // We'll modify the color if it doesn't pass GAR
+ int contrastedColor = mDrawableColor;
+ if (!NotificationColorUtil.satisfiesTextContrast(mCachedContrastBackgroundColor,
+ contrastedColor)) {
+ float[] hsl = new float[3];
+ ColorUtils.colorToHSL(mDrawableColor, hsl);
+ // This is basically a light grey, pushing the color will only distort it.
+ // Best thing to do in here is to fallback to the default color.
+ if (hsl[1] < 0.2f) {
+ contrastedColor = Notification.COLOR_DEFAULT;
+ }
+ contrastedColor = NotificationColorUtil.resolveContrastColor(mContext,
+ contrastedColor, mCachedContrastBackgroundColor);
+ }
+ mContrastedDrawableColor = contrastedColor;
+ }
+
public void setVisibleState(int state) {
setVisibleState(state, true /* animate */, null /* endRunnable */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 3f5f5a0..874f0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -31,8 +31,10 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.util.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -141,7 +143,8 @@
/**
* Returns true if there currently exist active high power location requests.
*/
- private boolean areActiveHighPowerLocationRequests() {
+ @VisibleForTesting
+ protected boolean areActiveHighPowerLocationRequests() {
List<AppOpsManager.PackageOps> packages
= mAppOpsManager.getPackagesForOps(mHighPowerRequestAppOpArray);
// AppOpsManager can return null when there is no requested data.
@@ -205,16 +208,14 @@
}
private void locationActiveChanged() {
- for (LocationChangeCallback cb : mSettingsChangeCallbacks) {
- cb.onLocationActiveChanged(mAreActiveLocationRequests);
- }
+ Utils.safeForeach(mSettingsChangeCallbacks,
+ cb -> cb.onLocationActiveChanged(mAreActiveLocationRequests));
}
private void locationSettingsChanged() {
boolean isEnabled = isLocationEnabled();
- for (LocationChangeCallback cb : mSettingsChangeCallbacks) {
- cb.onLocationSettingsChanged(isEnabled);
- }
+ Utils.safeForeach(mSettingsChangeCallbacks,
+ cb -> cb.onLocationSettingsChanged(isEnabled));
}
}
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 612a54a..b12fd1c 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -37,6 +37,7 @@
<uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 68f9cb05..7b2071c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -16,8 +16,10 @@
package com.android.systemui.statusbar;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,12 +35,14 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -100,4 +104,22 @@
assertFalse(mIconView.set(mStatusBarIcon));
}
+
+ @Test
+ public void testGetContrastedStaticDrawableColor() {
+ mIconView.setStaticDrawableColor(Color.DKGRAY);
+ int color = mIconView.getContrastedStaticDrawableColor(Color.WHITE);
+ assertEquals("Color should not change when we have enough contrast",
+ Color.DKGRAY, color);
+
+ mIconView.setStaticDrawableColor(Color.WHITE);
+ color = mIconView.getContrastedStaticDrawableColor(Color.WHITE);
+ assertTrue("Similar colors should be shifted to satisfy contrast",
+ NotificationColorUtil.satisfiesTextContrast(Color.WHITE, color));
+
+ mIconView.setStaticDrawableColor(Color.GREEN);
+ color = mIconView.getContrastedStaticDrawableColor(0xcc000000);
+ assertEquals("Transparent backgrounds should fallback to drawable color",
+ color, mIconView.getStaticDrawableColor());
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
new file mode 100644
index 0000000..a10bebf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.systemui.statusbar.policy;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.location.LocationManager;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class LocationControllerImplTest extends SysuiTestCase {
+
+ private LocationControllerImpl mLocationController;
+
+ @Before
+ public void setup() {
+ mLocationController = spy(new LocationControllerImpl(mContext,
+ TestableLooper.get(this).getLooper()));
+ }
+
+ @Test
+ public void testRemoveSelfActive_DoesNotCrash() {
+ LocationController.LocationChangeCallback callback = new LocationChangeCallback() {
+ @Override
+ public void onLocationActiveChanged(boolean active) {
+ mLocationController.removeCallback(this);
+ }
+ };
+ mLocationController.addCallback(callback);
+ mLocationController.addCallback(mock(LocationChangeCallback.class));
+
+ when(mLocationController.areActiveHighPowerLocationRequests()).thenReturn(false);
+ mLocationController.onReceive(mContext, new Intent(
+ LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION));
+ when(mLocationController.areActiveHighPowerLocationRequests()).thenReturn(true);
+ mLocationController.onReceive(mContext, new Intent(
+ LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION));
+
+ TestableLooper.get(this).processAllMessages();
+ }
+
+ @Test
+ public void testRemoveSelfSettings_DoesNotCrash() {
+ LocationController.LocationChangeCallback callback = new LocationChangeCallback() {
+ @Override
+ public void onLocationSettingsChanged(boolean isEnabled) {
+ mLocationController.removeCallback(this);
+ }
+ };
+ mLocationController.addCallback(callback);
+ mLocationController.addCallback(mock(LocationChangeCallback.class));
+
+ TestableLooper.get(this).processAllMessages();
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6c3a8a4..607b84c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4064,7 +4064,11 @@
aInfo.applicationInfo.uid, true);
if (app == null || app.instr == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
- mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);
+ final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
+ // For ANR debugging to verify if the user activity is the one that actually
+ // launched.
+ final String myReason = reason + ":" + userId + ":" + resolvedUserId;
+ mActivityStarter.startHomeActivityLocked(intent, aInfo, myReason);
}
} else {
Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index be30d5a..902353e 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -117,6 +117,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.text.TextUtils;
import android.util.EventLog;
+import android.util.Printer;
import android.util.Slog;
import com.android.internal.app.HeavyWeightSwitcherActivity;
@@ -129,6 +130,7 @@
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
/**
* Controller for interpreting how and then launching activities.
@@ -246,6 +248,34 @@
mUsingVr2dDisplay = false;
}
+ // TODO(b/38121026): Remove once issue has been resolved.
+ private class ActivityInfoAssignment {
+ final ActivityInfo info;
+ final String description;
+ final long timestamp;
+
+ public ActivityInfoAssignment(ActivityInfo info, String description) {
+ timestamp = System.currentTimeMillis();
+ this.info = info;
+ this.description = description;
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + " " + timestamp + ":" + description + ":" + describeInfo());
+ }
+
+ private String describeInfo() {
+ return "ActivityInfo[obj:" + info + " userId:"
+ + (info != null ? UserHandle.getUserId(info.applicationInfo.uid) : 0) + "]";
+ }
+ }
+
+ private List<ActivityInfoAssignment> mLastStartActivityInfoAssignments = new ArrayList<>();
+
+ private void addActivityInfoAssignment(ActivityInfo info, String description) {
+ mLastStartActivityInfoAssignments.add(new ActivityInfoAssignment(info, description));
+ }
+
ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) {
mService = service;
mSupervisor = supervisor;
@@ -253,6 +283,8 @@
mUsingVr2dDisplay = false;
}
+
+
int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
@@ -261,6 +293,8 @@
ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
TaskRecord inTask, String reason) {
+ mLastStartActivityInfoAssignments.clear();
+ addActivityInfoAssignment(aInfo, "startActivityLocked::initial");
if (TextUtils.isEmpty(reason)) {
throw new IllegalArgumentException("Need to specify a reason.");
@@ -452,6 +486,7 @@
intent = mInterceptor.mIntent;
rInfo = mInterceptor.mRInfo;
aInfo = mInterceptor.mAInfo;
+ addActivityInfoAssignment(aInfo, "startActivity::mInterceptor.mAInfo");
resolvedType = mInterceptor.mResolvedType;
inTask = mInterceptor.mInTask;
callingPid = mInterceptor.mCallingPid;
@@ -498,6 +533,7 @@
rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags,
null /*profilerInfo*/);
+ addActivityInfoAssignment(aInfo, "startActivity::isPermissionReviewRequired");
if (DEBUG_PERMISSIONS_REVIEW) {
Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
@@ -522,12 +558,14 @@
callingPid = realCallingPid;
aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
+ addActivityInfoAssignment(aInfo, "startActivity::auxiliaryInfo != null");
}
ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
mSupervisor, container, options, sourceRecord);
+ addActivityInfoAssignment(aInfo, "startActivity:: value used to create new activity");
if (outActivity != null) {
outActivity[0] = r;
}
@@ -2308,6 +2346,7 @@
pw.println(prefix + "ActivityStarter:");
prefix = prefix + " ";
+ pw.println(prefix + "mCurrentUser=" + mSupervisor.mCurrentUser);
pw.println(prefix + "mLastStartReason=" + mLastStartReason);
pw.println(prefix + "mLastStartActivityTimeMs="
+ DateFormat.getDateTimeInstance().format(new Date(mLastStartActivityTimeMs)));
@@ -2327,6 +2366,16 @@
pw.println(prefix + "mStartActivity:");
mStartActivity.dump(pw, prefix + " ");
}
+
+ if (!mLastStartActivityInfoAssignments.isEmpty()) {
+ pw.println(prefix + "mLastStartActivityInfoAssignments:");
+ for (ActivityInfoAssignment assignment : mLastStartActivityInfoAssignments) {
+ assignment.dump(pw, prefix);
+ /*pw.println(prefix + prefix + assignment.description + "@" + p
+ + ":" + assignment.info);*/
+ }
+ }
+
if (mIntent != null) {
pw.println(prefix + "mIntent=" + mIntent);
}
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index 2e0ec0b..a46c851 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
@@ -34,6 +35,7 @@
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Slog;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -111,22 +113,28 @@
* etc.
*/
void keyguardGoingAway(int flags) {
- if (mKeyguardShowing) {
- mWindowManager.deferSurfaceLayout();
- try {
- setKeyguardGoingAway(true);
- mWindowManager.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY,
- false /* alwaysKeepCurrent */, convertTransitFlags(flags),
- false /* forceOverride */);
- mService.updateSleepIfNeededLocked();
+ if (!mKeyguardShowing) {
+ return;
+ }
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway");
+ mWindowManager.deferSurfaceLayout();
+ try {
+ setKeyguardGoingAway(true);
+ mWindowManager.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY,
+ false /* alwaysKeepCurrent */, convertTransitFlags(flags),
+ false /* forceOverride */);
+ mService.updateSleepIfNeededLocked();
- // Some stack visibility might change (e.g. docked stack)
- mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
- mStackSupervisor.addStartingWindowsForVisibleActivities(true /* taskSwitch */);
- mWindowManager.executeAppTransition();
- } finally {
- mWindowManager.continueSurfaceLayout();
- }
+ // Some stack visibility might change (e.g. docked stack)
+ mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+ mStackSupervisor.addStartingWindowsForVisibleActivities(true /* taskSwitch */);
+ mWindowManager.executeAppTransition();
+ } finally {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway: surfaceLayout");
+ mWindowManager.continueSurfaceLayout();
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 3fd91dc..20711c0 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -778,6 +778,18 @@
return isEnabled();
}
};
+
+ /*
+ * A cycle of native_init() and native_cleanup() is needed so that callbacks are registered
+ * after bootup even when location is disabled. This will allow Emergency SUPL to work even
+ * when location is disabled before device restart.
+ * */
+ boolean isInitialized = native_init();
+ if(!isInitialized) {
+ Log.d(TAG, "Failed to initialize at bootup");
+ } else {
+ native_cleanup();
+ }
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9eda08f..28b6205 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7179,16 +7179,13 @@
*/
private List<ResolveInfo> applyPostResolutionFilter(List<ResolveInfo> resolveInfos,
String ephemeralPkgName) {
- // TODO: When adding on-demand split support for non-instant apps, remove this check
- // and always apply post filtering
- if (ephemeralPkgName == null) {
- return resolveInfos;
- }
for (int i = resolveInfos.size() - 1; i >= 0; i--) {
final ResolveInfo info = resolveInfos.get(i);
final boolean isEphemeralApp = info.activityInfo.applicationInfo.isInstantApp();
+ // TODO: When adding on-demand split support for non-instant apps, remove this check
+ // and always apply post filtering
// allow activities that are defined in the provided package
- if (isEphemeralApp && ephemeralPkgName.equals(info.activityInfo.packageName)) {
+ if (isEphemeralApp) {
if (info.activityInfo.splitName != null
&& !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames,
info.activityInfo.splitName)) {
@@ -7213,6 +7210,10 @@
}
continue;
}
+ // caller is a full app, don't need to apply any other filtering
+ if (ephemeralPkgName == null) {
+ continue;
+ }
// allow activities that have been explicitly exposed to ephemeral apps
if (!isEphemeralApp
&& ((info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) {
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index fe74947..5f34c60 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -614,8 +614,8 @@
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
return snapshot == null ? STARTING_WINDOW_TYPE_NONE
- : snapshotFillsWidth(snapshot) || fromRecents ? STARTING_WINDOW_TYPE_SNAPSHOT
- : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+ : snapshotOrientationSameAsDisplay(snapshot) || fromRecents
+ ? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else {
return STARTING_WINDOW_TYPE_NONE;
}
@@ -640,7 +640,7 @@
return true;
}
- private boolean snapshotFillsWidth(TaskSnapshot snapshot) {
+ private boolean snapshotOrientationSameAsDisplay(TaskSnapshot snapshot) {
if (snapshot == null) {
return false;
}
@@ -655,7 +655,9 @@
mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
stableInsets);
displayBounds.inset(stableInsets);
- return rect.width() >= displayBounds.width();
+ final boolean snapshotInLandscape = rect.width() >= rect.height();
+ final boolean displayInLandscape = displayBounds.width() >= displayBounds.height();
+ return snapshotInLandscape == displayInLandscape;
}
public void removeStartingWindow() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 6a7123c..7e575bf 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
@@ -37,6 +38,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.MergedConfiguration;
import android.util.Slog;
@@ -85,6 +87,7 @@
private boolean mClientDead = false;
private float mLastReportedAnimatorScale;
private String mPackageName;
+ private String mRelayoutTag;
public Session(WindowManagerService service, IWindowSessionCallback callback,
IInputMethodClient client, IInputContext inputContext) {
@@ -221,10 +224,12 @@
MergedConfiguration mergedConfiguration, Surface outSurface) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
int res = mService.relayoutWindow(this, window, seq, attrs,
requestedWidth, requestedHeight, viewFlags, flags,
outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
outStableInsets, outsets, outBackdropFrame, mergedConfiguration, outSurface);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
+ Binder.getCallingPid());
return res;
@@ -550,6 +555,7 @@
void windowAddedLocked(String packageName) {
mPackageName = packageName;
+ mRelayoutTag = "relayoutWindow: " + mPackageName;
if (mSurfaceSession == null) {
if (WindowManagerService.localLOGV) Slog.v(
TAG_WM, "First window added to " + this + ", creating SurfaceSession");
@@ -673,6 +679,7 @@
pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
+ pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ec7ab23..7a91d1e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -30,6 +30,7 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.myPid;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -884,11 +885,16 @@
}
void openSurfaceTransaction() {
- synchronized (mWindowMap) {
- if (mRoot.mSurfaceTraceEnabled) {
- mRoot.mRemoteEventTrace.openSurfaceTransaction();
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
+ synchronized (mWindowMap) {
+ if (mRoot.mSurfaceTraceEnabled) {
+ mRoot.mRemoteEventTrace.openSurfaceTransaction();
+ }
+ SurfaceControl.openTransaction();
}
- SurfaceControl.openTransaction();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -904,16 +910,21 @@
* blocks and we call it repeatedly, like we do for animations.
*/
void closeSurfaceTransaction(boolean withLockHeld) {
- synchronized (mWindowMap) {
- if (mRoot.mSurfaceTraceEnabled) {
- mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
+ synchronized (mWindowMap) {
+ if (mRoot.mSurfaceTraceEnabled) {
+ mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+ }
+ if (withLockHeld) {
+ SurfaceControl.closeTransaction();
+ }
}
- if (withLockHeld) {
+ if (!withLockHeld) {
SurfaceControl.closeTransaction();
}
- }
- if (!withLockHeld) {
- SurfaceControl.closeTransaction();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -1988,6 +1999,8 @@
(win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
|| !win.mAppToken.isClientHidden())) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
+
// We are about to create a surface, but we didn't run a layout yet. So better run
// a layout now that we already know the right size, as a resize call will make the
// surface transaction blocking until next vsync and slow us down.
@@ -1999,6 +2012,7 @@
}
result = win.relayoutVisibleWindow(mergedConfiguration, result, attrChanges,
oldVisibility);
+
try {
result = createSurfaceControl(outSurface, result, win, winAnimator);
} catch (Exception e) {
@@ -2018,7 +2032,10 @@
imMayMove = true;
}
win.adjustStartingWindowFlags();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} else {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_2");
+
winAnimator.mEnterAnimationPending = false;
winAnimator.mEnteringAnimation = false;
final boolean usingSavedSurfaceBeforeVisible =
@@ -2053,18 +2070,22 @@
// We already told the client to go invisible, but the message may not be
// handled yet, or it might want to draw a last frame. If we already have a
// surface, let the client use that, but don't create new surface at this point.
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
winAnimator.mSurfaceController.getSurface(outSurface);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} else {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
try {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+ win.mAttrs.getTitle());
outSurface.release();
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
+
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (focusMayChange) {
@@ -2101,8 +2122,11 @@
}
win.setDisplayLayoutNeeded();
- win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
+ win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+ "relayoutWindow: updateOrientationFromAppTokens");
configChanged = updateOrientationFromAppTokensLocked(false, displayId);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
// We may be deferring layout passes at the moment, but since the client is interested
// in the new out values right now we need to force a layout.
@@ -2155,7 +2179,9 @@
}
if (configChanged) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: sendNewConfiguration");
sendNewConfiguration(displayId);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
Binder.restoreCallingIdentity(origId);
return result;
@@ -2216,8 +2242,14 @@
if (!win.mHasSurface) {
result |= RELAYOUT_RES_SURFACE_CHANGED;
}
- WindowSurfaceController surfaceController = winAnimator.createSurfaceLocked(
- win.mAttrs.type, win.mOwnerUid);
+
+ WindowSurfaceController surfaceController;
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
+ surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
if (surfaceController != null) {
surfaceController.getSurface(outSurface);
if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " OUT SURFACE " + outSurface + ": copied");
@@ -2227,6 +2259,7 @@
Slog.w(TAG_WM, "Failed to create surface control for " + win);
outSurface.release();
}
+
return result;
}
@@ -2273,7 +2306,7 @@
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
// is running.
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
if (okToDisplay()) {
final DisplayContent displayContent = atoken.getTask().getDisplayContent();
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
@@ -2329,7 +2362,7 @@
} else {
atoken.mAppAnimator.clearAnimation();
}
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return atoken.mAppAnimator.animation != null;
}
@@ -2907,9 +2940,10 @@
}
public void setKeyguardGoingAway(boolean keyguardGoingAway) {
- synchronized (mWindowMap) {
- mKeyguardGoingAway = keyguardGoingAway;
- }
+// TODO: Use of this can be removed. Revert ag/I8369723d6a77f2c602f1ef080371fa7cd9ee094e
+// synchronized (mWindowMap) {
+// mKeyguardGoingAway = keyguardGoingAway;
+// }
}
// -------------------------------------------------------------
@@ -3412,7 +3446,7 @@
if (!mBootAnimationStopped) {
// Do this one time.
- Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
+ Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
try {
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
if (surfaceFlinger != null) {
@@ -3435,7 +3469,7 @@
}
EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());
- Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
+ Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
mDisplayEnabled = true;
if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!");
@@ -3666,12 +3700,12 @@
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
try {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
+ Trace.traceBegin(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 */);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -3857,6 +3891,8 @@
+ " alwaysSendConfiguration=" + alwaysSendConfiguration
+ " forceRelayout=" + forceRelayout);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
+
long origId = Binder.clearCallingIdentity();
try {
@@ -3865,20 +3901,28 @@
final int displayId;
synchronized (mWindowMap) {
final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
rotationChanged = displayContent.updateRotationUnchecked(
false /* inTransaction */);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (!rotationChanged || forceRelayout) {
displayContent.setLayoutNeeded();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+ "updateRotation: performSurfacePlacement");
mWindowPlacerLocked.performSurfacePlacement();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
displayId = displayContent.getDisplayId();
}
if (rotationChanged || alwaysSendConfiguration) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: sendNewConfiguration");
sendNewConfiguration(displayId);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
} finally {
Binder.restoreCallingIdentity(origId);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -5806,7 +5850,7 @@
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
WindowState newFocus = mRoot.computeFocusedWindow();
if (mCurrentFocus != newFocus) {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
// This check makes sure that we don't already have the focus
// change message pending.
mH.removeMessages(H.REPORT_FOCUS_CHANGE);
@@ -5882,7 +5926,7 @@
// other apps' UI.
displayContent.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index edbdf8b..27927e6 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -33,6 +34,7 @@
import android.graphics.Region;
import android.os.IBinder;
import android.os.Debug;
+import android.os.Trace;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -101,8 +103,10 @@
mSurfaceControl = new SurfaceTrace(
s, name, w, h, format, flags, windowType, ownerUid);
} else {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
mSurfaceControl = new SurfaceControl(
s, name, w, h, format, flags, windowType, ownerUid);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (mService.mRoot.mSurfaceTraceEnabled) {
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index f09ec6e..a94536c 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -714,7 +714,6 @@
return shouldLog;
}
- // TODO: Migrate all Log.e(...) to logError(...).
private void logError(String fmt, Object... args) {
final String msg = "ERROR " + String.format(fmt, args);
Log.e(mTag, msg);
@@ -1035,7 +1034,7 @@
}
private void doImmediateProvisioningFailure(int failureType) {
- if (DBG) { Log.e(mTag, "onProvisioningFailure(): " + failureType); }
+ logError("onProvisioningFailure(): %s", failureType);
recordMetric(failureType);
mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
}
@@ -1170,7 +1169,7 @@
case DhcpClient.CMD_ON_QUIT:
// Everything is already stopped.
- Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped).");
+ logError("Unexpected CMD_ON_QUIT (already stopped).");
break;
default:
@@ -1376,7 +1375,7 @@
break;
case CMD_START:
- Log.e(mTag, "ALERT: START received in StartedState. Please fix caller.");
+ logError("ALERT: START received in StartedState. Please fix caller.");
break;
case CMD_CONFIRM:
@@ -1475,13 +1474,13 @@
handleIPv4Failure();
break;
default:
- Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
+ logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
}
break;
case DhcpClient.CMD_ON_QUIT:
// DHCPv4 quit early for some reason.
- Log.e(mTag, "Unexpected CMD_ON_QUIT.");
+ logError("Unexpected CMD_ON_QUIT.");
mDhcpClient = null;
break;
diff --git a/test-runner/src/android/test/TestCaseUtil.java b/test-runner/src/android/test/TestCaseUtil.java
index dc053a2..1562909 100644
--- a/test-runner/src/android/test/TestCaseUtil.java
+++ b/test-runner/src/android/test/TestCaseUtil.java
@@ -40,16 +40,6 @@
private TestCaseUtil() {
}
- @SuppressWarnings("unchecked")
- public static List<String> getTestCaseNames(Test test, boolean flatten) {
- List<Test> tests = (List<Test>) getTests(test, flatten);
- List<String> testCaseNames = new ArrayList<>();
- for (Test aTest : tests) {
- testCaseNames.add(getTestName(aTest));
- }
- return testCaseNames;
- }
-
public static List<? extends Test> getTests(Test test, boolean flatten) {
return getTests(test, flatten, new HashSet<Class<?>>());
}
@@ -92,7 +82,7 @@
return testCases;
}
- private static Test invokeSuiteMethodIfPossible(Class testClass,
+ static Test invokeSuiteMethodIfPossible(Class testClass,
Set<Class<?>> seen) {
try {
Method suiteMethod = testClass.getMethod(
@@ -120,7 +110,7 @@
return null;
}
- public static String getTestName(Test test) {
+ static String getTestName(Test test) {
if (test instanceof TestCase) {
TestCase testCase = (TestCase) test;
return testCase.getName();
@@ -138,34 +128,4 @@
}
return "";
}
-
- public static Test getTestAtIndex(TestSuite testSuite, int position) {
- int index = 0;
- Enumeration enumeration = testSuite.tests();
- while (enumeration.hasMoreElements()) {
- Test test = (Test) enumeration.nextElement();
- if (index == position) {
- return test;
- }
- index++;
- }
- return null;
- }
-
- public static TestSuite createTestSuite(Class<? extends Test> testClass)
- throws InstantiationException, IllegalAccessException {
-
- Test test = invokeSuiteMethodIfPossible(testClass,
- new HashSet<Class<?>>());
- if (test == null) {
- return new TestSuite(testClass);
-
- } else if (TestCase.class.isAssignableFrom(test.getClass())) {
- TestSuite testSuite = new TestSuite(test.getClass().getName());
- testSuite.addTest(test);
- return testSuite;
- }
-
- return (TestSuite) test;
- }
}
diff --git a/test-runner/src/android/test/TestPrinter.java b/test-runner/src/android/test/TestPrinter.java
index a23f06d..01d392d 100644
--- a/test-runner/src/android/test/TestPrinter.java
+++ b/test-runner/src/android/test/TestPrinter.java
@@ -21,7 +21,6 @@
import junit.framework.TestListener;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
/**
@@ -34,52 +33,37 @@
* {@hide} Not needed for 1.0 SDK.
*/
@Deprecated
-public class TestPrinter implements TestRunner.Listener, TestListener {
+class TestPrinter implements TestListener {
private String mTag;
private boolean mOnlyFailures;
private Set<String> mFailedTests = new HashSet<String>();
- public TestPrinter(String tag, boolean onlyFailures) {
+ TestPrinter(String tag, boolean onlyFailures) {
mTag = tag;
mOnlyFailures = onlyFailures;
}
- public void started(String className) {
+ private void started(String className) {
if (!mOnlyFailures) {
Log.i(mTag, "started: " + className);
}
}
- public void finished(String className) {
+ private void finished(String className) {
if (!mOnlyFailures) {
Log.i(mTag, "finished: " + className);
}
}
- public void performance(String className,
- long itemTimeNS, int iterations,
- List<TestRunner.IntermediateTime> intermediates) {
- Log.i(mTag, "perf: " + className + " = " + itemTimeNS + "ns/op (done "
- + iterations + " times)");
- if (intermediates != null && intermediates.size() > 0) {
- int N = intermediates.size();
- for (int i = 0; i < N; i++) {
- TestRunner.IntermediateTime time = intermediates.get(i);
- Log.i(mTag, " intermediate: " + time.name + " = "
- + time.timeInNS + "ns");
- }
- }
- }
-
- public void passed(String className) {
+ private void passed(String className) {
if (!mOnlyFailures) {
Log.i(mTag, "passed: " + className);
}
}
- public void failed(String className, Throwable exception) {
+ private void failed(String className, Throwable exception) {
Log.i(mTag, "failed: " + className);
Log.i(mTag, "----- begin exception -----");
Log.i(mTag, "", exception);
diff --git a/test-runner/src/android/test/TestRunner.java b/test-runner/src/android/test/TestRunner.java
deleted file mode 100644
index ff045c3..0000000
--- a/test-runner/src/android/test/TestRunner.java
+++ /dev/null
@@ -1,725 +0,0 @@
-/*
- * Copyright (C) 2006 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.test;
-
-import android.content.Context;
-import android.util.Log;
-import android.os.Debug;
-import android.os.SystemClock;
-
-import java.io.File;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.TestSuite;
-import junit.framework.TestListener;
-import junit.framework.Test;
-import junit.framework.TestResult;
-
-/**
- * Support class that actually runs a test. Android uses this class,
- * and you probably will not need to instantiate, extend, or call this
- * class yourself. See the full {@link android.test} package description
- * to learn more about testing Android applications.
- *
- * {@hide} Not needed for 1.0 SDK.
- */
-@Deprecated
-public class TestRunner implements PerformanceTestCase.Intermediates {
- public static final int REGRESSION = 0;
- public static final int PERFORMANCE = 1;
- public static final int PROFILING = 2;
-
- public static final int CLEARSCREEN = 0;
- private static final String TAG = "TestHarness";
- private Context mContext;
-
- private int mMode = REGRESSION;
-
- private List<Listener> mListeners = new ArrayList<>();
- private int mPassed;
- private int mFailed;
-
- private int mInternalIterations;
- private long mStartTime;
- private long mEndTime;
-
- private String mClassName;
-
- List<IntermediateTime> mIntermediates = null;
-
- private static Class mRunnableClass;
- private static Class mJUnitClass;
-
- static {
- try {
- mRunnableClass = Class.forName("java.lang.Runnable", false, null);
- mJUnitClass = Class.forName("junit.framework.TestCase", false, null);
- } catch (ClassNotFoundException ex) {
- throw new RuntimeException("shouldn't happen", ex);
- }
- }
-
- public class JunitTestSuite extends TestSuite implements TestListener {
- boolean mError = false;
-
- public JunitTestSuite() {
- super();
- }
-
- @Override
- public void run(TestResult result) {
- result.addListener(this);
- super.run(result);
- result.removeListener(this);
- }
-
- /**
- * Implemented method of the interface TestListener which will listen for the
- * start of a test.
- *
- * @param test
- */
- public void startTest(Test test) {
- started(test.toString());
- }
-
- /**
- * Implemented method of the interface TestListener which will listen for the
- * end of the test.
- *
- * @param test
- */
- public void endTest(Test test) {
- finished(test.toString());
- if (!mError) {
- passed(test.toString());
- }
- }
-
- /**
- * Implemented method of the interface TestListener which will listen for an
- * mError while running the test.
- *
- * @param test
- */
- public void addError(Test test, Throwable t) {
- mError = true;
- failed(test.toString(), t);
- }
-
- public void addFailure(Test test, junit.framework.AssertionFailedError t) {
- mError = true;
- failed(test.toString(), t);
- }
- }
-
- /**
- * Listener.performance() 'intermediates' argument is a list of these.
- */
- public static class IntermediateTime {
- public IntermediateTime(String name, long timeInNS) {
- this.name = name;
- this.timeInNS = timeInNS;
- }
-
- public String name;
- public long timeInNS;
- }
-
- /**
- * Support class that receives status on test progress. You should not need to
- * extend this interface yourself.
- */
- public interface Listener {
- void started(String className);
- void finished(String className);
- void performance(String className,
- long itemTimeNS, int iterations,
- List<IntermediateTime> itermediates);
- void passed(String className);
- void failed(String className, Throwable execption);
- }
-
- public TestRunner(Context context) {
- mContext = context;
- }
-
- public void addListener(Listener listener) {
- mListeners.add(listener);
- }
-
- public void startProfiling() {
- File file = new File("/tmp/trace");
- file.mkdir();
- String base = "/tmp/trace/" + mClassName + ".dmtrace";
- Debug.startMethodTracing(base, 8 * 1024 * 1024);
- }
-
- public void finishProfiling() {
- Debug.stopMethodTracing();
- }
-
- private void started(String className) {
-
- int count = mListeners.size();
- for (int i = 0; i < count; i++) {
- mListeners.get(i).started(className);
- }
- }
-
- private void finished(String className) {
- int count = mListeners.size();
- for (int i = 0; i < count; i++) {
- mListeners.get(i).finished(className);
- }
- }
-
- private void performance(String className,
- long itemTimeNS,
- int iterations,
- List<IntermediateTime> intermediates) {
- int count = mListeners.size();
- for (int i = 0; i < count; i++) {
- mListeners.get(i).performance(className,
- itemTimeNS,
- iterations,
- intermediates);
- }
- }
-
- public void passed(String className) {
- mPassed++;
- int count = mListeners.size();
- for (int i = 0; i < count; i++) {
- mListeners.get(i).passed(className);
- }
- }
-
- public void failed(String className, Throwable exception) {
- mFailed++;
- int count = mListeners.size();
- for (int i = 0; i < count; i++) {
- mListeners.get(i).failed(className, exception);
- }
- }
-
- public int passedCount() {
- return mPassed;
- }
-
- public int failedCount() {
- return mFailed;
- }
-
- public void run(String[] classes) {
- for (String cl : classes) {
- run(cl);
- }
- }
-
- public void setInternalIterations(int count) {
- mInternalIterations = count;
- }
-
- public void startTiming(boolean realTime) {
- if (realTime) {
- mStartTime = System.currentTimeMillis();
- } else {
- mStartTime = SystemClock.currentThreadTimeMillis();
- }
- }
-
- public void addIntermediate(String name) {
- addIntermediate(name, (System.currentTimeMillis() - mStartTime) * 1000000);
- }
-
- public void addIntermediate(String name, long timeInNS) {
- mIntermediates.add(new IntermediateTime(name, timeInNS));
- }
-
- public void finishTiming(boolean realTime) {
- if (realTime) {
- mEndTime = System.currentTimeMillis();
- } else {
- mEndTime = SystemClock.currentThreadTimeMillis();
- }
- }
-
- public void setPerformanceMode(int mode) {
- mMode = mode;
- }
-
- private void missingTest(String className, Throwable e) {
- started(className);
- finished(className);
- failed(className, e);
- }
-
- /*
- This class determines if more suites are added to this class then adds all individual
- test classes to a test suite for run
- */
- public void run(String className) {
- try {
- mClassName = className;
- Class clazz = mContext.getClassLoader().loadClass(className);
- Method method = getChildrenMethod(clazz);
- if (method != null) {
- String[] children = getChildren(method);
- run(children);
- } else if (mRunnableClass.isAssignableFrom(clazz)) {
- Runnable test = (Runnable) clazz.newInstance();
- TestCase testcase = null;
- if (test instanceof TestCase) {
- testcase = (TestCase) test;
- }
- Throwable e = null;
- boolean didSetup = false;
- started(className);
- try {
- if (testcase != null) {
- testcase.setUp(mContext);
- didSetup = true;
- }
- if (mMode == PERFORMANCE) {
- runInPerformanceMode(test, className, false, className);
- } else if (mMode == PROFILING) {
- //Need a way to mark a test to be run in profiling mode or not.
- startProfiling();
- test.run();
- finishProfiling();
- } else {
- test.run();
- }
- } catch (Throwable ex) {
- e = ex;
- }
- if (testcase != null && didSetup) {
- try {
- testcase.tearDown();
- } catch (Throwable ex) {
- e = ex;
- }
- }
- finished(className);
- if (e == null) {
- passed(className);
- } else {
- failed(className, e);
- }
- } else if (mJUnitClass.isAssignableFrom(clazz)) {
- Throwable e = null;
- //Create a Junit Suite.
- JunitTestSuite suite = new JunitTestSuite();
- Method[] methods = getAllTestMethods(clazz);
- for (Method m : methods) {
- junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance();
- test.setName(m.getName());
-
- if (test instanceof AndroidTestCase) {
- AndroidTestCase testcase = (AndroidTestCase) test;
- try {
- testcase.setContext(mContext);
- testcase.setTestContext(mContext);
- } catch (Exception ex) {
- Log.i("TestHarness", ex.toString());
- }
- }
- suite.addTest(test);
- }
- if (mMode == PERFORMANCE) {
- final int testCount = suite.testCount();
-
- for (int j = 0; j < testCount; j++) {
- Test test = suite.testAt(j);
- started(test.toString());
- try {
- runInPerformanceMode(test, className, true, test.toString());
- } catch (Throwable ex) {
- e = ex;
- }
- finished(test.toString());
- if (e == null) {
- passed(test.toString());
- } else {
- failed(test.toString(), e);
- }
- }
- } else if (mMode == PROFILING) {
- //Need a way to mark a test to be run in profiling mode or not.
- startProfiling();
- junit.textui.TestRunner.run(suite);
- finishProfiling();
- } else {
- junit.textui.TestRunner.run(suite);
- }
- } else {
- System.out.println("Test wasn't Runnable and didn't have a"
- + " children method: " + className);
- }
- } catch (ClassNotFoundException e) {
- Log.e("ClassNotFoundException for " + className, e.toString());
- if (isJunitTest(className)) {
- runSingleJunitTest(className);
- } else {
- missingTest(className, e);
- }
- } catch (InstantiationException e) {
- System.out.println("InstantiationException for " + className);
- missingTest(className, e);
- } catch (IllegalAccessException e) {
- System.out.println("IllegalAccessException for " + className);
- missingTest(className, e);
- }
- }
-
- public void runInPerformanceMode(Object testCase, String className, boolean junitTest,
- String testNameInDb) throws Exception {
- boolean increaseIterations = true;
- int iterations = 1;
- long duration = 0;
- mIntermediates = null;
-
- mInternalIterations = 1;
- Class clazz = mContext.getClassLoader().loadClass(className);
- Object perftest = clazz.newInstance();
-
- PerformanceTestCase perftestcase = null;
- if (perftest instanceof PerformanceTestCase) {
- perftestcase = (PerformanceTestCase) perftest;
- // only run the test if it is not marked as a performance only test
- if (mMode == REGRESSION && perftestcase.isPerformanceOnly()) return;
- }
-
- // First force GCs, to avoid GCs happening during out
- // test and skewing its time.
- Runtime.getRuntime().runFinalization();
- Runtime.getRuntime().gc();
-
- if (perftestcase != null) {
- mIntermediates = new ArrayList<IntermediateTime>();
- iterations = perftestcase.startPerformance(this);
- if (iterations > 0) {
- increaseIterations = false;
- } else {
- iterations = 1;
- }
- }
-
- // Pause briefly to let things settle down...
- Thread.sleep(1000);
- do {
- mEndTime = 0;
- if (increaseIterations) {
- // Test case does not implement
- // PerformanceTestCase or returned 0 iterations,
- // so we take care of measure the whole test time.
- mStartTime = SystemClock.currentThreadTimeMillis();
- } else {
- // Try to make it obvious if the test case
- // doesn't call startTiming().
- mStartTime = 0;
- }
-
- if (junitTest) {
- for (int i = 0; i < iterations; i++) {
- junit.textui.TestRunner.run((junit.framework.Test) testCase);
- }
- } else {
- Runnable test = (Runnable) testCase;
- for (int i = 0; i < iterations; i++) {
- test.run();
- }
- }
-
- long endTime = mEndTime;
- if (endTime == 0) {
- endTime = SystemClock.currentThreadTimeMillis();
- }
-
- duration = endTime - mStartTime;
- if (!increaseIterations) {
- break;
- }
- if (duration <= 1) {
- iterations *= 1000;
- } else if (duration <= 10) {
- iterations *= 100;
- } else if (duration < 100) {
- iterations *= 10;
- } else if (duration < 1000) {
- iterations *= (int) ((1000 / duration) + 2);
- } else {
- break;
- }
- } while (true);
-
- if (duration != 0) {
- iterations *= mInternalIterations;
- performance(testNameInDb, (duration * 1000000) / iterations,
- iterations, mIntermediates);
- }
- }
-
- public void runSingleJunitTest(String className) {
- Throwable excep = null;
- int index = className.lastIndexOf('$');
- String testName = "";
- String originalClassName = className;
- if (index >= 0) {
- className = className.substring(0, index);
- testName = originalClassName.substring(index + 1);
- }
- try {
- Class clazz = mContext.getClassLoader().loadClass(className);
- if (mJUnitClass.isAssignableFrom(clazz)) {
- junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance();
- JunitTestSuite newSuite = new JunitTestSuite();
- test.setName(testName);
-
- if (test instanceof AndroidTestCase) {
- AndroidTestCase testcase = (AndroidTestCase) test;
- try {
- testcase.setContext(mContext);
- } catch (Exception ex) {
- Log.w(TAG, "Exception encountered while trying to set the context.", ex);
- }
- }
- newSuite.addTest(test);
-
- if (mMode == PERFORMANCE) {
- try {
- started(test.toString());
- runInPerformanceMode(test, className, true, test.toString());
- finished(test.toString());
- if (excep == null) {
- passed(test.toString());
- } else {
- failed(test.toString(), excep);
- }
- } catch (Throwable ex) {
- excep = ex;
- }
-
- } else if (mMode == PROFILING) {
- startProfiling();
- junit.textui.TestRunner.run(newSuite);
- finishProfiling();
- } else {
- junit.textui.TestRunner.run(newSuite);
- }
- }
- } catch (ClassNotFoundException e) {
- Log.e("TestHarness", "No test case to run", e);
- } catch (IllegalAccessException e) {
- Log.e("TestHarness", "Illegal Access Exception", e);
- } catch (InstantiationException e) {
- Log.e("TestHarness", "Instantiation Exception", e);
- }
- }
-
- public static Method getChildrenMethod(Class clazz) {
- try {
- return clazz.getMethod("children", (Class[]) null);
- } catch (NoSuchMethodException e) {
- }
-
- return null;
- }
-
- public static Method getChildrenMethod(Context c, String className) {
- try {
- return getChildrenMethod(c.getClassLoader().loadClass(className));
- } catch (ClassNotFoundException e) {
- }
- return null;
- }
-
- public static String[] getChildren(Context c, String className) {
- Method m = getChildrenMethod(c, className);
- String[] testChildren = getTestChildren(c, className);
- if (m == null & testChildren == null) {
- throw new RuntimeException("couldn't get children method for "
- + className);
- }
- if (m != null) {
- String[] children = getChildren(m);
- if (testChildren != null) {
- String[] allChildren = new String[testChildren.length + children.length];
- System.arraycopy(children, 0, allChildren, 0, children.length);
- System.arraycopy(testChildren, 0, allChildren, children.length, testChildren.length);
- return allChildren;
- } else {
- return children;
- }
- } else {
- if (testChildren != null) {
- return testChildren;
- }
- }
- return null;
- }
-
- public static String[] getChildren(Method m) {
- try {
- if (!Modifier.isStatic(m.getModifiers())) {
- throw new RuntimeException("children method is not static");
- }
- return (String[]) m.invoke(null, (Object[]) null);
- } catch (IllegalAccessException e) {
- } catch (InvocationTargetException e) {
- }
- return new String[0];
- }
-
- public static String[] getTestChildren(Context c, String className) {
- try {
- Class clazz = c.getClassLoader().loadClass(className);
-
- if (mJUnitClass.isAssignableFrom(clazz)) {
- return getTestChildren(clazz);
- }
- } catch (ClassNotFoundException e) {
- Log.e("TestHarness", "No class found", e);
- }
- return null;
- }
-
- public static String[] getTestChildren(Class clazz) {
- Method[] methods = getAllTestMethods(clazz);
-
- String[] onScreenTestNames = new String[methods.length];
- int index = 0;
- for (Method m : methods) {
- onScreenTestNames[index] = clazz.getName() + "$" + m.getName();
- index++;
- }
- return onScreenTestNames;
- }
-
- public static Method[] getAllTestMethods(Class clazz) {
- Method[] allMethods = clazz.getDeclaredMethods();
- int numOfMethods = 0;
- for (Method m : allMethods) {
- boolean mTrue = isTestMethod(m);
- if (mTrue) {
- numOfMethods++;
- }
- }
- int index = 0;
- Method[] testMethods = new Method[numOfMethods];
- for (Method m : allMethods) {
- boolean mTrue = isTestMethod(m);
- if (mTrue) {
- testMethods[index] = m;
- index++;
- }
- }
- return testMethods;
- }
-
- private static boolean isTestMethod(Method m) {
- return m.getName().startsWith("test") &&
- m.getReturnType() == void.class &&
- m.getParameterTypes().length == 0;
- }
-
- public static int countJunitTests(Class clazz) {
- Method[] allTestMethods = getAllTestMethods(clazz);
- int numberofMethods = allTestMethods.length;
-
- return numberofMethods;
- }
-
- public static boolean isTestSuite(Context c, String className) {
- boolean childrenMethods = getChildrenMethod(c, className) != null;
-
- try {
- Class clazz = c.getClassLoader().loadClass(className);
- if (mJUnitClass.isAssignableFrom(clazz)) {
- int numTests = countJunitTests(clazz);
- if (numTests > 0)
- childrenMethods = true;
- }
- } catch (ClassNotFoundException e) {
- }
- return childrenMethods;
- }
-
-
- public boolean isJunitTest(String className) {
- int index = className.lastIndexOf('$');
- if (index >= 0) {
- className = className.substring(0, index);
- }
- try {
- Class clazz = mContext.getClassLoader().loadClass(className);
- if (mJUnitClass.isAssignableFrom(clazz)) {
- return true;
- }
- } catch (ClassNotFoundException e) {
- }
- return false;
- }
-
- /**
- * Returns the number of tests that will be run if you try to do this.
- */
- public static int countTests(Context c, String className) {
- try {
- Class clazz = c.getClassLoader().loadClass(className);
- Method method = getChildrenMethod(clazz);
- if (method != null) {
-
- String[] children = getChildren(method);
- int rv = 0;
- for (String child : children) {
- rv += countTests(c, child);
- }
- return rv;
- } else if (mRunnableClass.isAssignableFrom(clazz)) {
- return 1;
- } else if (mJUnitClass.isAssignableFrom(clazz)) {
- return countJunitTests(clazz);
- }
- } catch (ClassNotFoundException e) {
- return 1; // this gets the count right, because either this test
- // is missing, and it will fail when run or it is a single Junit test to be run.
- }
- return 0;
- }
-
- /**
- * Returns a title to display given the className of a test.
- * <p/>
- * <p>Currently this function just returns the portion of the
- * class name after the last '.'
- */
- public static String getTitle(String className) {
- int indexDot = className.lastIndexOf('.');
- int indexDollar = className.lastIndexOf('$');
- int index = indexDot > indexDollar ? indexDot : indexDollar;
- if (index >= 0) {
- className = className.substring(index + 1);
- }
- return className;
- }
-}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index bfc2d72..24e7bff 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -810,6 +810,12 @@
/** {@hide} */
@Override
+ public boolean canLoadUnsafeResources() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
public IBinder getActivityToken() {
throw new UnsupportedOperationException();
}
diff --git a/test-runner/tests/src/android/test/TestCaseUtilTest.java b/test-runner/tests/src/android/test/TestCaseUtilTest.java
index 9d12eaf..6d424b0 100644
--- a/test-runner/tests/src/android/test/TestCaseUtilTest.java
+++ b/test-runner/tests/src/android/test/TestCaseUtilTest.java
@@ -16,6 +16,8 @@
package android.test;
+import java.util.ArrayList;
+import java.util.HashSet;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
@@ -24,38 +26,50 @@
public class TestCaseUtilTest extends TestCase {
- public void testGetTestCaseNamesForTestSuiteWithSuiteMethod() throws Exception {
+ @SuppressWarnings("unchecked")
+ private static List<String> getTestCaseNames(Test test) {
+ List<Test> tests = (List<Test>) TestCaseUtil.getTests(test, false);
+ List<String> testCaseNames = new ArrayList<>();
+ for (Test aTest : tests) {
+ testCaseNames.add(TestCaseUtil.getTestName(aTest));
+ }
+ return testCaseNames;
+ }
+
+ public void testGetTests_ForTestSuiteWithSuiteMethod() throws Exception {
TestSuite testSuite = new TwoTestsInTestSuite();
- List<String> testCaseNames = TestCaseUtil.getTestCaseNames(testSuite, false);
+ List<String> testCaseNames = getTestCaseNames(testSuite);
assertEquals(0, testCaseNames.size());
}
- public void testGetTestCaseNamesForTestCaseWithSuiteMethod() throws Exception {
+ public void testGetTests_ForTestCaseWithSuiteMethod() throws Exception {
TestCase testCase = new OneTestTestCaseWithSuite();
- List<String> testCaseNames = TestCaseUtil.getTestCaseNames(testCase, false);
+ List<String> testCaseNames = getTestCaseNames(testCase);
assertEquals(1, testCaseNames.size());
assertTrue(testCaseNames.get(0).endsWith("testOne"));
}
- public void testCreateTestForTestCase() throws Exception {
- Test test = TestCaseUtil.createTestSuite(OneTestTestCase.class);
- assertEquals(1, test.countTestCases());
+ public void testInvokeSuiteMethodIfPossible_ForTestCase() throws Exception {
+ Test test = TestCaseUtil.invokeSuiteMethodIfPossible(OneTestTestCase.class, new HashSet<>());
+ assertNull(test);
}
-
- public void testCreateTestForTestSuiteWithSuiteMethod() throws Exception {
- Test test = TestCaseUtil.createTestSuite(TwoTestsInTestSuite.class);
+
+ public void testInvokeSuiteMethodIfPossible_ForTestSuiteWithSuiteMethod() throws Exception {
+ Test test = TestCaseUtil.invokeSuiteMethodIfPossible(TwoTestsInTestSuite.class, new HashSet<>());
+ assertNotNull(test);
assertEquals(2, test.countTestCases());
}
- public void testCreateTestForTestCaseWithSuiteMethod() throws Exception {
- Test test = TestCaseUtil.createTestSuite(OneTestTestCaseWithSuite.class);
+ public void testInvokeSuiteMethodIfPossible_ForTestCaseWithSuiteMethod() throws Exception {
+ Test test = TestCaseUtil.invokeSuiteMethodIfPossible(OneTestTestCaseWithSuite.class, new HashSet<>());
+ assertNotNull(test);
assertEquals(1, test.countTestCases());
}
-
+
public void testReturnEmptyStringForTestSuiteWithNoName() throws Exception {
assertEquals("", TestCaseUtil.getTestName(new TestSuite()));
}
diff --git a/tests/net/java/android/net/util/SharedLogTest.java b/tests/net/java/android/net/util/SharedLogTest.java
index 7fd7a63..3957cb0 100644
--- a/tests/net/java/android/net/util/SharedLogTest.java
+++ b/tests/net/java/android/net/util/SharedLogTest.java
@@ -33,9 +33,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SharedLogTest {
- private static final String TIMESTAMP_PATTERN =
- "^[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]";
- private static final String TIMESTAMP = "mm-dd HH:MM:SS.xxx";
+ private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}\\.\\d{3}";
+ private static final String TIMESTAMP = "HH:MM:SS.xxx";
@Test
public void testBasicOperation() {
@@ -53,12 +52,12 @@
logLevel2a.mark("ok: last post");
final String[] expected = {
- TIMESTAMP + " - MARK first post!",
- TIMESTAMP + " - [twoB] ERROR 2b or not 2b",
- TIMESTAMP + " - [twoA] WARN second post?",
- TIMESTAMP + " - still logging",
- TIMESTAMP + " - [twoA.three] 3 >> 2",
- TIMESTAMP + " - [twoA] MARK ok: last post",
+ " - MARK first post!",
+ " - [twoB] ERROR 2b or not 2b",
+ " - [twoA] WARN second post?",
+ " - still logging",
+ " - [twoA.three] 3 >> 2",
+ " - [twoA] MARK ok: last post",
};
// Verify the logs are all there and in the correct order.
verifyLogLines(expected, logTop);
@@ -82,13 +81,12 @@
final String[] lines = dumpOutput.split("\n");
assertEquals(expected.length, lines.length);
- for (int i = 0; i < lines.length; i++) {
- // Fix up the timestamps.
- lines[i] = lines[i].replaceAll(TIMESTAMP_PATTERN, TIMESTAMP);
- }
-
for (int i = 0; i < expected.length; i++) {
- assertEquals(expected[i], lines[i]);
+ String got = lines[i];
+ String want = expected[i];
+ assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want));
+ assertTrue(String.format("'%s' did not contain a HH:MM:SS.xxx timestamp", got),
+ got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP));
}
}
}
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 8bd924e..1fc5d72 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
@@ -2008,6 +2008,11 @@
return false;
}
+ @Override
+ public boolean canLoadUnsafeResources() {
+ return false;
+ }
+
/**
* The cached value depends on
* <ol>
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
new file mode 100644
index 0000000..0aed0ce
--- /dev/null
+++ b/tools/locked_region_code_injection/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_JAR_MANIFEST := manifest.txt
+LOCAL_MODULE := lockedregioncodeinjection
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ asm-5.2 \
+ asm-commons-5.2 \
+ asm-tree-5.2 \
+ asm-analysis-5.2
+
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/locked_region_code_injection/manifest.txt b/tools/locked_region_code_injection/manifest.txt
new file mode 100644
index 0000000..4b9de00
--- /dev/null
+++ b/tools/locked_region_code_injection/manifest.txt
@@ -0,0 +1 @@
+Main-Class: lockedregioncodeinjection.Main
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
new file mode 100644
index 0000000..9374f23
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -0,0 +1,241 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.TryCatchBlockSorter;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+import org.objectweb.asm.tree.analysis.Analyzer;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.objectweb.asm.tree.analysis.BasicValue;
+import org.objectweb.asm.tree.analysis.Frame;
+
+/**
+ * This visitor does two things:
+ *
+ * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and insert the corresponding pre
+ * and post methods calls should it matches one of the given target type in the Configuration.
+ *
+ * 2. Find all methods that are synchronized and insert pre method calls in the beginning and post
+ * method calls just before all return instructions.
+ */
+class LockFindingClassVisitor extends ClassVisitor {
+ private String className = null;
+ private final List<LockTarget> targets;
+
+ public LockFindingClassVisitor(List<LockTarget> targets, ClassVisitor chain) {
+ super(Utils.ASM_VERSION, chain);
+ this.targets = targets;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ assert this.className != null;
+ MethodNode mn = new TryCatchBlockSorter(null, access, name, desc, signature, exceptions);
+ MethodVisitor chain = super.visitMethod(access, name, desc, signature, exceptions);
+ return new LockFindingMethodVisitor(this.className, mn, chain);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ this.className = name;
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ class LockFindingMethodVisitor extends MethodVisitor {
+ private String owner;
+ private MethodVisitor chain;
+
+ public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
+ super(Opcodes.ASM5, mn);
+ assert owner != null;
+ this.owner = owner;
+ this.chain = chain;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void visitEnd() {
+ MethodNode mn = (MethodNode) mv;
+
+ Analyzer a = new Analyzer(new LockTargetStateAnalysis(targets));
+
+ LockTarget ownerMonitor = null;
+ if ((mn.access & Opcodes.ACC_SYNCHRONIZED) != 0) {
+ for (LockTarget t : targets) {
+ if (t.getTargetDesc().equals("L" + owner + ";")) {
+ ownerMonitor = t;
+ }
+ }
+ }
+
+ try {
+ a.analyze(owner, mn);
+ } catch (AnalyzerException e) {
+ e.printStackTrace();
+ }
+ InsnList instructions = mn.instructions;
+
+ Frame[] frames = a.getFrames();
+ List<Frame> frameMap = new LinkedList<>();
+ frameMap.addAll(Arrays.asList(frames));
+
+ List<List<TryCatchBlockNode>> handlersMap = new LinkedList<>();
+
+ for (int i = 0; i < instructions.size(); i++) {
+ handlersMap.add(a.getHandlers(i));
+ }
+
+ if (ownerMonitor != null) {
+ AbstractInsnNode s = instructions.getFirst();
+ MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
+ ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false);
+ insertMethodCallBefore(mn, frameMap, handlersMap, s, 0, call);
+ }
+
+ for (int i = 0; i < instructions.size(); i++) {
+ AbstractInsnNode s = instructions.get(i);
+
+ if (s.getOpcode() == Opcodes.MONITORENTER) {
+ Frame f = frameMap.get(i);
+ BasicValue operand = (BasicValue) f.getStack(f.getStackSize() - 1);
+ if (operand instanceof LockTargetState) {
+ LockTargetState state = (LockTargetState) operand;
+ for (int j = 0; j < state.getTargets().size(); j++) {
+ LockTarget target = state.getTargets().get(j);
+ MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
+ target.getPreOwner(), target.getPreMethod(), "()V", false);
+ insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
+ }
+ }
+ }
+
+ if (s.getOpcode() == Opcodes.MONITOREXIT) {
+ Frame f = frameMap.get(i);
+ BasicValue operand = (BasicValue) f.getStack(f.getStackSize() - 1);
+ if (operand instanceof LockTargetState) {
+ LockTargetState state = (LockTargetState) operand;
+ for (int j = 0; j < state.getTargets().size(); j++) {
+ LockTarget target = state.getTargets().get(j);
+ MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
+ target.getPostOwner(), target.getPostMethod(), "()V", false);
+ insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
+ }
+ }
+ }
+
+ if (ownerMonitor != null && (s.getOpcode() == Opcodes.RETURN
+ || s.getOpcode() == Opcodes.ARETURN || s.getOpcode() == Opcodes.DRETURN
+ || s.getOpcode() == Opcodes.FRETURN || s.getOpcode() == Opcodes.IRETURN)) {
+ MethodInsnNode call =
+ new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(),
+ ownerMonitor.getPostMethod(), "()V", false);
+ insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call);
+ i++; // Skip ahead. Otherwise, we will revisit this instruction again.
+ }
+ }
+ super.visitEnd();
+ mn.accept(chain);
+ }
+ }
+
+ public static void insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
+ List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+ MethodInsnNode call) {
+ List<TryCatchBlockNode> handlers = handlersMap.get(index);
+ InsnList instructions = mn.instructions;
+ LabelNode end = new LabelNode();
+ instructions.insert(node, end);
+ frameMap.add(index, null);
+ handlersMap.add(index, null);
+ instructions.insertBefore(node, call);
+ frameMap.add(index, null);
+ handlersMap.add(index, null);
+
+ LabelNode start = new LabelNode();
+ instructions.insert(node, start);
+ frameMap.add(index, null);
+ handlersMap.add(index, null);
+ updateCatchHandler(mn, handlers, start, end, handlersMap);
+ }
+
+ public static void insertMethodCallAfter(MethodNode mn, List<Frame> frameMap,
+ List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+ MethodInsnNode call) {
+ List<TryCatchBlockNode> handlers = handlersMap.get(index + 1);
+ InsnList instructions = mn.instructions;
+
+ LabelNode end = new LabelNode();
+ instructions.insert(node, end);
+ frameMap.add(index + 1, null);
+ handlersMap.add(index + 1, null);
+
+ instructions.insert(node, call);
+ frameMap.add(index + 1, null);
+ handlersMap.add(index + 1, null);
+
+ LabelNode start = new LabelNode();
+ instructions.insert(node, start);
+ frameMap.add(index + 1, null);
+ handlersMap.add(index + 1, null);
+
+ updateCatchHandler(mn, handlers, start, end, handlersMap);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers,
+ LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) {
+ if (handlers == null || handlers.size() == 0) {
+ return;
+ }
+
+ InsnList instructions = mn.instructions;
+ List<TryCatchBlockNode> newNodes = new ArrayList<>(handlers.size());
+ for (TryCatchBlockNode handler : handlers) {
+ if (!(instructions.indexOf(handler.start) <= instructions.indexOf(start)
+ && instructions.indexOf(end) <= instructions.indexOf(handler.end))) {
+ TryCatchBlockNode newNode =
+ new TryCatchBlockNode(start, end, handler.handler, handler.type);
+ newNodes.add(newNode);
+ for (int i = instructions.indexOf(start); i <= instructions.indexOf(end); i++) {
+ if (handlersMap.get(i) == null) {
+ handlersMap.set(i, new ArrayList<>());
+ }
+ handlersMap.get(i).add(newNode);
+ }
+ } else {
+ for (int i = instructions.indexOf(start); i <= instructions.indexOf(end); i++) {
+ if (handlersMap.get(i) == null) {
+ handlersMap.set(i, new ArrayList<>());
+ }
+ handlersMap.get(i).add(handler);
+ }
+ }
+ }
+ mn.tryCatchBlocks.addAll(0, newNodes);
+ }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
new file mode 100644
index 0000000..c5e59e3
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
@@ -0,0 +1,61 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+/**
+ * Represent a specific class that is used for synchronization. A pre and post method can be
+ * specified to by the user to be called right after monitor_enter and after monitor_exit
+ * respectively.
+ */
+public class LockTarget {
+ public static final LockTarget NO_TARGET = new LockTarget("", null, null);
+
+ private final String targetDesc;
+ private final String pre;
+ private final String post;
+
+ public LockTarget(String targetDesc, String pre, String post) {
+ this.targetDesc = targetDesc;
+ this.pre = pre;
+ this.post = post;
+ }
+
+ public String getTargetDesc() {
+ return targetDesc;
+ }
+
+ public String getPre() {
+ return pre;
+ }
+
+ public String getPreOwner() {
+ return pre.substring(0, pre.lastIndexOf('.'));
+ }
+
+ public String getPreMethod() {
+ return pre.substring(pre.lastIndexOf('.') + 1);
+ }
+
+ public String getPost() {
+ return post;
+ }
+
+ public String getPostOwner() {
+ return post.substring(0, post.lastIndexOf('.'));
+ }
+
+ public String getPostMethod() {
+ return post.substring(post.lastIndexOf('.') + 1);
+ }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
new file mode 100644
index 0000000..99d8418
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
@@ -0,0 +1,34 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.util.List;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.analysis.BasicValue;
+
+public class LockTargetState extends BasicValue {
+ private final List<LockTarget> lockTargets;
+
+ /**
+ * @param type
+ */
+ public LockTargetState(Type type, List<LockTarget> lockTargets) {
+ super(type);
+ this.lockTargets = lockTargets;
+ }
+
+ public List<LockTarget> getTargets() {
+ return lockTargets;
+ }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java
new file mode 100644
index 0000000..1002c88
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java
@@ -0,0 +1,109 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.objectweb.asm.tree.analysis.BasicInterpreter;
+import org.objectweb.asm.tree.analysis.BasicValue;
+
+/**
+ * A simple dataflow analysis to determine if the operands on the stack must be one of target lock
+ * class type.
+ */
+public class LockTargetStateAnalysis extends BasicInterpreter {
+
+ private final List<LockTarget> targetLocks;
+
+ public LockTargetStateAnalysis(List<LockTarget> targetLocks) {
+ this.targetLocks = targetLocks;
+ }
+
+ @Override
+ public BasicValue naryOperation(AbstractInsnNode inst, @SuppressWarnings("rawtypes") List args)
+ throws AnalyzerException {
+ // We target the return type of any invocation.
+
+ @SuppressWarnings("unchecked")
+ BasicValue base = super.naryOperation(inst, args);
+ if (!(inst instanceof MethodInsnNode)) {
+ return base;
+ }
+
+ MethodInsnNode invoke = (MethodInsnNode) inst;
+ Type returnType = Type.getReturnType(invoke.desc);
+ if (returnType.equals(Type.VOID_TYPE)) {
+ return base;
+ }
+
+ List<LockTarget> types = new ArrayList<>();
+
+ for (LockTarget target : targetLocks) {
+ if (returnType.getDescriptor().equals(target.getTargetDesc())) {
+ types.add(target);
+ }
+ }
+
+ return new LockTargetState(base.getType(), types);
+ }
+
+ @Override
+ public BasicValue newValue(Type type) {
+ BasicValue base = super.newValue(type);
+ List<LockTarget> types = new ArrayList<>();
+
+ if (type == null) {
+ return base;
+ }
+ for (LockTarget target : targetLocks) {
+ if (type.getDescriptor().equals(target.getTargetDesc())) {
+ types.add(target);
+ }
+ }
+
+ if (types.isEmpty()) {
+ return base;
+ }
+
+ return new LockTargetState(base.getType(), types);
+ }
+
+ @Override
+ public BasicValue merge(BasicValue v1, BasicValue v2) {
+ BasicValue base = super.merge(v1, v2);
+
+ if (!(v1 instanceof LockTargetState)) {
+ return base;
+ }
+ if (!(v2 instanceof LockTargetState)) {
+ return base;
+ }
+
+ LockTargetState state1 = (LockTargetState) v1;
+ LockTargetState state2 = (LockTargetState) v2;
+
+ List<LockTarget> newList = new ArrayList<>(state1.getTargets());
+ for (LockTarget otherTarget : state2.getTargets()) {
+ if (!newList.contains(otherTarget)) {
+ newList.add(otherTarget);
+ }
+ }
+
+ return new LockTargetState(base.getType(), newList);
+ }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
new file mode 100644
index 0000000..edb9a49
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
@@ -0,0 +1,104 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.io.BufferedInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+public class Main {
+ public static void main(String[] args) throws IOException {
+ String inJar = null;
+ String outJar = null;
+
+ String legacyTargets = null;
+ String legacyPreMethods = null;
+ String legacyPostMethods = null;
+ for (int i = 0; i < args.length; i++) {
+ if ("-i".equals(args[i].trim())) {
+ i++;
+ inJar = args[i].trim();
+ } else if ("-o".equals(args[i].trim())) {
+ i++;
+ outJar = args[i].trim();
+ } else if ("--targets".equals(args[i].trim())) {
+ i++;
+ legacyTargets = args[i].trim();
+ } else if ("--pre".equals(args[i].trim())) {
+ i++;
+ legacyPreMethods = args[i].trim();
+ } else if ("--post".equals(args[i].trim())) {
+ i++;
+ legacyPostMethods = args[i].trim();
+ }
+
+ }
+
+ // TODO(acleung): Better help message than asserts.
+ assert inJar != null;
+ assert outJar != null;
+ assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null);
+
+ ZipFile zipSrc = new ZipFile(inJar);
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar));
+ List<LockTarget> targets = null;
+ if (legacyTargets != null) {
+ targets = Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
+ legacyPostMethods);
+ } else {
+ targets = Collections.emptyList();
+ }
+
+ Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries();
+ while (srcEntries.hasMoreElements()) {
+ ZipEntry entry = srcEntries.nextElement();
+ ZipEntry newEntry = new ZipEntry(entry.getName());
+ zos.putNextEntry(newEntry);
+ BufferedInputStream bis = new BufferedInputStream(zipSrc.getInputStream(entry));
+
+ if (entry.getName().endsWith(".class")) {
+ convert(bis, zos, targets);
+ } else {
+ while (bis.available() > 0) {
+ zos.write(bis.read());
+ }
+ zos.closeEntry();
+ bis.close();
+ }
+ }
+ zos.finish();
+ zos.close();
+ zipSrc.close();
+ }
+
+ private static void convert(InputStream in, OutputStream out, List<LockTarget> targets)
+ throws IOException {
+ ClassReader cr = new ClassReader(in);
+ ClassWriter cw = new ClassWriter(0);
+ LockFindingClassVisitor cv = new LockFindingClassVisitor(targets, cw);
+ cr.accept(cv, 0);
+ byte[] data = cw.toByteArray();
+ out.write(data);
+ }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
new file mode 100644
index 0000000..d2a2e7b
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
@@ -0,0 +1,46 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class Utils {
+
+ public static final int ASM_VERSION = Opcodes.ASM5;
+
+ /**
+ * Reads a comma separated configuration similar to the Jack definition.
+ */
+ public static List<LockTarget> getTargetsFromLegacyJackConfig(String classList,
+ String requestList, String resetList) {
+
+ String[] classes = classList.split(",");
+ String[] requests = requestList.split(",");
+ String[] resets = resetList.split(",");
+
+ int total = classes.length;
+ assert requests.length == total;
+ assert resets.length == total;
+
+ List<LockTarget> config = new ArrayList<LockTarget>();
+
+ for (int i = 0; i < total; i++) {
+ config.add(new LockTarget(classes[i], requests[i], resets[i]));
+ }
+
+ return config;
+ }
+}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
new file mode 100644
index 0000000..1d4f2d4
--- /dev/null
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
@@ -0,0 +1,231 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * To run the unit tests:
+ *
+ * <pre>
+ * <code>
+ * set -x
+ *
+ * # Clean
+ * rm -fr out/*
+ *
+ * # Make booster
+ * javac -cp lib/asm-all-5.2.jar src/*/*.java -d out/
+ * pushd out
+ * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main */*.class
+ * popd
+ *
+ * # Make unit tests.
+ * javac -cp lib/junit-4.12.jar test/*/*.java -d out/
+ *
+ * pushd out
+ * jar cfe test_input.jar lockedregioncodeinjection.Test */*.class
+ * popd
+ *
+ * # Run tool on unit tests.
+ * java -ea -cp lib/asm-all-5.2.jar:out/lockedregioncodeinjection.jar \
+ * lockedregioncodeinjection.Main \
+ * -i out/test_input.jar -o out/test_output.jar \
+ * --targets 'Llockedregioncodeinjection/TestTarget;' \
+ * --pre 'lockedregioncodeinjection/TestTarget.boost' \
+ * --post 'lockedregioncodeinjection/TestTarget.unboost'
+ *
+ * # Run unit tests.
+ * java -ea -cp lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar:out/test_output.jar \
+ * org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
+ * </code>
+ * </pre>
+ */
+public class TestMain {
+ @Test
+ public void testSimpleSynchronizedBlock() {
+ TestTarget.resetCount();
+ TestTarget t = new TestTarget();
+
+ Assert.assertEquals(TestTarget.boostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+
+ synchronized (t) {
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+ TestTarget.invoke();
+ }
+
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 1);
+ Assert.assertEquals(TestTarget.invokeCount, 1);
+ }
+
+ @Test
+ public void testSimpleSynchronizedMethod() {
+ TestTarget.resetCount();
+ TestTarget t = new TestTarget();
+
+ Assert.assertEquals(TestTarget.boostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+
+ t.synchronizedCall();
+
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 1);
+ Assert.assertEquals(TestTarget.invokeCount, 1);
+ }
+
+ @Test
+ public void testSimpleSynchronizedMethod2() {
+ TestTarget.resetCount();
+ TestTarget t = new TestTarget();
+
+ Assert.assertEquals(TestTarget.boostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+
+ t.synchronizedCallReturnInt();
+
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 1);
+ Assert.assertEquals(TestTarget.invokeCount, 1);
+ }
+
+ @Test
+ public void testSimpleSynchronizedMethod3() {
+ TestTarget.resetCount();
+ TestTarget t = new TestTarget();
+
+ Assert.assertEquals(TestTarget.boostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+
+ t.synchronizedCallReturnObject();
+
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 1);
+ Assert.assertEquals(TestTarget.invokeCount, 1);
+ }
+
+ @SuppressWarnings("unused")
+ @Test
+ public void testCaughtException() {
+ TestTarget.resetCount();
+ TestTarget t = new TestTarget();
+ boolean caughtException = false;
+
+ Assert.assertEquals(TestTarget.boostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+
+ try {
+ synchronized (t) {
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+ if (true) {
+ throw new RuntimeException();
+ }
+ TestTarget.invoke();
+ }
+ } catch (Throwable e) {
+ caughtException = true;
+ }
+
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 1);
+ Assert.assertEquals(TestTarget.invokeCount, 0); // Not called
+ Assert.assertTrue(caughtException);
+ }
+
+ @SuppressWarnings("unused")
+ private void testUncaughtException() {
+ TestTarget t = new TestTarget();
+ synchronized (t) {
+ if (true) {
+ throw new RuntimeException();
+ }
+ TestTarget.invoke();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @Test
+ public void testHandledFinally() {
+ TestTarget.resetCount();
+ try {
+ testUncaughtException();
+ } catch (Throwable t) {
+
+ }
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 1);
+ Assert.assertEquals(TestTarget.invokeCount, 0); // Not called
+ }
+
+ @Test
+ public void testNestedSynchronizedBlock() {
+ TestTarget.resetCount();
+ TestTarget t = new TestTarget();
+
+ Assert.assertEquals(TestTarget.boostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+
+ synchronized (t) {
+ synchronized (t) {
+ synchronized (t) {
+ synchronized (t) {
+ synchronized (t) {
+ synchronized (t) {
+ Assert.assertEquals(TestTarget.boostCount, 6);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+ TestTarget.invoke();
+ }
+ Assert.assertEquals(TestTarget.unboostCount, 1);
+ }
+ Assert.assertEquals(TestTarget.unboostCount, 2);
+ }
+ Assert.assertEquals(TestTarget.unboostCount, 3);
+ }
+ Assert.assertEquals(TestTarget.unboostCount, 4);
+ }
+ Assert.assertEquals(TestTarget.unboostCount, 5);
+ }
+
+ Assert.assertEquals(TestTarget.boostCount, 6);
+ Assert.assertEquals(TestTarget.unboostCount, 6);
+ Assert.assertEquals(TestTarget.invokeCount, 1);
+ }
+
+ @Test
+ public void testMethodWithControlFlow() {
+ TestTarget.resetCount();
+ TestTarget t = new TestTarget();
+
+ Assert.assertEquals(TestTarget.boostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+
+ if ((t.hashCode() + " ").contains("1")) {
+ t.synchronizedCall();
+ } else {
+ t.synchronizedCall();
+ }
+
+ // Should only be boosted once.
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 1);
+ Assert.assertEquals(TestTarget.invokeCount, 1);
+ }
+}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
new file mode 100644
index 0000000..8e7d478
--- /dev/null
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
@@ -0,0 +1,52 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+public class TestTarget {
+ public static int boostCount = 0;
+ public static int unboostCount = 0;
+ public static int invokeCount = 0;
+
+ public static void boost() {
+ boostCount++;
+ }
+
+ public static void unboost() {
+ unboostCount++;
+ }
+
+ public static void invoke() {
+ invokeCount++;
+ }
+
+ public static void resetCount() {
+ boostCount = 0;
+ unboostCount = 0;
+ invokeCount = 0;
+ }
+
+ public synchronized void synchronizedCall() {
+ invoke();
+ }
+
+ public synchronized int synchronizedCallReturnInt() {
+ invoke();
+ return 0;
+ }
+
+ public synchronized Object synchronizedCallReturnObject() {
+ invoke();
+ return this;
+ }
+}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 18f30f8..bb3af3c 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -156,9 +156,20 @@
}
- /** Copy constructor */
- public WifiEnterpriseConfig(WifiEnterpriseConfig source) {
+ /**
+ * Copy over the contents of the source WifiEnterpriseConfig object over to this object.
+ *
+ * @param source Source WifiEnterpriseConfig object.
+ * @param ignoreMaskedPassword Set to true to ignore masked password field, false otherwise.
+ * @param mask if |ignoreMaskedPassword| is set, check if the incoming password field is set
+ * to this value.
+ */
+ private void copyFrom(WifiEnterpriseConfig source, boolean ignoreMaskedPassword, String mask) {
for (String key : source.mFields.keySet()) {
+ if (ignoreMaskedPassword && key.equals(PASSWORD_KEY)
+ && TextUtils.equals(source.mFields.get(key), mask)) {
+ continue;
+ }
mFields.put(key, source.mFields.get(key));
}
if (source.mCaCerts != null) {
@@ -178,6 +189,29 @@
mPhase2Method = source.mPhase2Method;
}
+ /**
+ * Copy constructor.
+ * This copies over all the fields verbatim (does not ignore masked password fields).
+ *
+ * @param source Source WifiEnterpriseConfig object.
+ */
+ public WifiEnterpriseConfig(WifiEnterpriseConfig source) {
+ copyFrom(source, false, "");
+ }
+
+ /**
+ * Copy fields from the provided external WifiEnterpriseConfig.
+ * This is needed to handle the WifiEnterpriseConfig objects which were sent by apps with the
+ * password field masked.
+ *
+ * @param externalConfig External WifiEnterpriseConfig object.
+ * @param mask String mask to compare against.
+ * @hide
+ */
+ public void copyFromExternal(WifiEnterpriseConfig externalConfig, String mask) {
+ copyFrom(externalConfig, true, convertToQuotedString(mask));
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index d0aedba..1a7dd13 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -316,15 +317,37 @@
assertEquals("\"auth=AKA'\"", getSupplicantPhase2Method());
}
- /** Verfies that the copy constructor preseves the inner method information. */
+ /**
+ * Verifies that the copy constructor preseves both the masked password and inner method
+ * information.
+ */
@Test
public void copyConstructor() {
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ enterpriseConfig.setPassword("*");
enterpriseConfig.setEapMethod(Eap.TTLS);
enterpriseConfig.setPhase2Method(Phase2.GTC);
mEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
assertEquals("TTLS", getSupplicantEapMethod());
assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
+ assertEquals("*", mEnterpriseConfig.getPassword());
+ }
+
+ /**
+ * Verifies that the copy from external ignores masked passwords and preserves the
+ * inner method information.
+ */
+ @Test
+ public void copyFromExternal() {
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ enterpriseConfig.setPassword("*");
+ enterpriseConfig.setEapMethod(Eap.TTLS);
+ enterpriseConfig.setPhase2Method(Phase2.GTC);
+ mEnterpriseConfig = new WifiEnterpriseConfig();
+ mEnterpriseConfig.copyFromExternal(enterpriseConfig, "*");
+ assertEquals("TTLS", getSupplicantEapMethod());
+ assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
+ assertNotEquals("*", mEnterpriseConfig.getPassword());
}
/** Verfies that parceling a WifiEnterpriseConfig preseves method information. */