Merge "Media: Eliminate NPE from race conditions"
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a040520f..318c7ac 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2157,6 +2157,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/content/Context.java b/core/java/android/content/Context.java
index 5929aca..128d195 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3913,7 +3913,7 @@
* @see #getSystemService
* @hide
*/
- public static final String RADIO_SERVICE = "radio";
+ public static final String RADIO_SERVICE = "broadcastradio";
/**
* Use with {@link #getSystemService} to retrieve a
@@ -4681,6 +4681,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 c719c64..a9fd58b 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -925,6 +925,12 @@
return mBase.isCredentialProtectedStorage();
}
+ /** {@hide} */
+ @Override
+ public boolean canLoadUnsafeResources() {
+ return mBase.canLoadUnsafeResources();
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 0045308..d73d3d8 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -654,6 +654,7 @@
Preconditions.checkNotNull(port, "port must not be null");
UsbPort.checkRoles(powerRole, dataRole);
+ Log.d(TAG, "setPortRoles Package:" + mContext.getPackageName());
try {
mService.setPortRoles(port.getId(), powerRole, dataRole);
} catch (RemoteException e) {
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..fd6da05a 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/jni/Android.bp b/core/jni/Android.bp
index 7e71ce9..c5279e1 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_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 1779ada..e8f6074 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -396,7 +396,7 @@
}
static void
-android_media_AudioSystem_recording_callback(int event, audio_session_t session, int source,
+android_media_AudioSystem_recording_callback(int event, const record_client_info_t *clientInfo,
const audio_config_base_t *clientConfig, const audio_config_base_t *deviceConfig,
audio_patch_handle_t patchHandle)
{
@@ -404,8 +404,8 @@
if (env == NULL) {
return;
}
- if (clientConfig == NULL || deviceConfig == NULL) {
- ALOGE("Unexpected null client/device configurations in recording callback");
+ if (clientInfo == NULL || clientConfig == NULL || deviceConfig == NULL) {
+ ALOGE("Unexpected null client/device info or configurations in recording callback");
return;
}
@@ -433,7 +433,7 @@
jclass clazz = env->FindClass(kClassPathName);
env->CallStaticVoidMethod(clazz,
gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative,
- event, session, source, recParamArray);
+ event, (jint) clientInfo->uid, clientInfo->session, clientInfo->source, recParamArray);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(recParamArray);
@@ -1930,7 +1930,7 @@
"dynamicPolicyCallbackFromNative", "(ILjava/lang/String;I)V");
gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative =
GetStaticMethodIDOrDie(env, env->FindClass(kClassPathName),
- "recordingCallbackFromNative", "(III[I)V");
+ "recordingCallbackFromNative", "(IIII[I)V");
jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix");
gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass);
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4d254c2..4ee9d54 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3003,6 +3003,21 @@
<item quantity="other">Open Wi-Fi networks available</item>
</plurals>
+ <!-- Notification title for a nearby open wireless network.-->
+ <string name="wifi_available_title">Connect to open Wi\u2011Fi network</string>
+ <!-- Notification title when the system is connecting to the specified open network. The network name is specified in the notification content. -->
+ <string name="wifi_available_title_connecting">Connecting to open Wi\u2011Fi network</string>
+ <!-- Notification title when the system has connected to the open network. The network name is specified in the notification content. -->
+ <string name="wifi_available_title_connected">Connected to Wi\u2011Fi network</string>
+ <!-- Notification title when the system failed to connect to the specified open network. -->
+ <string name="wifi_available_title_failed_to_connect">Could not connect to Wi\u2011Fi network</string>
+ <!-- Notification content when the system failed to connect to the specified open network. This informs the user that tapping on this notification will open the wifi picker. -->
+ <string name="wifi_available_content_failed_to_connect">Tap to see all networks</string>
+ <!-- Notification action name for connecting to the network specified in the notification body. -->
+ <string name="wifi_available_action_connect">Connect</string>
+ <!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. -->
+ <string name="wifi_available_action_all_networks">All Networks</string>
+
<!-- A notification is shown when a wifi captive portal network is detected. This is the notification's title. -->
<string name="wifi_available_sign_in">Sign in to Wi-Fi network</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4e634b7..6cbf11f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1869,6 +1869,13 @@
<java-symbol type="layout" name="app_error_dialog" />
<java-symbol type="plurals" name="wifi_available" />
<java-symbol type="plurals" name="wifi_available_detailed" />
+ <java-symbol type="string" name="wifi_available_title" />
+ <java-symbol type="string" name="wifi_available_title_connecting" />
+ <java-symbol type="string" name="wifi_available_title_connected" />
+ <java-symbol type="string" name="wifi_available_title_failed_to_connect" />
+ <java-symbol type="string" name="wifi_available_content_failed_to_connect" />
+ <java-symbol type="string" name="wifi_available_action_connect" />
+ <java-symbol type="string" name="wifi_available_action_all_networks" />
<java-symbol type="string" name="accessibility_binding_label" />
<java-symbol type="string" name="adb_active_notification_message" />
<java-symbol type="string" name="adb_active_notification_title" />
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/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 81cc93d..93fc3da 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -267,6 +267,42 @@
**/
public static final int ENCODING_DOLBY_TRUEHD = 14;
+ /** @hide */
+ public static String toLogFriendlyEncoding(int enc) {
+ switch(enc) {
+ case ENCODING_INVALID:
+ return "ENCODING_INVALID";
+ case ENCODING_PCM_16BIT:
+ return "ENCODING_PCM_16BIT";
+ case ENCODING_PCM_8BIT:
+ return "ENCODING_PCM_8BIT";
+ case ENCODING_PCM_FLOAT:
+ return "ENCODING_PCM_FLOAT";
+ case ENCODING_AC3:
+ return "ENCODING_AC3";
+ case ENCODING_E_AC3:
+ return "ENCODING_E_AC3";
+ case ENCODING_DTS:
+ return "ENCODING_DTS";
+ case ENCODING_DTS_HD:
+ return "ENCODING_DTS_HD";
+ case ENCODING_MP3:
+ return "ENCODING_MP3";
+ case ENCODING_AAC_LC:
+ return "ENCODING_AAC_LC";
+ case ENCODING_AAC_HE_V1:
+ return "ENCODING_AAC_HE_V1";
+ case ENCODING_AAC_HE_V2:
+ return "ENCODING_AAC_HE_V2";
+ case ENCODING_IEC61937:
+ return "ENCODING_IEC61937";
+ case ENCODING_DOLBY_TRUEHD:
+ return "ENCODING_DOLBY_TRUEHD";
+ default :
+ return "invalid encoding " + enc;
+ }
+ }
+
/** Invalid audio channel configuration */
/** @deprecated Use {@link #CHANNEL_INVALID} instead. */
@Deprecated public static final int CHANNEL_CONFIGURATION_INVALID = 0;
@@ -693,6 +729,12 @@
return mPropertySetMask;
}
+ /** @hide */
+ public String toLogFriendlyString() {
+ return String.format("%dch %dHz %s",
+ getChannelCount(), mSampleRate, toLogFriendlyEncoding(mEncoding));
+ }
+
/**
* Builder class for {@link AudioFormat} objects.
* Use this class to configure and create an AudioFormat instance. By setting format
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 50dbd03..984c554 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -17,10 +17,12 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -52,18 +54,59 @@
private final AudioFormat mDeviceFormat;
private final AudioFormat mClientFormat;
+ @NonNull private final String mClientPackageName;
+ private final int mClientUid;
+
private final int mPatchHandle;
/**
* @hide
*/
- public AudioRecordingConfiguration(int session, int source, AudioFormat clientFormat,
- AudioFormat devFormat, int patchHandle) {
+ public AudioRecordingConfiguration(int uid, int session, int source, AudioFormat clientFormat,
+ AudioFormat devFormat, int patchHandle, String packageName) {
+ mClientUid = uid;
mSessionId = session;
mClientSource = source;
mClientFormat = clientFormat;
mDeviceFormat = devFormat;
mPatchHandle = patchHandle;
+ mClientPackageName = packageName;
+ }
+
+ /**
+ * @hide
+ * For AudioService dump
+ * @param pw
+ */
+ public void dump(PrintWriter pw) {
+ pw.println(" " + toLogFriendlyString(this));
+ }
+
+ /**
+ * @hide
+ */
+ public static String toLogFriendlyString(AudioRecordingConfiguration arc) {
+ return new String("session:" + arc.mSessionId
+ + " -- source:" + MediaRecorder.toLogFriendlyAudioSource(arc.mClientSource)
+ + " -- uid:" + arc.mClientUid
+ + " -- patch:" + arc.mPatchHandle
+ + " -- pack:" + arc.mClientPackageName
+ + " -- format client=" + arc.mClientFormat.toLogFriendlyString()
+ + ", dev=" + arc.mDeviceFormat.toLogFriendlyString());
+ }
+
+ // Note that this method is called server side, so no "privileged" information is ever sent
+ // to a client that is not supposed to have access to it.
+ /**
+ * @hide
+ * Creates a copy of the recording configuration that is stripped of any data enabling
+ * identification of which application it is associated with ("anonymized").
+ * @param in
+ */
+ public static AudioRecordingConfiguration anonymizedCopy(AudioRecordingConfiguration in) {
+ return new AudioRecordingConfiguration( /*anonymized uid*/ -1,
+ in.mSessionId, in.mClientSource, in.mClientFormat,
+ in.mDeviceFormat, in.mPatchHandle, "" /*empty package name*/);
}
// matches the sources that return false in MediaRecorder.isSystemOnlyAudioSource(source)
@@ -120,6 +163,30 @@
public AudioFormat getClientFormat() { return mClientFormat; }
/**
+ * @pending for SystemApi
+ * Returns the package name of the application performing the recording.
+ * Where there are multiple packages sharing the same user id through the "sharedUserId"
+ * mechanism, only the first one with that id will be returned
+ * (see {@link PackageManager#getPackagesForUid(int)}).
+ * <p>This information is only available if the caller has the
+ * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING} permission.
+ * <br>When called without the permission, the result is an empty string.
+ * @return the package name
+ */
+ public String getClientPackageName() { return mClientPackageName; }
+
+ /**
+ * @pending for SystemApi
+ * Returns the user id of the application performing the recording.
+ * <p>This information is only available if the caller has the
+ * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING}
+ * permission.
+ * <br>The result is -1 without the permission.
+ * @return the user id
+ */
+ public int getClientUid() { return mClientUid; }
+
+ /**
* Returns information about the audio input device used for this recording.
* @return the audio recording device or null if this information cannot be retrieved
*/
@@ -185,6 +252,8 @@
mClientFormat.writeToParcel(dest, 0);
mDeviceFormat.writeToParcel(dest, 0);
dest.writeInt(mPatchHandle);
+ dest.writeString(mClientPackageName);
+ dest.writeInt(mClientUid);
}
private AudioRecordingConfiguration(Parcel in) {
@@ -193,6 +262,8 @@
mClientFormat = AudioFormat.CREATOR.createFromParcel(in);
mDeviceFormat = AudioFormat.CREATOR.createFromParcel(in);
mPatchHandle = in.readInt();
+ mClientPackageName = in.readString();
+ mClientUid = in.readInt();
}
@Override
@@ -202,10 +273,12 @@
AudioRecordingConfiguration that = (AudioRecordingConfiguration) o;
- return ((mSessionId == that.mSessionId)
+ return ((mClientUid == that.mClientUid)
+ && (mSessionId == that.mSessionId)
&& (mClientSource == that.mClientSource)
&& (mPatchHandle == that.mPatchHandle)
&& (mClientFormat.equals(that.mClientFormat))
- && (mDeviceFormat.equals(that.mDeviceFormat)));
+ && (mDeviceFormat.equals(that.mDeviceFormat))
+ && (mClientPackageName.equals(that.mClientPackageName)));
}
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 6ef3091..c7c2dd8 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -287,6 +287,7 @@
/**
* Callback for recording activity notifications events
* @param event
+ * @param uid uid of the client app performing the recording
* @param session
* @param source
* @param recordingFormat an array of ints containing respectively the client and device
@@ -298,9 +299,10 @@
* 4: device channel mask
* 5: device sample rate
* 6: patch handle
+ * @param packName package name of the client app performing the recording. NOT SUPPORTED
*/
- void onRecordingConfigurationChanged(int event, int session, int source,
- int[] recordingFormat);
+ void onRecordingConfigurationChanged(int event, int uid, int session, int source,
+ int[] recordingFormat, String packName);
}
private static AudioRecordingCallback sRecordingCallback;
@@ -318,17 +320,18 @@
* @param session
* @param source
* @param recordingFormat see
- * {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])} for
- * the description of the record format.
+ * {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int, int[])}
+ * for the description of the record format.
*/
- private static void recordingCallbackFromNative(int event, int session, int source,
+ private static void recordingCallbackFromNative(int event, int uid, int session, int source,
int[] recordingFormat) {
AudioRecordingCallback cb = null;
synchronized (AudioSystem.class) {
cb = sRecordingCallback;
}
if (cb != null) {
- cb.onRecordingConfigurationChanged(event, session, source, recordingFormat);
+ // TODO receive package name from native
+ cb.onRecordingConfigurationChanged(event, uid, session, source, recordingFormat, "");
}
}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 33a7c83..59a124f 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -324,6 +324,40 @@
}
}
+ /** @hide */
+ public static final String toLogFriendlyAudioSource(int source) {
+ switch(source) {
+ case AudioSource.DEFAULT:
+ return "DEFAULT";
+ case AudioSource.MIC:
+ return "MIC";
+ case AudioSource.VOICE_UPLINK:
+ return "VOICE_UPLINK";
+ case AudioSource.VOICE_DOWNLINK:
+ return "VOICE_DOWNLINK";
+ case AudioSource.VOICE_CALL:
+ return "VOICE_CALL";
+ case AudioSource.CAMCORDER:
+ return "CAMCORDER";
+ case AudioSource.VOICE_RECOGNITION:
+ return "VOICE_RECOGNITION";
+ case AudioSource.VOICE_COMMUNICATION:
+ return "VOICE_COMMUNICATION";
+ case AudioSource.REMOTE_SUBMIX:
+ return "REMOTE_SUBMIX";
+ case AudioSource.UNPROCESSED:
+ return "UNPROCESSED";
+ case AudioSource.RADIO_TUNER:
+ return "RADIO_TUNER";
+ case AudioSource.HOTWORD:
+ return "HOTWORD";
+ case AudioSource.AUDIO_SOURCE_INVALID:
+ return "AUDIO_SOURCE_INVALID";
+ default:
+ return "unknown source " + source;
+ }
+ }
+
/**
* Defines the video source. These constants are used with
* {@link MediaRecorder#setVideoSource(int)}.
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
index 5a1e603..f1d43bf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
@@ -51,6 +51,12 @@
public Icon icon;
/**
+ * Whether the icon can be tinted. This should be set to true for monochrome (single-color)
+ * icons that can be tinted to match the design.
+ */
+ public boolean isIconTintable;
+
+ /**
* Intent to launch when the preference is selected.
*/
public Intent intent;
@@ -126,6 +132,7 @@
dest.writeBundle(metaData);
dest.writeString(key);
dest.writeParcelable(remoteViews, flags);
+ dest.writeBoolean(isIconTintable);
}
public void readFromParcel(Parcel in) {
@@ -147,6 +154,7 @@
metaData = in.readBundle();
key = in.readString();
remoteViews = in.readParcelable(RemoteViews.class.getClassLoader());
+ isIconTintable = in.readBoolean();
}
Tile(Parcel in) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 04a3d1f..9620a91 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -133,15 +133,25 @@
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify the title that should be displayed for the preference.
+ * to specify whether the icon is tintable. This should be a boolean value {@code true} or
+ * {@code false}, set using {@code android:value}
*/
- @Deprecated
- public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
+ public static final String META_DATA_PREFERENCE_ICON_TINTABLE =
+ "com.android.settings.icon_tintable";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the title that should be displayed for the preference.
+ *
+ * <p>Note: It is preferred to provide this value using {@code android:resource} with a string
+ * resource for localization.
*/
+ public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
+
+ /**
+ * @deprecated Use {@link #META_DATA_PREFERENCE_TITLE} with {@code android:resource}
+ */
+ @Deprecated
public static final String META_DATA_PREFERENCE_TITLE_RES_ID =
"com.android.settings.title.resid";
@@ -309,12 +319,13 @@
intent.setPackage(settingPkg);
}
getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
- usePriority, true);
+ usePriority, true, true);
}
- public static void getTilesForIntent(Context context, UserHandle user, Intent intent,
+ public static void getTilesForIntent(
+ Context context, UserHandle user, Intent intent,
Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
- boolean usePriority, boolean checkCategory) {
+ boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
@@ -350,7 +361,7 @@
tile.priority = usePriority ? resolved.priority : 0;
tile.metaData = activityInfo.metaData;
updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
- pm, providerMap);
+ pm, providerMap, forceTintExternalIcon);
if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
addedCache.put(key, tile);
@@ -366,25 +377,40 @@
private static boolean updateTileData(Context context, Tile tile,
ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm,
- Map<String, IContentProvider> providerMap) {
+ Map<String, IContentProvider> providerMap, boolean forceTintExternalIcon) {
if (applicationInfo.isSystemApp()) {
+ boolean forceTintIcon = false;
int icon = 0;
Pair<String, Integer> iconFromUri = null;
CharSequence title = null;
String summary = null;
String keyHint = null;
+ boolean isIconTintable = false;
RemoteViews remoteViews = null;
// Get the activity's meta-data
try {
- Resources res = pm.getResourcesForApplication(
- applicationInfo.packageName);
+ Resources res = pm.getResourcesForApplication(applicationInfo.packageName);
Bundle metaData = activityInfo.metaData;
+ if (forceTintExternalIcon
+ && !context.getPackageName().equals(applicationInfo.packageName)) {
+ isIconTintable = true;
+ forceTintIcon = true;
+ }
+
if (res != null && metaData != null) {
if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
}
+ if (metaData.containsKey(META_DATA_PREFERENCE_ICON_TINTABLE)) {
+ if (forceTintIcon) {
+ Log.w(LOG_TAG, "Ignoring icon tintable for " + activityInfo);
+ } else {
+ isIconTintable =
+ metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE);
+ }
+ }
int resId = 0;
if (metaData.containsKey(META_DATA_PREFERENCE_TITLE_RES_ID)) {
resId = metaData.getInt(META_DATA_PREFERENCE_TITLE_RES_ID);
@@ -392,8 +418,6 @@
title = res.getString(resId);
}
}
- // Fallback to legacy title extraction if we couldn't get the title through
- // res id.
if ((resId == 0) && metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
@@ -457,6 +481,7 @@
activityInfo.name);
// Suggest a key for this tile
tile.key = keyHint;
+ tile.isIconTintable = isIconTintable;
tile.remoteViews = remoteViews;
return true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
index a28bece..167ffe6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
@@ -208,7 +208,7 @@
intent.setPackage(category.pkg);
}
TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
- mAddCache, null, suggestions, true, false);
+ mAddCache, null, suggestions, true, false, false);
filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);
if (!category.multiple && suggestions.size() > (countBefore + 1)) {
// If there are too many, remove them all and only re-add the one with the highest
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
index 22fd83c..31abecd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
@@ -19,5 +19,5 @@
public class TestConfig {
public static final int SDK_VERSION = 23;
public static final String MANIFEST_PATH =
- "frameworks/base/packages/SettingsLib/robotests/AndroidManifest.xml";
+ "frameworks/base/packages/SettingsLib/tests/robotests/AndroidManifest.xml";
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 7cfb32d..0364418 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -16,6 +16,19 @@
package com.android.settingslib.drawer;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.RuntimeEnvironment.application;
+
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -50,8 +63,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
import org.robolectric.internal.ShadowExtractor;
import java.util.ArrayList;
@@ -59,20 +73,6 @@
import java.util.List;
import java.util.Map;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
@@ -100,8 +100,11 @@
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getResourcesForApplication(anyString())).thenReturn(mResources);
- mContentResolver = spy(RuntimeEnvironment.application.getContentResolver());
+ when(mPackageManager.getApplicationInfo(eq("abc"), anyInt()))
+ .thenReturn(application.getApplicationInfo());
+ mContentResolver = spy(application.getContentResolver());
when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.getPackageName()).thenReturn("com.android.settings");
}
@Test
@@ -118,7 +121,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
assertThat(outTiles.get(0).category).isEqualTo(testCategory);
@@ -139,7 +142,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
assertThat(outTiles.get(0).key).isEqualTo(keyHint);
@@ -159,7 +162,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.isEmpty()).isTrue();
}
@@ -182,7 +185,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
SuggestionParser parser = new SuggestionParser(
@@ -255,7 +258,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
assertThat(outTiles.get(0).title).isEqualTo("my title");
@@ -279,10 +282,60 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
assertThat(outTiles.get(0).title).isEqualTo("my localized title");
+
+ // Icon should be tintable because the tile is not from settings package, and
+ // "forceTintExternalIcon" is set
+ assertThat(outTiles.get(0).isIconTintable).isTrue();
+ }
+
+ @Test
+ public void getTilesForIntent_shouldNotTintIconIfInSettingsPackage() {
+ Intent intent = new Intent();
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, null, 123);
+ resolveInfo.activityInfo.packageName = "com.android.settings";
+ resolveInfo.activityInfo.applicationInfo.packageName = "com.android.settings";
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+ null /* defaultCategory */, outTiles, false /* usePriority */,
+ false /* checkCategory */, true /* forceTintExternalIcon */);
+
+ assertThat(outTiles.size()).isEqualTo(1);
+ assertThat(outTiles.get(0).isIconTintable).isFalse();
+ }
+
+ @Test
+ public void getTilesForIntent_shouldMarkIconTintableIfMetadataSet() {
+ Intent intent = new Intent();
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, null, 123);
+ resolveInfo.activityInfo.metaData
+ .putBoolean(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE, true);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+ null /* defaultCategory */, outTiles, false /* usePriority */,
+ false /* checkCategory */, false /* forceTintExternalIcon */);
+
+ assertThat(outTiles.size()).isEqualTo(1);
+ assertThat(outTiles.get(0).isIconTintable).isTrue();
}
@Test
@@ -301,7 +354,7 @@
// Case 1: No provider associated with the uri specified.
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
assertThat(outTiles.get(0).icon.getResId()).isEqualTo(314159);
@@ -319,7 +372,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
assertThat(outTiles.get(0).icon.getResId()).isEqualTo(314159);
@@ -341,7 +394,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
}
@@ -362,7 +415,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
Tile tile = outTiles.get(0);
@@ -399,7 +452,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
Tile tile = outTiles.get(0);
@@ -437,7 +490,7 @@
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
- false /* checkCategory */);
+ false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
Tile tile = outTiles.get(0);
@@ -484,7 +537,9 @@
if (summaryUri != null) {
info.activityInfo.metaData.putString("com.android.settings.summary_uri", summaryUri);
}
- if (title != null) {
+ if (titleResId != 0) {
+ info.activityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title);
+ } else if (title != null) {
info.activityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title);
}
if (titleResId != 0) {
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/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 9076199..18ffd0f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -16,12 +16,12 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/volume_dialog"
- android:layout_width="@dimen/volume_dialog_panel_width"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/volume_dialog_margin_bottom"
- android:layout_gravity="center_vertical|end"
+ android:background="@drawable/volume_dialog_background"
android:paddingTop="@dimen/volume_dialog_padding_top"
- android:translationZ="8dp" >
+ android:translationZ="4dp" >
<LinearLayout
android:id="@+id/volume_dialog_content"
@@ -57,7 +57,6 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
- android:visibility="gone"
android:textAppearance="@style/TextAppearance.Volume.Header" />
<com.android.keyguard.AlphaOptimizedImageButton
xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SystemUI/res/layout/volume_dialog_wrapped.xml b/packages/SystemUI/res/layout/volume_dialog_wrapped.xml
deleted file mode 100644
index 57489fd..0000000
--- a/packages/SystemUI/res/layout/volume_dialog_wrapped.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.systemui.HardwareUiLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/top_padding"
- android:layout_marginBottom="@dimen/bottom_padding">
-
- <include layout="@layout/volume_dialog"/>
-
-</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9395e5a..93d2072 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -216,6 +216,8 @@
<!-- The width of the panel that holds the quick settings. -->
<dimen name="qs_panel_width">@dimen/notification_panel_width</dimen>
+ <dimen name="volume_dialog_panel_width">@dimen/standard_notification_panel_width</dimen>
+
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
@@ -800,7 +802,6 @@
<dimen name="hwui_edge_margin">16dp</dimen>
- <dimen name="volume_dialog_panel_width">315dp</dimen>
<dimen name="global_actions_panel_width">125dp</dimen>
<dimen name="global_actions_top_padding">100dp</dimen>
@@ -819,7 +820,7 @@
<dimen name="edge_margin">16dp</dimen>
<dimen name="rounded_corner_radius">0dp</dimen>
- <dimen name="rounded_corner_content_padding">0dp</dimen>
+ <dimen name="rounded_corner_content_padding">8dp</dimen>
<!-- Intended corner radius when drawing the mobile signal -->
<dimen name="stat_sys_mobile_signal_corner_radius">0.75dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
index 7af4a90..14e15ec 100644
--- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
+++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
@@ -55,11 +55,17 @@
public void start() {
mRoundedDefault = mContext.getResources().getDimensionPixelSize(
R.dimen.rounded_corner_radius);
- if (mRoundedDefault == 0) {
- // No rounded corners on this device.
- return;
+ if (mRoundedDefault != 0) {
+ setupRounding();
}
+ int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.rounded_corner_content_padding);
+ if (padding != 0) {
+ setupPadding(padding);
+ }
+ }
+ private void setupRounding() {
mOverlay = LayoutInflater.from(mContext)
.inflate(R.layout.rounded_corners, null);
mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
@@ -82,10 +88,10 @@
mDensity = metrics.density;
Dependency.get(TunerService.class).addTunable(this, SIZE);
+ }
+ private void setupPadding(int padding) {
// Add some padding to all the content near the edge of the screen.
- int padding = mContext.getResources().getDimensionPixelSize(
- R.dimen.rounded_corner_content_padding);
StatusBar sb = getComponent(StatusBar.class);
View statusBar = sb.getStatusBarWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 699fdef..6777ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -25,6 +25,7 @@
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Handler;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -37,8 +38,6 @@
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.FlingAnimationUtils;
-import java.util.HashMap;
-
public class SwipeHelper implements Gefingerpoken {
static final String TAG = "com.android.systemui.SwipeHelper";
private static final boolean DEBUG = false;
@@ -51,10 +50,10 @@
public static final int X = 0;
public static final int Y = 1;
- private float SWIPE_ESCAPE_VELOCITY = 500f; // dp/sec
- private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
- private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
- private int MAX_DISMISS_VELOCITY = 4000; // dp/sec
+ private static final float SWIPE_ESCAPE_VELOCITY = 500f; // dp/sec
+ private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
+ private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
+ private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
@@ -65,13 +64,13 @@
private float mMinSwipeProgress = 0f;
private float mMaxSwipeProgress = 1f;
- private FlingAnimationUtils mFlingAnimationUtils;
+ private final FlingAnimationUtils mFlingAnimationUtils;
private float mPagingTouchSlop;
- private Callback mCallback;
- private Handler mHandler;
- private int mSwipeDirection;
- private VelocityTracker mVelocityTracker;
- private FalsingManager mFalsingManager;
+ private final Callback mCallback;
+ private final Handler mHandler;
+ private final int mSwipeDirection;
+ private final VelocityTracker mVelocityTracker;
+ private final FalsingManager mFalsingManager;
private float mInitialTouchPos;
private float mPerpendicularInitialTouchPos;
@@ -86,16 +85,16 @@
private boolean mLongPressSent;
private LongPressListener mLongPressListener;
private Runnable mWatchLongPress;
- private long mLongPressTimeout;
+ private final long mLongPressTimeout;
final private int[] mTmpPos = new int[2];
- private int mFalsingThreshold;
+ private final int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
private boolean mDisableHwLayers;
- private boolean mFadeDependingOnAmountSwiped;
- private Context mContext;
+ private final boolean mFadeDependingOnAmountSwiped;
+ private final Context mContext;
- private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
+ private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
public SwipeHelper(int swipeDirection, Callback callback, Context context) {
mContext = context;
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/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 2742fdd..2b2ad69 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -18,7 +18,6 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
@@ -32,10 +31,12 @@
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -48,9 +49,11 @@
import android.transition.AutoTransition;
import android.transition.Transition;
import android.transition.TransitionManager;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -71,7 +74,6 @@
import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -96,8 +98,7 @@
*
* Methods ending in "H" must be called on the (ui) handler.
*/
-public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable,
- ColorExtractor.OnColorsChangedListener {
+public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private static final String TAG = Util.logTag(VolumeDialogImpl.class);
public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
@@ -107,8 +108,6 @@
private final Context mContext;
private final H mHandler = new H();
- private final GradientDrawable mGradientDrawable;
- private final ColorExtractor mColorExtractor;
private final VolumeDialogController mController;
private Window mWindow;
@@ -163,9 +162,6 @@
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
- mGradientDrawable = new GradientDrawable(mContext);
- mGradientDrawable.setAlpha((int) (ScrimController.GRADIENT_SCRIM_ALPHA * 255));
- mColorExtractor = Dependency.get(SysuiColorExtractor.class);
}
public void init(int windowType, Callback callback) {
@@ -187,7 +183,6 @@
@Override
public void destroy() {
mController.removeCallback(mControllerCallbackH);
- mColorExtractor.removeOnColorsChangedListener(this);
}
private void initDialog() {
@@ -198,52 +193,64 @@
mShowing = false;
mWindow = mDialog.getWindow();
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
- mWindow.setBackgroundDrawable(mGradientDrawable);
+ mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
mDialog.setCanceledOnTouchOutside(true);
final Resources res = mContext.getResources();
+ final WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.type = mWindowType;
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle(VolumeDialogImpl.class.getSimpleName());
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
+ lp.gravity = Gravity.TOP;
+ lp.windowAnimations = -1;
+ mWindow.setAttributes(lp);
mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
- mDialog.setContentView(R.layout.volume_dialog_wrapped);
- mDialogView = mDialog.findViewById(R.id.volume_dialog);
- mDialogView.setOnHoverListener((v, event) -> {
- int action = event.getActionMasked();
- mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
- || (action == MotionEvent.ACTION_HOVER_MOVE);
- rescheduleTimeoutH();
- return true;
+ mDialog.setContentView(R.layout.volume_dialog);
+ mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
+ mDialogView.setOnHoverListener(new View.OnHoverListener() {
+ @Override
+ public boolean onHover(View v, MotionEvent event) {
+ int action = event.getActionMasked();
+ mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
+ || (action == MotionEvent.ACTION_HOVER_MOVE);
+ rescheduleTimeoutH();
+ return true;
+ }
});
- mColorExtractor.addOnColorsChangedListener(this);
- mGradientDrawable.setScreenSize(displaySize.x, displaySize.y);
-
mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
mExpanded = false;
- mExpandButton = mDialogView.findViewById(R.id.volume_expand_button);
+ mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
mExpandButton.setOnClickListener(mClickExpand);
mExpandButton.setVisibility(
AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE);
+ updateWindowWidthH();
updateExpandButtonH();
- mMotion = new VolumeDialogMotion(mDialog, (View) mDialogView.getParent(),
- mDialogContentView, mExpandButton, mGradientDrawable, animating -> {
- if (animating) return;
- if (mPendingStateChanged) {
- mHandler.sendEmptyMessage(H.STATE_CHANGED);
- mPendingStateChanged = false;
- }
- if (mPendingRecheckAll) {
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- mPendingRecheckAll = false;
+ mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
+ new VolumeDialogMotion.Callback() {
+ @Override
+ public void onAnimatingChanged(boolean animating) {
+ if (animating) return;
+ if (mPendingStateChanged) {
+ mHandler.sendEmptyMessage(H.STATE_CHANGED);
+ mPendingStateChanged = false;
+ }
+ if (mPendingRecheckAll) {
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ mPendingRecheckAll = false;
+ }
}
});
@@ -268,20 +275,11 @@
addExistingRows();
}
mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
- mZenFooter = mDialog.findViewById(R.id.volume_zen_footer);
+ mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
mZenFooter.init(mZenModeController);
- mZenPanel = mDialog.findViewById(R.id.tuner_zen_mode_panel);
+ mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
mZenPanel.init(mZenModeController);
mZenPanel.setCallback(mZenPanelCallback);
-
- final WindowManager.LayoutParams lp = mWindow.getAttributes();
- lp.width = MATCH_PARENT;
- lp.height = MATCH_PARENT;
- lp.type = mWindowType;
- lp.format = PixelFormat.TRANSLUCENT;
- lp.setTitle(VolumeDialogImpl.class.getSimpleName());
- lp.windowAnimations = -1;
- mWindow.setAttributes(lp);
}
@Override
@@ -295,6 +293,20 @@
return ColorStateList.valueOf(mContext.getColor(colorResId));
}
+ private void updateWindowWidthH() {
+ final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams();
+ final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
+ int w = dm.widthPixels;
+ final int max = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.volume_dialog_panel_width);
+ if (w > max) {
+ w = max;
+ }
+ lp.width = w;
+ mDialogView.setLayoutParams(lp);
+ }
+
public void setStreamImportant(int stream, boolean important) {
mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
}
@@ -478,10 +490,6 @@
rescheduleTimeoutH();
if (mShowing) return;
mShowing = true;
- ColorExtractor.GradientColors colors = mColorExtractor.getColors(
- mKeyguard.isKeyguardLocked() ? WallpaperManager.FLAG_LOCK
- : WallpaperManager.FLAG_SYSTEM);
- mGradientDrawable.setColors(colors, false);
mMotion.startShow();
Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
mController.notifyVisible(true);
@@ -539,8 +547,10 @@
}
private void updateDialogBottomMarginH() {
+ final long diff = System.currentTimeMillis() - mCollapseTime;
+ final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
- final int bottomMargin =
+ final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
if (bottomMargin != mlp.bottomMargin) {
if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
@@ -570,7 +580,7 @@
TransitionManager.endTransitions(mDialogView);
final VolumeRow activeRow = getActiveRow();
if (!dismissing) {
- mWindow.setLayout(mWindow.getAttributes().width, MATCH_PARENT);
+ mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT);
TransitionManager.beginDelayedTransition(mDialogView, getTransition());
}
updateRowsH(activeRow);
@@ -632,7 +642,7 @@
final boolean isActive = row == activeRow;
final boolean shouldBeVisible = shouldBeVisibleH(row, isActive);
Util.setVisOrGone(row.view, shouldBeVisible);
- Util.setVisOrGone(row.header, shouldBeVisible && mExpanded);
+ Util.setVisOrGone(row.header, shouldBeVisible);
if (row.view.isShown()) {
updateVolumeRowSliderTintH(row, isActive);
}
@@ -689,18 +699,12 @@
final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
&& (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
&& !mZenPanel.isEditing();
-
- if (wasVisible != visible) {
- mZenFooter.update();
- if (visible) {
- HardwareUiLayout.get(mZenFooter).setDivisionView(mZenFooter);
- } else {
- mHandler.postDelayed(() ->
- HardwareUiLayout.get(mZenFooter).setDivisionView(mZenFooter),
- mExpandButtonAnimationDuration);
- }
- Util.setVisOrGone(mZenFooter, visible);
+ TransitionManager.beginDelayedTransition(mDialogView, getTransition());
+ if (wasVisible != visible && !visible) {
+ prepareForCollapse();
}
+ Util.setVisOrGone(mZenFooter, visible);
+ mZenFooter.update();
final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
final boolean fullVisible = mShowFullZen && !visible;
@@ -960,7 +964,8 @@
@Override
public void onTransitionEnd(Transition transition) {
- mWindow.setLayout(MATCH_PARENT, MATCH_PARENT);
+ mWindow.setLayout(
+ mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
@@ -969,7 +974,8 @@
@Override
public void onTransitionPause(Transition transition) {
- mWindow.setLayout(MATCH_PARENT, MATCH_PARENT);
+ mWindow.setLayout(
+ mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
@@ -1021,6 +1027,7 @@
initDialog();
mDensity = density;
}
+ updateWindowWidthH();
mConfigurableTexts.update();
mZenFooter.onConfigurationChanged();
}
@@ -1076,26 +1083,10 @@
if (mExpandButtonAnimationRunning) return;
final boolean newExpand = !mExpanded;
Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand);
- if (!newExpand) {
- HardwareUiLayout.get(mDialogContentView).setCollapse();
- }
updateExpandedH(newExpand, false /* dismissing */);
}
};
- @Override
- public void onColorsChanged(ColorExtractor extractor, int which) {
- if (mKeyguard.isKeyguardLocked()) {
- if ((WallpaperManager.FLAG_LOCK & which) != 0) {
- mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK));
- }
- } else {
- if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
- mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
- }
- }
- }
-
private final class H extends Handler {
private static final int SHOW = 1;
private static final int DISMISS = 2;
@@ -1167,8 +1158,8 @@
event.setPackageName(mContext.getPackageName());
ViewGroup.LayoutParams params = getWindow().getAttributes();
- boolean isFullScreen = (params.width == MATCH_PARENT) &&
- (params.height == MATCH_PARENT);
+ boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
+ (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
event.setFullScreen(isFullScreen);
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
index 2df2227..01d31e2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.systemui.volume;
import android.animation.Animator;
@@ -41,10 +42,8 @@
private final View mDialogView;
private final ViewGroup mContents; // volume rows + zen footer
private final View mChevron;
- private final Drawable mBackground;
private final Handler mHandler = new Handler();
private final Callback mCallback;
- private final int mBackgroundTargetAlpha;
private boolean mAnimating; // show or dismiss animation is running
private boolean mShowing; // show animation is running
@@ -53,14 +52,12 @@
private ValueAnimator mContentsPositionAnimator;
public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
- Drawable background, Callback callback) {
+ Callback callback) {
mDialog = dialog;
mDialogView = dialogView;
mContents = contents;
mChevron = chevron;
mCallback = callback;
- mBackground = background;
- mBackgroundTargetAlpha = mBackground.getAlpha();
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
@@ -71,9 +68,8 @@
@Override
public void onShow(DialogInterface dialog) {
if (D.BUG) Log.d(TAG, "mDialog.onShow");
- final int w = mDialogView.getWidth() / 4;
- mDialogView.setTranslationX(w);
- mBackground.setAlpha(0);
+ final int h = mDialogView.getHeight();
+ mDialogView.setTranslationY(-h);
startShowAnimation();
}
});
@@ -122,7 +118,7 @@
}
private int chevronDistance() {
- return 0;
+ return mChevron.getHeight() / 6;
}
private int chevronPosY() {
@@ -133,29 +129,26 @@
private void startShowAnimation() {
if (D.BUG) Log.d(TAG, "startShowAnimation");
mDialogView.animate()
- .translationX(0)
.translationY(0)
- .alpha(1)
.setDuration(scaledDuration(300))
.setInterpolator(new LogDecelerateInterpolator())
.setListener(null)
.setUpdateListener(animation -> {
- mBackground.setAlpha(
- (int) (animation.getAnimatedFraction() * mBackgroundTargetAlpha));
if (mChevronPositionAnimator != null) {
final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
if (mChevronPositionAnimator == null) return;
// reposition chevron
final int posY = chevronPosY();
+ mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
- mBackground.setAlpha(mBackgroundTargetAlpha);
if (mChevronPositionAnimator == null) return;
// reposition chevron
final int posY = chevronPosY();
+ mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
}
})
.start();
@@ -171,13 +164,19 @@
if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
setShowing(false);
}
-
@Override
public void onAnimationCancel(Animator animation) {
if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
mCancelled = true;
}
});
+ mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float v = (Float) animation.getAnimatedValue();
+ mContents.setTranslationY(v + -mDialogView.getTranslationY());
+ }
+ });
mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
mContentsPositionAnimator.start();
@@ -219,30 +218,34 @@
setShowing(false);
}
mDialogView.animate()
- .translationX(mDialogView.getWidth() / 4)
- .alpha(0)
+ .translationY(-mDialogView.getHeight())
.setDuration(scaledDuration(250))
.setInterpolator(new LogAccelerateInterpolator())
- .setUpdateListener(animation -> {
- final float v = 1 - mChevronPositionAnimator.getAnimatedFraction();
- mBackground.setAlpha((int) (v * mBackgroundTargetAlpha));
+ .setUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mContents.setTranslationY(-mDialogView.getTranslationY());
+ final int posY = chevronPosY();
+ mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
+ }
})
.setListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
-
@Override
public void onAnimationEnd(Animator animation) {
if (mCancelled) return;
if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
- mHandler.postDelayed(() -> {
- if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
- mDialog.dismiss();
- onComplete.run();
- setDismissing(false);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
+ mDialog.dismiss();
+ onComplete.run();
+ setDismissing(false);
+ }
}, PRE_DISMISS_DELAY);
}
-
@Override
public void onAnimationCancel(Animator animation) {
if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index b3a4007..2edcd71 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4134,6 +4134,31 @@
// OS: O DR
ACTION_APP_LOCATION_CHECK = 1021;
+ // Device headset status
+ // CATEGORY: OTHER
+ // SUBTYPE: 1 is DON, 2 is DOFF
+ // OS: O DR
+ ACTION_HEADSET_STATUS = 1022;
+
+ // Device Headset Plug status
+ // CATEGORY: OTHER
+ // SUBTYPE: 1 is AC power, 2 is USB power, 3 is Unplug
+ // OS: O DR
+ ACTION_HEADSET_PLUG = 1023;
+
+ // Device Headset battery level on Plug
+ // CATEGORY: OTHER
+ // FIELD - The battery percentage when the user decided to plug in
+ // Type: integer
+ // OS: O DR
+ FIELD_PLUG_BATTERY_PERCENTAGE = 1024;
+
+ // Device Headset Pose status
+ // CATEGORY: OTHER
+ // SUBTYPE: 1 is 6DOF, 2 is 3DOF
+ // OS: O DR
+ ACTION_HEADSET_POSE_STATUS = 1025;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 7275461..eda283e 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -324,58 +324,13 @@
ActivityManager.OnUidImportanceListener uidImportanceListener
= new ActivityManager.OnUidImportanceListener() {
@Override
- public void onUidImportance(int uid, int importance) {
- boolean foreground = isImportanceForeground(importance);
- HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
- synchronized (mLock) {
- for (Entry<String, ArrayList<UpdateRecord>> entry
- : mRecordsByProvider.entrySet()) {
- String provider = entry.getKey();
- for (UpdateRecord record : entry.getValue()) {
- if (record.mReceiver.mIdentity.mUid == uid
- && record.mIsForegroundUid != foreground) {
- if (D) Log.d(TAG, "request from uid " + uid + " is now "
- + (foreground ? "foreground" : "background)"));
- record.mIsForegroundUid = foreground;
-
- if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
- affectedProviders.add(provider);
- }
- }
- }
+ public void onUidImportance(final int uid, final int importance) {
+ mLocationHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onUidImportanceChanged(uid, importance);
}
- for (String provider : affectedProviders) {
- applyRequirementsLocked(provider);
- }
-
- for (Entry<IGnssMeasurementsListener, Identity> entry
- : mGnssMeasurementsListeners.entrySet()) {
- if (entry.getValue().mUid == uid) {
- if (D) Log.d(TAG, "gnss measurements listener from uid " + uid
- + " is now " + (foreground ? "foreground" : "background)"));
- if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssMeasurementsProvider.addListener(entry.getKey());
- } else {
- mGnssMeasurementsProvider.removeListener(entry.getKey());
- }
- }
- }
-
- for (Entry<IGnssNavigationMessageListener, Identity> entry
- : mGnssNavigationMessageListeners.entrySet()) {
- if (entry.getValue().mUid == uid) {
- if (D) Log.d(TAG, "gnss navigation message listener from uid "
- + uid + " is now "
- + (foreground ? "foreground" : "background)"));
- if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssNavigationMessageProvider.addListener(entry.getKey());
- } else {
- mGnssNavigationMessageProvider.removeListener(entry.getKey());
- }
- }
- }
- }
-
+ });
}
};
mActivityManager.addOnUidImportanceListener(uidImportanceListener,
@@ -455,6 +410,59 @@
}, UserHandle.ALL, intentFilter, null, mLocationHandler);
}
+ private void onUidImportanceChanged(int uid, int importance) {
+ boolean foreground = isImportanceForeground(importance);
+ HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
+ synchronized (mLock) {
+ for (Entry<String, ArrayList<UpdateRecord>> entry
+ : mRecordsByProvider.entrySet()) {
+ String provider = entry.getKey();
+ for (UpdateRecord record : entry.getValue()) {
+ if (record.mReceiver.mIdentity.mUid == uid
+ && record.mIsForegroundUid != foreground) {
+ if (D) Log.d(TAG, "request from uid " + uid + " is now "
+ + (foreground ? "foreground" : "background)"));
+ record.mIsForegroundUid = foreground;
+
+ if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+ affectedProviders.add(provider);
+ }
+ }
+ }
+ }
+ for (String provider : affectedProviders) {
+ applyRequirementsLocked(provider);
+ }
+
+ for (Entry<IGnssMeasurementsListener, Identity> entry
+ : mGnssMeasurementsListeners.entrySet()) {
+ if (entry.getValue().mUid == uid) {
+ if (D) Log.d(TAG, "gnss measurements listener from uid " + uid
+ + " is now " + (foreground ? "foreground" : "background)"));
+ if (foreground || isThrottlingExemptLocked(entry.getValue())) {
+ mGnssMeasurementsProvider.addListener(entry.getKey());
+ } else {
+ mGnssMeasurementsProvider.removeListener(entry.getKey());
+ }
+ }
+ }
+
+ for (Entry<IGnssNavigationMessageListener, Identity> entry
+ : mGnssNavigationMessageListeners.entrySet()) {
+ if (entry.getValue().mUid == uid) {
+ if (D) Log.d(TAG, "gnss navigation message listener from uid "
+ + uid + " is now "
+ + (foreground ? "foreground" : "background)"));
+ if (foreground || isThrottlingExemptLocked(entry.getValue())) {
+ mGnssNavigationMessageProvider.addListener(entry.getKey());
+ } else {
+ mGnssNavigationMessageProvider.removeListener(entry.getKey());
+ }
+ }
+ }
+ }
+ }
+
private static boolean isImportanceForeground(int importance) {
return importance <= FOREGROUND_IMPORTANCE_CUTOFF;
}
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index 1810823..21aeee2 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -49,6 +49,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -797,12 +798,13 @@
pw.println(
" " + "mScLocale=" + req.mLocale + " mUid=" + req.mUserId);
}
- final int N = grp.mListeners.size();
+ final int N = grp.mListeners.getRegisteredCallbackCount();
for (int i = 0; i < N; i++) {
- final InternalDeathRecipient listener = grp.mListeners.get(i);
+ final ISpellCheckerSessionListener mScListener =
+ grp.mListeners.getRegisteredCallbackItem(i);
pw.println(" " + "Listener #" + i + ":");
- pw.println(" " + "mScListener=" + listener.mScListener);
- pw.println(" " + "mGroup=" + listener.mGroup);
+ pw.println(" " + "mScListener=" + mScListener);
+ pw.println(" " + "mGroup=" + grp);
}
}
pw.println("");
@@ -840,7 +842,7 @@
private final class SpellCheckerBindGroup {
private final String TAG = SpellCheckerBindGroup.class.getSimpleName();
private final InternalServiceConnection mInternalConnection;
- private final ArrayList<InternalDeathRecipient> mListeners = new ArrayList<>();
+ private final InternalDeathRecipients mListeners;
private boolean mUnbindCalled;
private ISpellCheckerService mSpellChecker;
private boolean mConnected;
@@ -849,6 +851,7 @@
public SpellCheckerBindGroup(InternalServiceConnection connection) {
mInternalConnection = connection;
+ mListeners = new InternalDeathRecipients(this);
}
public void onServiceConnected(ISpellCheckerService spellChecker) {
@@ -881,26 +884,7 @@
Slog.w(TAG, "remove listener: " + listener.hashCode());
}
synchronized(mSpellCheckerMap) {
- final int size = mListeners.size();
- final ArrayList<InternalDeathRecipient> removeList = new ArrayList<>();
- for (int i = 0; i < size; ++i) {
- final InternalDeathRecipient tempRecipient = mListeners.get(i);
- if(tempRecipient.hasSpellCheckerListener(listener)) {
- if (DBG) {
- Slog.w(TAG, "found existing listener.");
- }
- removeList.add(tempRecipient);
- }
- }
- final int removeSize = removeList.size();
- for (int i = 0; i < removeSize; ++i) {
- if (DBG) {
- Slog.w(TAG, "Remove " + removeList.get(i));
- }
- final InternalDeathRecipient idr = removeList.get(i);
- idr.mScListener.asBinder().unlinkToDeath(idr, 0);
- mListeners.remove(idr);
- }
+ mListeners.unregister(listener);
cleanLocked();
}
}
@@ -914,7 +898,7 @@
return;
}
// If there are no more active listeners, clean up. Only do this once.
- if (!mListeners.isEmpty()) {
+ if (mListeners.getRegisteredCallbackCount() > 0) {
return;
}
if (!mPendingSessionRequests.isEmpty()) {
@@ -938,12 +922,10 @@
public void removeAll() {
Slog.e(TAG, "Remove the spell checker bind unexpectedly.");
synchronized(mSpellCheckerMap) {
- final int size = mListeners.size();
+ final int size = mListeners.getRegisteredCallbackCount();
for (int i = 0; i < size; ++i) {
- final InternalDeathRecipient idr = mListeners.get(i);
- idr.mScListener.asBinder().unlinkToDeath(idr, 0);
+ mListeners.unregister(mListeners.getRegisteredCallbackItem(i));
}
- mListeners.clear();
mPendingSessionRequests.clear();
mOnGoingSessionRequests.clear();
cleanLocked();
@@ -984,12 +966,9 @@
return;
}
if (mOnGoingSessionRequests.remove(request)) {
- final InternalDeathRecipient recipient =
- new InternalDeathRecipient(this, request.mScListener);
try {
request.mTsListener.onServiceConnected(newSession);
- request.mScListener.asBinder().linkToDeath(recipient, 0);
- mListeners.add(recipient);
+ mListeners.register(request.mScListener);
} catch (RemoteException e) {
// Technically this can happen if the spell checker client app is already
// dead. We can just forget about this request; the request is already
@@ -1045,24 +1024,21 @@
}
}
- private static final class InternalDeathRecipient implements IBinder.DeathRecipient {
- public final ISpellCheckerSessionListener mScListener;
+ private final class InternalDeathRecipients extends
+ RemoteCallbackList<ISpellCheckerSessionListener> {
private final SpellCheckerBindGroup mGroup;
- public InternalDeathRecipient(SpellCheckerBindGroup group,
- ISpellCheckerSessionListener scListener) {
- mScListener = scListener;
+ public InternalDeathRecipients(SpellCheckerBindGroup group) {
mGroup = group;
}
- public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) {
- return listener.asBinder().equals(mScListener.asBinder());
+ @Override
+ public void onCallbackDied(ISpellCheckerSessionListener listener) {
+ synchronized(mSpellCheckerMap) {
+ mGroup.removeListener(listener);
+ }
}
- @Override
- public void binderDied() {
- mGroup.removeListener(mScListener);
- }
}
private static final class ISpellCheckerServiceCallbackBinder
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5bffe1e..de853df 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4044,7 +4044,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..a145435 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -2308,6 +2308,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)));
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e5ab784..c6307a7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -706,6 +706,8 @@
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
+ mRecordMonitor = new RecordingActivityMonitor(mContext);
+
readAndSetLowRamDevice();
// Call setRingerModeInt() to apply correct mute
@@ -6309,6 +6311,8 @@
dumpAudioPolicies(pw);
mPlaybackMonitor.dump(pw);
+
+ mRecordMonitor.dump(pw);
}
private static String safeMediaVolumeStateToString(Integer state) {
@@ -6730,10 +6734,13 @@
//======================
// Audio policy callbacks from AudioSystem for recording configuration updates
//======================
- private final RecordingActivityMonitor mRecordMonitor = new RecordingActivityMonitor();
+ private final RecordingActivityMonitor mRecordMonitor;
public void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
- mRecordMonitor.registerRecordingCallback(rcdb);
+ final boolean isPrivileged =
+ (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+ mRecordMonitor.registerRecordingCallback(rcdb, isPrivileged);
}
public void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
@@ -6741,7 +6748,10 @@
}
public List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
- return mRecordMonitor.getActiveRecordingConfigurations();
+ final boolean isPrivileged =
+ (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+ return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged);
}
public void disableRingtoneSync(final int userId) {
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 57d55de..34309b6 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -16,8 +16,11 @@
package com.android.server.audio;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioSystem;
import android.media.IRecordingConfigDispatcher;
@@ -26,7 +29,10 @@
import android.os.RemoteException;
import android.util.Log;
+import java.io.PrintWriter;
+import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -39,31 +45,47 @@
public final static String TAG = "AudioService.RecordingActivityMonitor";
private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
+ // a public client is one that needs an anonymized version of the playback configurations, we
+ // keep track of whether there is at least one to know when we need to create the list of
+ // playback configurations that do not contain uid/package name information.
+ private boolean mHasPublicClients = false;
private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs =
new HashMap<Integer, AudioRecordingConfiguration>();
- RecordingActivityMonitor() {
+ private final PackageManager mPackMan;
+
+ RecordingActivityMonitor(Context ctxt) {
RecMonitorClient.sMonitor = this;
+ mPackMan = ctxt.getPackageManager();
}
/**
* Implementation of android.media.AudioSystem.AudioRecordingCallback
*/
- public void onRecordingConfigurationChanged(int event, int session, int source,
- int[] recordingInfo) {
+ public void onRecordingConfigurationChanged(int event, int uid, int session, int source,
+ int[] recordingInfo, String packName) {
if (MediaRecorder.isSystemOnlyAudioSource(source)) {
return;
}
- final List<AudioRecordingConfiguration> configs =
- updateSnapshot(event, session, source, recordingInfo);
- if (configs != null){
- synchronized(mClients) {
+ final List<AudioRecordingConfiguration> configsSystem =
+ updateSnapshot(event, uid, session, source, recordingInfo);
+ if (configsSystem != null){
+ synchronized (mClients) {
+ // list of recording configurations for "public consumption". It is only computed if
+ // there are non-system recording activity listeners.
+ final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients ?
+ anonymizeForPublicConsumption(configsSystem) :
+ new ArrayList<AudioRecordingConfiguration>();
final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
while (clientIterator.hasNext()) {
+ final RecMonitorClient rmc = clientIterator.next();
try {
- clientIterator.next().mDispatcherCb.dispatchRecordingConfigChange(
- configs);
+ if (rmc.mIsPrivileged) {
+ rmc.mDispatcherCb.dispatchRecordingConfigChange(configsSystem);
+ } else {
+ rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
}
@@ -72,17 +94,42 @@
}
}
+ protected void dump(PrintWriter pw) {
+ // players
+ pw.println("\nRecordActivityMonitor dump time: "
+ + DateFormat.getTimeInstance().format(new Date()));
+ synchronized(mRecordConfigs) {
+ for (AudioRecordingConfiguration conf : mRecordConfigs.values()) {
+ conf.dump(pw);
+ }
+ }
+ }
+
+ private ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
+ List<AudioRecordingConfiguration> sysConfigs) {
+ ArrayList<AudioRecordingConfiguration> publicConfigs =
+ new ArrayList<AudioRecordingConfiguration>();
+ // only add active anonymized configurations,
+ for (AudioRecordingConfiguration config : sysConfigs) {
+ publicConfigs.add(AudioRecordingConfiguration.anonymizedCopy(config));
+ }
+ return publicConfigs;
+ }
+
void initMonitor() {
AudioSystem.setRecordingCallback(this);
}
- void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
+ void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
if (rcdb == null) {
return;
}
- synchronized(mClients) {
- final RecMonitorClient rmc = new RecMonitorClient(rcdb);
+ synchronized (mClients) {
+ final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
if (rmc.init()) {
+ if (!isPrivileged) {
+ mHasPublicClients = true;
+ }
mClients.add(rmc);
}
}
@@ -92,22 +139,34 @@
if (rcdb == null) {
return;
}
- synchronized(mClients) {
+ synchronized (mClients) {
final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
+ boolean hasPublicClients = false;
while (clientIterator.hasNext()) {
RecMonitorClient rmc = clientIterator.next();
if (rcdb.equals(rmc.mDispatcherCb)) {
rmc.release();
clientIterator.remove();
- break;
+ } else {
+ if (!rmc.mIsPrivileged) {
+ hasPublicClients = true;
+ }
}
}
+ mHasPublicClients = hasPublicClients;
}
}
- List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
+ List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
synchronized(mRecordConfigs) {
- return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
+ if (isPrivileged) {
+ return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
+ } else {
+ final List<AudioRecordingConfiguration> configsPublic =
+ anonymizeForPublicConsumption(
+ new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values()));
+ return configsPublic;
+ }
}
}
@@ -122,8 +181,8 @@
* @return null if the list of active recording sessions has not been modified, a list
* with the current active configurations otherwise.
*/
- private List<AudioRecordingConfiguration> updateSnapshot(int event, int session, int source,
- int[] recordingInfo) {
+ private List<AudioRecordingConfiguration> updateSnapshot(int event, int uid, int session,
+ int source, int[] recordingInfo) {
final boolean configChanged;
final ArrayList<AudioRecordingConfiguration> configs;
synchronized(mRecordConfigs) {
@@ -147,10 +206,19 @@
.build();
final int patchHandle = recordingInfo[6];
final Integer sessionKey = new Integer(session);
+
+ final String[] packages = mPackMan.getPackagesForUid(uid);
+ final String packageName;
+ if (packages != null && packages.length > 0) {
+ packageName = packages[0];
+ } else {
+ packageName = "";
+ }
+ final AudioRecordingConfiguration updatedConfig =
+ new AudioRecordingConfiguration(uid, session, source,
+ clientFormat, deviceFormat, patchHandle, packageName);
+
if (mRecordConfigs.containsKey(sessionKey)) {
- final AudioRecordingConfiguration updatedConfig =
- new AudioRecordingConfiguration(session, source,
- clientFormat, deviceFormat, patchHandle);
if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) {
configChanged = false;
} else {
@@ -160,9 +228,7 @@
configChanged = true;
}
} else {
- mRecordConfigs.put(sessionKey,
- new AudioRecordingConfiguration(session, source,
- clientFormat, deviceFormat, patchHandle));
+ mRecordConfigs.put(sessionKey, updatedConfig);
configChanged = true;
}
break;
@@ -189,9 +255,11 @@
static RecordingActivityMonitor sMonitor;
final IRecordingConfigDispatcher mDispatcherCb;
+ final boolean mIsPrivileged;
- RecMonitorClient(IRecordingConfigDispatcher rcdb) {
+ RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
mDispatcherCb = rcdb;
+ mIsPrivileged = isPrivileged;
}
public void binderDied() {
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index da75722..4511aa9 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -789,6 +789,18 @@
}
};
mGnssMetrics = new GnssMetrics();
+
+ /*
+ * 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 0c854c2..c1c14e8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7175,16 +7175,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)) {
@@ -7209,6 +7206,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/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index ebad81c..5e5ba46 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -816,6 +816,12 @@
/** {@hide} */
@Override
+ public boolean canLoadUnsafeResources() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
public IBinder getActivityToken() {
throw new UnsupportedOperationException();
}