Merge "Allow more than one temp view to be registered at a time" into pi-dev
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index ee667c2..1b6b5a0 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -80,8 +80,7 @@
* {@link #getBondedDevices()}; start device discovery with
* {@link #startDiscovery()}; or create a {@link BluetoothServerSocket} to
* listen for incoming RFComm connection requests with {@link
- * #listenUsingRfcommWithServiceRecord(String, UUID)}; listen for incoming L2CAP Connection-oriented
- * Channels (CoC) connection requests with listenUsingL2capCoc(int)}; or start a scan for
+ * #listenUsingRfcommWithServiceRecord(String, UUID)}; or start a scan for
* Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}.
* </p>
* <p>This class is thread safe.</p>
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 4ed2500..ef1b0bd 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -701,10 +701,14 @@
* <p>If the local device has already exposed services when this function
* is called, a service update notification will be sent to all clients.
*
+ * <p>The {@link BluetoothGattServerCallback#onServiceAdded} callback will indicate
+ * whether this service has been added successfully. Do not add another service
+ * before this callback.
+ *
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param service Service to be added to the list of services provided by this device.
- * @return true, if the service has been added successfully
+ * @return true, if the request to add service has been initiated
*/
public boolean addService(BluetoothGattService service) {
if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index ead6c25..fc58533 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -396,4 +396,14 @@
mPackage = pkg;
mClass = in.readString();
}
+
+ /**
+ * Interface for classes associated with a component name.
+ * @hide
+ */
+ @FunctionalInterface
+ public interface WithComponentName {
+ /** Return the associated component name. */
+ ComponentName getComponentName();
+ }
}
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index ded11cfd..2d521e9 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -28,10 +28,15 @@
public class SyncStatusInfo implements Parcelable {
private static final String TAG = "Sync";
- static final int VERSION = 5;
+ static final int VERSION = 6;
private static final int MAX_EVENT_COUNT = 10;
+ /**
+ * Number of sync sources. KEEP THIS AND SyncStorageEngine.SOURCES IN SYNC.
+ */
+ private static final int SOURCE_COUNT = 6;
+
public final int authorityId;
/**
@@ -120,7 +125,10 @@
public long initialFailureTime;
public boolean pending;
public boolean initialize;
-
+
+ public final long[] perSourceLastSuccessTimes = new long[SOURCE_COUNT];
+ public final long[] perSourceLastFailureTimes = new long[SOURCE_COUNT];
+
// Warning: It is up to the external caller to ensure there are
// no race conditions when accessing this list
private ArrayList<Long> periodicSyncTimes;
@@ -191,6 +199,10 @@
todayStats.writeToParcel(parcel);
yesterdayStats.writeToParcel(parcel);
+
+ // Version 6.
+ parcel.writeLongArray(perSourceLastSuccessTimes);
+ parcel.writeLongArray(perSourceLastFailureTimes);
}
public SyncStatusInfo(Parcel parcel) {
@@ -260,6 +272,10 @@
todayStats.readFromParcel(parcel);
yesterdayStats.readFromParcel(parcel);
}
+ if (version >= 6) {
+ parcel.readLongArray(perSourceLastSuccessTimes);
+ parcel.readLongArray(perSourceLastFailureTimes);
+ }
}
public SyncStatusInfo(SyncStatusInfo other) {
@@ -284,6 +300,13 @@
}
mLastEventTimes.addAll(other.mLastEventTimes);
mLastEvents.addAll(other.mLastEvents);
+
+ copy(perSourceLastSuccessTimes, other.perSourceLastSuccessTimes);
+ copy(perSourceLastFailureTimes, other.perSourceLastFailureTimes);
+ }
+
+ private static void copy(long[] to, long[] from) {
+ System.arraycopy(from, 0, to, 0, to.length);
}
public void setPeriodicSyncTime(int index, long when) {
@@ -332,6 +355,34 @@
return mLastEvents.get(i);
}
+ /** Call this when a sync has succeeded. */
+ public void setLastSuccess(int source, long lastSyncTime) {
+ lastSuccessTime = lastSyncTime;
+ lastSuccessSource = source;
+ lastFailureTime = 0;
+ lastFailureSource = -1;
+ lastFailureMesg = null;
+ initialFailureTime = 0;
+
+ if (0 <= source && source < perSourceLastSuccessTimes.length) {
+ perSourceLastSuccessTimes[source] = lastSyncTime;
+ }
+ }
+
+ /** Call this when a sync has failed. */
+ public void setLastFailure(int source, long lastSyncTime, String failureMessage) {
+ lastFailureTime = lastSyncTime;
+ lastFailureSource = source;
+ lastFailureMesg = failureMessage;
+ if (initialFailureTime == 0) {
+ initialFailureTime = lastSyncTime;
+ }
+
+ if (0 <= source && source < perSourceLastFailureTimes.length) {
+ perSourceLastFailureTimes[source] = lastSyncTime;
+ }
+ }
+
public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
public SyncStatusInfo createFromParcel(Parcel in) {
return new SyncStatusInfo(in);
@@ -356,7 +407,7 @@
}
/**
- * If the last reset was not not today, move today's stats to yesterday's and clear today's.
+ * If the last reset was not today, move today's stats to yesterday's and clear today's.
*/
public void maybeResetTodayStats(boolean clockValid, boolean force) {
final long now = System.currentTimeMillis();
@@ -391,4 +442,4 @@
return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
&& c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1d232bf..0b4b921 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -4447,8 +4447,7 @@
pw.println(sb.toString());
}
- final long dischargeScreenOnCount =
- dischargeCount - dischargeScreenOffCount - dischargeScreenDozeCount;
+ final long dischargeScreenOnCount = dischargeCount - dischargeScreenOffCount;
if (dischargeScreenOnCount >= 0) {
sb.setLength(0);
sb.append(prefix);
diff --git a/core/java/android/os/ISchedulingPolicyService.aidl b/core/java/android/os/ISchedulingPolicyService.aidl
index efcf59a..78d299a 100644
--- a/core/java/android/os/ISchedulingPolicyService.aidl
+++ b/core/java/android/os/ISchedulingPolicyService.aidl
@@ -31,4 +31,13 @@
*/
int requestPriority(int pid, int tid, int prio, boolean isForApp);
+ /**
+ * Move media.codec process between SP_FOREGROUND and SP_TOP_APP.
+ * When 'enable' is 'true', server will attempt to move media.codec process
+ * from SP_FOREGROUND into SP_TOP_APP cpuset. A valid 'client' must be
+ * provided for the server to receive death notifications. When 'enable'
+ * is 'false', server will attempt to move media.codec process back to
+ * the original cpuset, and 'client' is ignored in this case.
+ */
+ int requestCpusetBoost(boolean enable, IBinder client);
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 60537a4..6c18b45 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -496,9 +496,9 @@
*
* <p>Apps that use standard Android widgets support autofill out-of-the-box and need to do
* very little to improve their user experience (annotating autofillable views and providing
- * autofill hints). However, some apps do their own rendering and the rendered content may
- * contain semantic structure that needs to be surfaced to the autofill framework. The platform
- * exposes APIs to achieve this, however it could take some time until these apps implement
+ * autofill hints). However, some apps (typically browsers) do their own rendering and the rendered
+ * content may contain semantic structure that needs to be surfaced to the autofill framework. The
+ * platform exposes APIs to achieve this, however it could take some time until these apps implement
* autofill support.
*
* <p>To enable autofill for such apps the platform provides a compatibility mode in which the
@@ -521,15 +521,33 @@
* <meta-data android:name="android.autofill" android:resource="@xml/autofillservice" />
* </service></pre>
*
- * <P>In the XML file you can specify one or more packages for which to enable compatibility
+ * <p>In the XML file you can specify one or more packages for which to enable compatibility
* mode. Below is a sample meta-data declaration:
*
* <pre> <autofill-service xmlns:android="http://schemas.android.com/apk/res/android">
* <compatibility-package android:name="foo.bar.baz" android:maxLongVersionCode="1000000000"/>
* </autofill-service></pre>
*
- * <p>When using compatibility mode, the {@link SaveInfo.Builder#setFlags(int) SaveInfo flags}
- * automatically include {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE}.
+ * <p>Notice that compatibility mode has limitations such as:
+ * <ul>
+ * <li>No manual autofill requests. Hence, the {@link FillRequest}
+ * {@link FillRequest#getFlags() flags} never have the {@link FillRequest#FLAG_MANUAL_REQUEST} flag.
+ * <li>The value of password fields are most likely masked—for example, {@code ****} instead
+ * of {@code 1234}. Hence, you must be careful when using these values to avoid updating the user
+ * data with invalid input. For example, when you parse the {@link FillRequest} and detect a
+ * password field, you could check if its
+ * {@link android.app.assist.AssistStructure.ViewNode#getInputType()
+ * input type} has password flags and if so, don't add it to the {@link SaveInfo} object.
+ * <li>The autofill context is not always {@link AutofillManager#commit() committed} when an HTML
+ * form is submitted. Hence, you must use other mechanisms to trigger save, such as setting the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag on {@link SaveInfo.Builder#setFlags(int)}
+ * or using {@link SaveInfo.Builder#setTriggerId(AutofillId)}.
+ * <li>Browsers often provide their own autofill management system. When both the browser and
+ * the platform render an autofill dialog at the same time, the result can be confusing to the user.
+ * Such browsers typically offer an option for users to disable autofill, so your service should
+ * also allow users to disable compatiblity mode for specific apps. That way, it is up to the user
+ * to decide which autofill mechanism—the browser's or the platform's—should be used.
+ * </ul>
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 71b6084..6b16d42 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -20700,7 +20700,7 @@
if (canTakeFocus()) {
// We have a robust focus, so parents should no longer be wanting focus.
clearParentsWantFocus();
- } else if (!getViewRootImpl().isInLayout()) {
+ } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
// This is a weird case. Most-likely the user, rather than ViewRootImpl, called
// layout. In this case, there's no guarantee that parent layouts will be evaluated
// and thus the safest action is to clear focus here.
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 96016b4..ad50dc0 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -277,12 +277,12 @@
*/
@Nullable
public static PendingIntent createPendingIntent(
- @NonNull final Context context, @NonNull final Intent intent) {
+ @NonNull final Context context, @NonNull final Intent intent, int requestCode) {
switch (getIntentType(intent, context)) {
case IntentType.ACTIVITY:
- return PendingIntent.getActivity(context, 0, intent, 0);
+ return PendingIntent.getActivity(context, requestCode, intent, 0);
case IntentType.SERVICE:
- return PendingIntent.getService(context, 0, intent, 0);
+ return PendingIntent.getService(context, requestCode, intent, 0);
default:
return null;
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 2213355..910fcaa 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -412,7 +412,7 @@
boolean isPrimaryAction = true;
for (LabeledIntent labeledIntent : IntentFactory.create(
mContext, referenceTime, highestScoringResult, classifiedText)) {
- RemoteAction action = labeledIntent.asRemoteAction(mContext);
+ final RemoteAction action = labeledIntent.asRemoteAction(mContext);
if (isPrimaryAction) {
// For O backwards compatibility, the first RemoteAction is also written to the
// legacy API fields.
@@ -421,7 +421,7 @@
builder.setIntent(labeledIntent.getIntent());
builder.setOnClickListener(TextClassification.createIntentOnClickListener(
TextClassification.createPendingIntent(mContext,
- labeledIntent.getIntent())));
+ labeledIntent.getIntent(), labeledIntent.getRequestCode())));
isPrimaryAction = false;
}
builder.addAction(action);
@@ -559,14 +559,30 @@
* Helper class to store the information from which RemoteActions are built.
*/
private static final class LabeledIntent {
- private String mTitle;
- private String mDescription;
- private Intent mIntent;
- LabeledIntent(String title, String description, Intent intent) {
+ static final int DEFAULT_REQUEST_CODE = 0;
+
+ private final String mTitle;
+ private final String mDescription;
+ private final Intent mIntent;
+ private final int mRequestCode;
+
+ /**
+ * Initializes a LabeledIntent.
+ *
+ * <p>NOTE: {@code reqestCode} is required to not be {@link #DEFAULT_REQUEST_CODE}
+ * if distinguishing info (e.g. the classified text) is represented in intent extras only.
+ * In such circumstances, the request code should represent the distinguishing info
+ * (e.g. by generating a hashcode) so that the generated PendingIntent is (somewhat)
+ * unique. To be correct, the PendingIntent should be definitely unique but we try a
+ * best effort approach that avoids spamming the system with PendingIntents.
+ */
+ // TODO: Fix the issue mentioned above so the behaviour is correct.
+ LabeledIntent(String title, String description, Intent intent, int requestCode) {
mTitle = title;
mDescription = description;
mIntent = intent;
+ mRequestCode = requestCode;
}
String getTitle() {
@@ -581,6 +597,10 @@
return mIntent;
}
+ int getRequestCode() {
+ return mRequestCode;
+ }
+
RemoteAction asRemoteAction(Context context) {
final PackageManager pm = context.getPackageManager();
final ResolveInfo resolveInfo = pm.resolveActivity(mIntent, 0);
@@ -602,8 +622,8 @@
icon = Icon.createWithResource("android",
com.android.internal.R.drawable.ic_more_items);
}
- RemoteAction action = new RemoteAction(icon, mTitle, mDescription,
- TextClassification.createPendingIntent(context, mIntent));
+ final RemoteAction action = new RemoteAction(icon, mTitle, mDescription,
+ TextClassification.createPendingIntent(context, mIntent, mRequestCode));
action.setShouldShowIcon(shouldShowIcon);
return action;
}
@@ -659,13 +679,15 @@
context.getString(com.android.internal.R.string.email),
context.getString(com.android.internal.R.string.email_desc),
new Intent(Intent.ACTION_SENDTO)
- .setData(Uri.parse(String.format("mailto:%s", text)))),
+ .setData(Uri.parse(String.format("mailto:%s", text))),
+ LabeledIntent.DEFAULT_REQUEST_CODE),
new LabeledIntent(
context.getString(com.android.internal.R.string.add_contact),
context.getString(com.android.internal.R.string.add_contact_desc),
new Intent(Intent.ACTION_INSERT_OR_EDIT)
.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.EMAIL, text)));
+ .putExtra(ContactsContract.Intents.Insert.EMAIL, text),
+ text.hashCode()));
}
@NonNull
@@ -679,20 +701,23 @@
context.getString(com.android.internal.R.string.dial),
context.getString(com.android.internal.R.string.dial_desc),
new Intent(Intent.ACTION_DIAL).setData(
- Uri.parse(String.format("tel:%s", text)))));
+ Uri.parse(String.format("tel:%s", text))),
+ LabeledIntent.DEFAULT_REQUEST_CODE));
}
actions.add(new LabeledIntent(
context.getString(com.android.internal.R.string.add_contact),
context.getString(com.android.internal.R.string.add_contact_desc),
new Intent(Intent.ACTION_INSERT_OR_EDIT)
.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.PHONE, text)));
+ .putExtra(ContactsContract.Intents.Insert.PHONE, text),
+ text.hashCode()));
if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
actions.add(new LabeledIntent(
context.getString(com.android.internal.R.string.sms),
context.getString(com.android.internal.R.string.sms_desc),
new Intent(Intent.ACTION_SENDTO)
- .setData(Uri.parse(String.format("smsto:%s", text)))));
+ .setData(Uri.parse(String.format("smsto:%s", text))),
+ LabeledIntent.DEFAULT_REQUEST_CODE));
}
return actions;
}
@@ -706,7 +731,8 @@
context.getString(com.android.internal.R.string.map),
context.getString(com.android.internal.R.string.map_desc),
new Intent(Intent.ACTION_VIEW)
- .setData(Uri.parse(String.format("geo:0,0?q=%s", encText)))));
+ .setData(Uri.parse(String.format("geo:0,0?q=%s", encText))),
+ LabeledIntent.DEFAULT_REQUEST_CODE));
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Could not encode address", e);
}
@@ -728,7 +754,8 @@
context.getString(com.android.internal.R.string.browse),
context.getString(com.android.internal.R.string.browse_desc),
new Intent(Intent.ACTION_VIEW, Uri.parse(text))
- .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName())));
+ .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()),
+ LabeledIntent.DEFAULT_REQUEST_CODE));
}
@NonNull
@@ -754,7 +781,8 @@
context.getString(com.android.internal.R.string.view_flight),
context.getString(com.android.internal.R.string.view_flight_desc),
new Intent(Intent.ACTION_WEB_SEARCH)
- .putExtra(SearchManager.QUERY, text)));
+ .putExtra(SearchManager.QUERY, text),
+ text.hashCode()));
}
@NonNull
@@ -765,7 +793,8 @@
return new LabeledIntent(
context.getString(com.android.internal.R.string.view_calendar),
context.getString(com.android.internal.R.string.view_calendar_desc),
- new Intent(Intent.ACTION_VIEW).setData(builder.build()));
+ new Intent(Intent.ACTION_VIEW).setData(builder.build()),
+ LabeledIntent.DEFAULT_REQUEST_CODE);
}
@NonNull
@@ -781,7 +810,8 @@
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,
parsedTime.toEpochMilli())
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
- parsedTime.toEpochMilli() + DEFAULT_EVENT_DURATION));
+ parsedTime.toEpochMilli() + DEFAULT_EVENT_DURATION),
+ parsedTime.hashCode());
}
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index dac100a..f6ac1cc 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4063,7 +4063,8 @@
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
mAssistClickHandlers.put(item, TextClassification.createIntentOnClickListener(
TextClassification.createPendingIntent(mTextView.getContext(),
- textClassification.getIntent())));
+ textClassification.getIntent(),
+ createAssistMenuItemPendingIntentRequestCode())));
}
final int count = textClassification.getActions().size();
for (int i = 1; i < count; i++) {
@@ -4121,7 +4122,9 @@
final Intent intent = assistMenuItem.getIntent();
if (intent != null) {
onClickListener = TextClassification.createIntentOnClickListener(
- TextClassification.createPendingIntent(mTextView.getContext(), intent));
+ TextClassification.createPendingIntent(
+ mTextView.getContext(), intent,
+ createAssistMenuItemPendingIntentRequestCode()));
}
}
if (onClickListener != null) {
@@ -4132,6 +4135,14 @@
return true;
}
+ private int createAssistMenuItemPendingIntentRequestCode() {
+ return mTextView.hasSelection()
+ ? mTextView.getText().subSequence(
+ mTextView.getSelectionStart(), mTextView.getSelectionEnd())
+ .hashCode()
+ : 0;
+ }
+
private boolean shouldEnableAssistMenuItems() {
return mTextView.isDeviceProvisioned()
&& TextClassificationManager.getSettings(mTextView.getContext())
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 433d14f..083c0c9 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -29,6 +29,7 @@
import java.util.List;
import java.util.Set;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Stream;
/**
@@ -84,6 +85,17 @@
return emptyIfNull(result);
}
+ /** Add all elements matching {@code predicate} in {@code source} to {@code dest}. */
+ public static <T> void addIf(@Nullable List<T> source, @NonNull Collection<? super T> dest,
+ @Nullable Predicate<? super T> predicate) {
+ for (int i = 0; i < size(source); i++) {
+ final T item = source.get(i);
+ if (predicate.test(item)) {
+ dest.add(item);
+ }
+ }
+ }
+
/**
* Returns a list of items resulting from applying the given function to each element of the
* provided list.
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index e85b782..7fd83bc 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -16,18 +16,25 @@
package com.android.internal.util;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
+import android.text.TextUtils;
import android.util.Slog;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Objects;
+import java.util.function.Predicate;
/**
* Helper functions for dumping the state of system services.
+ * Test:
+ atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
*/
public final class DumpUtils {
private static final String TAG = "DumpUtils";
@@ -153,4 +160,99 @@
PrintWriter pw) {
return checkDumpPermission(context, tag, pw) && checkUsageStatsPermission(context, tag, pw);
}
+
+ /**
+ * Return whether a package name is considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isPlatformPackage(@Nullable String packageName) {
+ return (packageName != null)
+ && (packageName.equals("android")
+ || packageName.startsWith("android.")
+ || packageName.startsWith("com.android."));
+ }
+
+ /**
+ * Return whether a package name is considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isPlatformPackage(@Nullable ComponentName cname) {
+ return (cname != null) && isPlatformPackage(cname.getPackageName());
+ }
+
+ /**
+ * Return whether a package name is considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isPlatformPackage(@Nullable ComponentName.WithComponentName wcn) {
+ return (wcn != null) && isPlatformPackage(wcn.getComponentName());
+ }
+
+ /**
+ * Return whether a package name is NOT considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isNonPlatformPackage(@Nullable String packageName) {
+ return (packageName != null) && !isPlatformPackage(packageName);
+ }
+
+ /**
+ * Return whether a package name is NOT considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isNonPlatformPackage(@Nullable ComponentName cname) {
+ return (cname != null) && isNonPlatformPackage(cname.getPackageName());
+ }
+
+ /**
+ * Return whether a package name is NOT considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isNonPlatformPackage(@Nullable ComponentName.WithComponentName wcn) {
+ return (wcn != null) && !isPlatformPackage(wcn.getComponentName());
+ }
+
+ /**
+ * Used for dumping providers and services. Return a predicate for a given filter string.
+ * @hide
+ */
+ public static <TRec extends ComponentName.WithComponentName> Predicate<TRec> filterRecord(
+ @Nullable String filterString) {
+
+ if (TextUtils.isEmpty(filterString)) {
+ return rec -> false;
+ }
+
+ // Dump all?
+ if ("all".equals(filterString)) {
+ return Objects::nonNull;
+ }
+
+ // Dump all platform?
+ if ("all-platform".equals(filterString)) {
+ return DumpUtils::isPlatformPackage;
+ }
+
+ // Dump all non-platform?
+ if ("all-non-platform".equals(filterString)) {
+ return DumpUtils::isNonPlatformPackage;
+ }
+
+ // Is the filter a component name? If so, do an exact match.
+ final ComponentName filterCname = ComponentName.unflattenFromString(filterString);
+ if (filterCname != null) {
+ // Do exact component name check.
+ return rec -> (rec != null) && filterCname.equals(rec.getComponentName());
+ }
+
+ // Otherwise, do a partial match against the component name.
+ // Also if the filter is a hex-decimal string, do the object ID match too.
+ final int id = ParseUtils.parseIntWithBase(filterString, 16, -1);
+ return rec -> {
+ final ComponentName cn = rec.getComponentName();
+ return ((id != -1) && (System.identityHashCode(rec) == id))
+ || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
+ };
+ }
}
+
diff --git a/core/java/com/android/internal/util/ParseUtils.java b/core/java/com/android/internal/util/ParseUtils.java
new file mode 100644
index 0000000..a591f4a
--- /dev/null
+++ b/core/java/com/android/internal/util/ParseUtils.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.util;
+
+import android.annotation.Nullable;
+
+/**
+ * Various numeric -> strings conversion.
+ *
+ * Test:
+ atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
+ */
+public final class ParseUtils {
+ private ParseUtils() {
+ }
+
+ /** Parse a value as a base-10 integer. */
+ public static int parseInt(@Nullable String value, int defValue) {
+ return parseIntWithBase(value, 10, defValue);
+ }
+
+ /** Parse a value as an integer of a given base. */
+ public static int parseIntWithBase(@Nullable String value, int base, int defValue) {
+ if (value == null) {
+ return defValue;
+ }
+ try {
+ return Integer.parseInt(value, base);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ /** Parse a value as a base-10 long. */
+ public static long parseLong(@Nullable String value, long defValue) {
+ return parseLongWithBase(value, 10, defValue);
+ }
+
+ /** Parse a value as a long of a given base. */
+ public static long parseLongWithBase(@Nullable String value, int base, long defValue) {
+ if (value == null) {
+ return defValue;
+ }
+ try {
+ return Long.parseLong(value, base);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ /** Parse a value as a float. */
+ public static float parseFloat(@Nullable String value, float defValue) {
+ if (value == null) {
+ return defValue;
+ }
+ try {
+ return Float.parseFloat(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ /** Parse a value as a double. */
+ public static double parseDouble(@Nullable String value, double defValue) {
+ if (value == null) {
+ return defValue;
+ }
+ try {
+ return Double.parseDouble(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ /** Parse a value as a boolean. */
+ public static boolean parseBoolean(@Nullable String value, boolean defValue) {
+ if ("true".equals(value)) {
+ return true;
+ }
+ if ("false".equals(value)) {
+ return false;
+ }
+ return parseInt(value, defValue ? 1 : 0) != 0;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
new file mode 100644
index 0000000..45b19bc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.util;
+
+import static com.android.internal.util.DumpUtils.filterRecord;
+import static com.android.internal.util.DumpUtils.isNonPlatformPackage;
+import static com.android.internal.util.DumpUtils.isPlatformPackage;
+
+import android.content.ComponentName;
+
+import junit.framework.TestCase;
+
+/**
+ * Run with:
+ atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/DumpTest.java
+ */
+public class DumpUtilsTest extends TestCase {
+
+ private static ComponentName cn(String componentName) {
+ if (componentName == null) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(componentName);
+ }
+
+ private static ComponentName.WithComponentName wcn(String componentName) {
+ if (componentName == null) {
+ return null;
+ }
+ return () -> cn(componentName);
+ }
+
+ public void testIsPlatformPackage() {
+ assertTrue(isPlatformPackage("android"));
+ assertTrue(isPlatformPackage("android.abc"));
+ assertTrue(isPlatformPackage("com.android.abc"));
+
+ assertFalse(isPlatformPackage((String) null));
+ assertFalse(isPlatformPackage("com.google"));
+
+ assertTrue(isPlatformPackage(cn("android/abc")));
+ assertTrue(isPlatformPackage(cn("android.abc/abc")));
+ assertTrue(isPlatformPackage(cn("com.android.def/abc")));
+
+ assertFalse(isPlatformPackage(cn(null)));
+ assertFalse(isPlatformPackage(cn("com.google.def/abc")));
+
+ assertTrue(isPlatformPackage(wcn("android/abc")));
+ assertTrue(isPlatformPackage(wcn("android.abc/abc")));
+ assertTrue(isPlatformPackage(wcn("com.android.def/abc")));
+
+ assertFalse(isPlatformPackage(wcn(null)));
+ assertFalse(isPlatformPackage(wcn("com.google.def/abc")));
+ }
+
+ public void testIsNonPlatformPackage() {
+ assertFalse(isNonPlatformPackage("android"));
+ assertFalse(isNonPlatformPackage("android.abc"));
+ assertFalse(isNonPlatformPackage("com.android.abc"));
+
+ assertFalse(isNonPlatformPackage((String) null));
+ assertTrue(isNonPlatformPackage("com.google"));
+
+ assertFalse(isNonPlatformPackage(cn("android/abc")));
+ assertFalse(isNonPlatformPackage(cn("android.abc/abc")));
+ assertFalse(isNonPlatformPackage(cn("com.android.def/abc")));
+
+ assertFalse(isNonPlatformPackage(cn(null)));
+ assertTrue(isNonPlatformPackage(cn("com.google.def/abc")));
+
+ assertFalse(isNonPlatformPackage(wcn("android/abc")));
+ assertFalse(isNonPlatformPackage(wcn("android.abc/abc")));
+ assertFalse(isNonPlatformPackage(wcn("com.android.def/abc")));
+
+ assertFalse(isNonPlatformPackage(wcn(null)));
+ assertTrue(isNonPlatformPackage(wcn("com.google.def/abc")));
+ }
+
+ public void testFilterRecord() {
+ assertFalse(filterRecord(null).test(wcn("com.google.p/abc")));
+ assertFalse(filterRecord(null).test(wcn("com.android.p/abc")));
+
+ assertTrue(filterRecord("all").test(wcn("com.google.p/abc")));
+ assertTrue(filterRecord("all").test(wcn("com.android.p/abc")));
+ assertFalse(filterRecord("all").test(wcn(null)));
+
+ assertFalse(filterRecord("all-platform").test(wcn("com.google.p/abc")));
+ assertTrue(filterRecord("all-platform").test(wcn("com.android.p/abc")));
+ assertFalse(filterRecord("all-platform").test(wcn(null)));
+
+ assertTrue(filterRecord("all-non-platform").test(wcn("com.google.p/abc")));
+ assertFalse(filterRecord("all-non-platform").test(wcn("com.android.p/abc")));
+ assertFalse(filterRecord("all-non-platform").test(wcn(null)));
+
+ // Partial string match.
+ assertTrue(filterRecord("abc").test(wcn("com.google.p/.abc")));
+ assertFalse(filterRecord("abc").test(wcn("com.google.p/.def")));
+ assertTrue(filterRecord("com").test(wcn("com.google.p/.xyz")));
+
+ // Full component name match.
+ assertTrue(filterRecord("com.google/com.google.abc").test(wcn("com.google/.abc")));
+ assertFalse(filterRecord("com.google/com.google.abc").test(wcn("com.google/.abc.def")));
+
+
+ // Hex ID match
+ ComponentName.WithComponentName component = wcn("com.google/.abc");
+
+ assertTrue(filterRecord(
+ Integer.toHexString(System.identityHashCode(component))).test(component));
+ // Same component name, but different ID, no match.
+ assertFalse(filterRecord(
+ Integer.toHexString(System.identityHashCode(component))).test(
+ wcn("com.google/.abc")));
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
new file mode 100644
index 0000000..f00c48c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Run with:
+ atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
+ */
+public class ParseUtilsTest extends TestCase {
+ public void testParseInt() {
+ assertEquals(1, ParseUtils.parseInt(null, 1));
+ assertEquals(1, ParseUtils.parseInt("", 1));
+ assertEquals(1, ParseUtils.parseInt("1x", 1));
+ assertEquals(2, ParseUtils.parseInt("2", 1));
+
+ assertEquals(2, ParseUtils.parseInt("+2", 1));
+ assertEquals(-2, ParseUtils.parseInt("-2", 1));
+ }
+
+ public void testParseIntWithBase() {
+ assertEquals(1, ParseUtils.parseIntWithBase(null, 10, 1));
+ assertEquals(1, ParseUtils.parseIntWithBase("", 10, 1));
+ assertEquals(1, ParseUtils.parseIntWithBase("1x", 10, 1));
+ assertEquals(2, ParseUtils.parseIntWithBase("2", 10, 1));
+ assertEquals(10, ParseUtils.parseIntWithBase("10", 10, 1));
+ assertEquals(3, ParseUtils.parseIntWithBase("10", 3, 1));
+
+ assertEquals(3, ParseUtils.parseIntWithBase("+10", 3, 1));
+ assertEquals(-3, ParseUtils.parseIntWithBase("-10", 3, 1));
+ }
+
+ public void testParseLong() {
+ assertEquals(1L, ParseUtils.parseLong(null, 1));
+ assertEquals(1L, ParseUtils.parseLong("", 1));
+ assertEquals(1L, ParseUtils.parseLong("1x", 1));
+ assertEquals(2L, ParseUtils.parseLong("2", 1));
+ }
+
+ public void testParseLongWithBase() {
+ assertEquals(1L, ParseUtils.parseLongWithBase(null, 10, 1));
+ assertEquals(1L, ParseUtils.parseLongWithBase("", 10, 1));
+ assertEquals(1L, ParseUtils.parseLongWithBase("1x", 10, 1));
+ assertEquals(2L, ParseUtils.parseLongWithBase("2", 10, 1));
+ assertEquals(10L, ParseUtils.parseLongWithBase("10", 10, 1));
+ assertEquals(3L, ParseUtils.parseLongWithBase("10", 3, 1));
+
+ assertEquals(3L, ParseUtils.parseLongWithBase("+10", 3, 1));
+ assertEquals(-3L, ParseUtils.parseLongWithBase("-10", 3, 1));
+
+ assertEquals(10_000_000_000L, ParseUtils.parseLongWithBase("+10000000000", 10, 1));
+ assertEquals(-10_000_000_000L, ParseUtils.parseLongWithBase("-10000000000", 10, 1));
+
+ assertEquals(10_000_000_000L, ParseUtils.parseLongWithBase(null, 10, 10_000_000_000L));
+ }
+
+ public void testParseFloat() {
+ assertEquals(0.5f, ParseUtils.parseFloat(null, 0.5f));
+ assertEquals(0.5f, ParseUtils.parseFloat("", 0.5f));
+ assertEquals(0.5f, ParseUtils.parseFloat("1x", 0.5f));
+ assertEquals(1.5f, ParseUtils.parseFloat("1.5", 0.5f));
+ }
+
+ public void testParseDouble() {
+ assertEquals(0.5, ParseUtils.parseDouble(null, 0.5));
+ assertEquals(0.5, ParseUtils.parseDouble("", 0.5));
+ assertEquals(0.5, ParseUtils.parseDouble("1x", 0.5));
+ assertEquals(1.5, ParseUtils.parseDouble("1.5", 0.5));
+ }
+
+ public void testParseBoolean() {
+ assertEquals(false, ParseUtils.parseBoolean(null, false));
+ assertEquals(true, ParseUtils.parseBoolean(null, true));
+
+ assertEquals(false, ParseUtils.parseBoolean("", false));
+ assertEquals(true, ParseUtils.parseBoolean("", true));
+
+ assertEquals(true, ParseUtils.parseBoolean("true", false));
+ assertEquals(true, ParseUtils.parseBoolean("true", true));
+
+ assertEquals(false, ParseUtils.parseBoolean("false", false));
+ assertEquals(false, ParseUtils.parseBoolean("false", true));
+
+ assertEquals(true, ParseUtils.parseBoolean("1", false));
+ assertEquals(true, ParseUtils.parseBoolean("1", true));
+
+ assertEquals(false, ParseUtils.parseBoolean("0", false));
+ assertEquals(false, ParseUtils.parseBoolean("0", true));
+ }
+}
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 3118009..f1cc569 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -386,6 +386,38 @@
EXPECT_EQ(basic::R::array::integerArray1, last_ref);
}
+TEST_F(AssetManager2Test, ResolveDeepIdReference) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ // Set up the resource ids
+ const uint32_t high_ref = assetmanager
+ .GetResourceId("@id/high_ref", "values", "com.android.basic");
+ ASSERT_NE(high_ref, 0u);
+ const uint32_t middle_ref = assetmanager
+ .GetResourceId("@id/middle_ref", "values", "com.android.basic");
+ ASSERT_NE(middle_ref, 0u);
+ const uint32_t low_ref = assetmanager
+ .GetResourceId("@id/low_ref", "values", "com.android.basic");
+ ASSERT_NE(low_ref, 0u);
+
+ // Retrieve the most shallow resource
+ Res_value value;
+ ResTable_config config;
+ uint32_t flags;
+ ApkAssetsCookie cookie = assetmanager.GetResource(high_ref, false /*may_be_bag*/,
+ 0 /*density_override*/,
+ &value, &config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
+ EXPECT_EQ(middle_ref, value.data);
+
+ // Check that resolving the reference resolves to the deepest id
+ uint32_t last_ref = high_ref;
+ assetmanager.ResolveReference(cookie, &value, &config, &flags, &last_ref);
+ EXPECT_EQ(last_ref, low_ref);
+}
+
TEST_F(AssetManager2Test, KeepLastReferenceIdUnmodifiedIfNoReferenceIsResolved) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({basic_assets_.get()});
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 1733b6a..b721ebf 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index b343562..d4b2683 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -78,4 +78,8 @@
<item type="string" name="test2" />
<item type="array" name="integerArray1" />
</overlayable>
+
+ <item name="high_ref" type="id">@id/middle_ref</item>
+ <item name="middle_ref" type="id">@id/low_ref</item>
+ <item name="low_ref" type="id"/>
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index a5f0f24..1ca3c1d 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -65,9 +65,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.pooled.PooledLambda;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -219,7 +217,7 @@
stopScan();
mDevicesFound.clear();
mSelectedDevice = null;
- mDevicesAdapter.notifyDataSetChanged();
+ notifyDataSetChanged();
}
@Override
@@ -265,7 +263,12 @@
onReadyToShowUI();
}
mDevicesFound.add(device);
- mDevicesAdapter.notifyDataSetChanged();
+ notifyDataSetChanged();
+ }
+
+ private void notifyDataSetChanged() {
+ Handler.getMain().sendMessage(obtainMessage(
+ DevicesAdapter::notifyDataSetChanged, mDevicesAdapter));
}
//TODO also, on timeout -> call onFailure
@@ -283,7 +286,7 @@
private void onDeviceLost(@Nullable DeviceFilterPair device) {
mDevicesFound.remove(device);
- mDevicesAdapter.notifyDataSetChanged();
+ notifyDataSetChanged();
if (DEBUG) Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
index 9554e81..95a8f1d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
@@ -28,6 +28,7 @@
public class CustomDialogPreference extends DialogPreference {
private CustomPreferenceDialogFragment mFragment;
+ private DialogInterface.OnShowListener mOnShowListener;
public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
@@ -54,6 +55,10 @@
return mFragment != null ? mFragment.getDialog() : null;
}
+ public void setOnShowListener(DialogInterface.OnShowListener listner) {
+ mOnShowListener = listner;
+ }
+
protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
DialogInterface.OnClickListener listener) {
}
@@ -71,6 +76,10 @@
mFragment = fragment;
}
+ private DialogInterface.OnShowListener getOnShowListener() {
+ return mOnShowListener;
+ }
+
public static class CustomPreferenceDialogFragment extends PreferenceDialogFragment {
public static CustomPreferenceDialogFragment newInstance(String key) {
@@ -104,6 +113,13 @@
}
@Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.setOnShowListener(getCustomizablePreference().getOnShowListener());
+ return dialog;
+ }
+
+ @Override
public void onClick(DialogInterface dialog, int which) {
super.onClick(dialog, which);
getCustomizablePreference().onClick(dialog, which);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 547cd9a..fe0b35b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -103,10 +103,9 @@
public void handleBroadcast(Intent intent) {
String action = intent.getAction();
if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_UNKNOWN);
- enabled = state == WifiManager.WIFI_STATE_ENABLED;
+ updateWifiState();
} else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ updateWifiState();
final NetworkInfo networkInfo =
intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
connected = networkInfo != null && networkInfo.isConnected();
@@ -128,6 +127,11 @@
}
}
+ private void updateWifiState() {
+ state = mWifiManager.getWifiState();
+ enabled = state == WifiManager.WIFI_STATE_ENABLED;
+ }
+
private void updateRssi(int newRssi) {
rssi = newRssi;
level = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS);
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b220091..a1e5b0e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -138,7 +138,7 @@
<!-- Vertical translation of the shelf during animation that happens after the
notification panel collapses -->
- <dimen name="shelf_appear_translation">9dp</dimen>
+ <dimen name="shelf_appear_translation">42dp</dimen>
<!-- The amount the content shifts upwards when transforming into the icon -->
<dimen name="notification_icon_transform_content_shift">32dp</dimen>
@@ -497,9 +497,6 @@
device. -->
<dimen name="unlock_move_distance">75dp</dimen>
- <!-- Distance after which the scrim starts fading in when dragging down the quick settings -->
- <dimen name="notification_scrim_wait_distance">100dp</dimen>
-
<!-- Move distance for the unlock hint animation on the lockscreen -->
<dimen name="hint_move_distance">75dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index d45c427..458e133d7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -24,6 +24,8 @@
<item type="id" name="scale_y_animator_tag"/>
<item type="id" name="top_inset_animator_tag"/>
<item type="id" name="height_animator_tag"/>
+ <item type="id" name="x_animator_tag"/>
+ <item type="id" name="y_animator_tag"/>
<item type="id" name="shadow_alpha_animator_tag"/>
<item type="id" name="translation_x_animator_end_value_tag"/>
<item type="id" name="translation_y_animator_end_value_tag"/>
@@ -34,6 +36,8 @@
<item type="id" name="top_inset_animator_end_value_tag"/>
<item type="id" name="height_animator_end_value_tag"/>
<item type="id" name="shadow_alpha_animator_end_value_tag"/>
+ <item type="id" name="x_animator_tag_end_value"/>
+ <item type="id" name="y_animator_tag_end_value"/>
<item type="id" name="translation_x_animator_start_value_tag"/>
<item type="id" name="translation_y_animator_start_value_tag"/>
<item type="id" name="translation_z_animator_start_value_tag"/>
@@ -43,6 +47,8 @@
<item type="id" name="top_inset_animator_start_value_tag"/>
<item type="id" name="height_animator_start_value_tag"/>
<item type="id" name="shadow_alpha_animator_start_value_tag"/>
+ <item type="id" name="x_animator_tag_start_value"/>
+ <item type="id" name="y_animator_tag_start_value"/>
<item type="id" name="doze_saved_filter_tag"/>
<item type="id" name="qs_icon_tag"/>
<item type="id" name="qs_slash_tag"/>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 6c7eda7..5ae43c6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -65,10 +65,9 @@
void setKeyguardShowing(boolean keyguardShowing);
/**
- * Returns the {@link View} that should expand the quick settings when clicked.
+ * Sets the {@link android.view.View.OnClickListener to be used on elements that expend QS.
*/
- @Nullable
- View getExpandView();
+ void setExpandClickListener(View.OnClickListener onClickListener);
default void disable(int state1, int state2, boolean animate) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index abe819b..fd9ddb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import android.content.Context;
import android.content.Intent;
@@ -27,6 +26,7 @@
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
+import android.os.Bundle;
import android.os.UserManager;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
@@ -34,6 +34,7 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -96,6 +97,7 @@
private ImageView mMobileRoaming;
private final int mColorForeground;
private final CellSignalState mInfo = new CellSignalState();
+ private OnClickListener mExpandClickListener;
public QSFooterImpl(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -140,6 +142,7 @@
mActivityStarter = Dependency.get(ActivityStarter.class);
addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
oldBottom) -> updateAnimator(right - left));
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
private void updateAnimator(int width) {
@@ -205,6 +208,11 @@
}
@Override
+ public void setExpandClickListener(OnClickListener onClickListener) {
+ mExpandClickListener = onClickListener;
+ }
+
+ @Override
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
@@ -238,8 +246,20 @@
}
@Override
- public View getExpandView() {
- return findViewById(R.id.expand_indicator);
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
+ if (mExpandClickListener != null) {
+ mExpandClickListener.onClick(null);
+ return true;
+ }
+ }
+ return super.performAccessibilityAction(action, arguments);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b82e355..cbd1ca1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -240,11 +240,6 @@
@Override
public void setHeaderClickable(boolean clickable) {
if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
-
- View expandView = mFooter.getExpandView();
- if (expandView != null) {
- expandView.setClickable(clickable);
- }
}
@Override
@@ -369,11 +364,7 @@
@Override
public void setExpandClickListener(OnClickListener onClickListener) {
- View expandView = mFooter.getExpandView();
-
- if (expandView != null) {
- expandView.setOnClickListener(onClickListener);
- }
+ mFooter.setExpandClickListener(onClickListener);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
index 24b5a34..2ea21c6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
@@ -114,11 +114,9 @@
}
}
- @Nullable
@Override
- public View getExpandView() {
+ public void setExpandClickListener(OnClickListener onClickListener) {
// No view that should expand/collapse the quick settings.
- return null;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 4388b41..011be88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -62,6 +62,10 @@
mEmptyText.setText(mText);
}
+ public int getTextResource() {
+ return mText;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 6364f5b..44136c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -58,7 +58,7 @@
= SystemProperties.getBoolean("debug.icon_scroll_animations", true);
private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
private static final String TAG = "NotificationShelf";
- private static final long SHELF_IN_TRANSLATION_DURATION = 220;
+ private static final long SHELF_IN_TRANSLATION_DURATION = 200;
private ViewInvertHelper mViewInvertHelper;
private boolean mDark;
@@ -157,14 +157,18 @@
public void fadeInTranslating() {
float translation = mShelfIcons.getTranslationY();
- mShelfIcons.setTranslationY(translation + mShelfAppearTranslation);
+ mShelfIcons.setTranslationY(translation - mShelfAppearTranslation);
mShelfIcons.setAlpha(0);
mShelfIcons.animate()
- .alpha(1)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .setInterpolator(Interpolators.DECELERATE_QUINT)
.translationY(translation)
.setDuration(SHELF_IN_TRANSLATION_DURATION)
.start();
+ mShelfIcons.animate()
+ .alpha(1)
+ .setInterpolator(Interpolators.LINEAR)
+ .setDuration(SHELF_IN_TRANSLATION_DURATION)
+ .start();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
index 14a6c42..b2eb18e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
+import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.util.AttributeSet;
@@ -35,6 +36,7 @@
private boolean mIsVisible;
private boolean mIsSecondaryVisible;
private boolean mAnimating;
+ private boolean mSecondaryAnimating;
private int mDuration = 260;
public StackScrollerDecorView(Context context, AttributeSet attrs) {
@@ -61,13 +63,26 @@
}
public void performVisibilityAnimation(boolean nowVisible) {
- animateText(mContent, nowVisible, null /* onFinishedRunnable */);
- mIsVisible = nowVisible;
+ performVisibilityAnimation(nowVisible, null /* onFinishedRunnable */);
}
public void performVisibilityAnimation(boolean nowVisible, Runnable onFinishedRunnable) {
- animateText(mContent, nowVisible, onFinishedRunnable);
- mIsVisible = nowVisible;
+ boolean oldVisible = isVisible();
+ animateText(mContent, nowVisible, oldVisible, new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mAnimating = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimating = false;
+ mIsVisible = nowVisible;
+ if (onFinishedRunnable != null) {
+ onFinishedRunnable.run();
+ }
+ }
+ });
}
public void performSecondaryVisibilityAnimation(boolean nowVisible) {
@@ -76,16 +91,43 @@
public void performSecondaryVisibilityAnimation(boolean nowVisible,
Runnable onFinishedRunnable) {
- animateText(mSecondaryView, nowVisible, onFinishedRunnable);
- mIsSecondaryVisible = nowVisible;
+ boolean oldVisible = isSecondaryVisible();
+ animateText(mSecondaryView, nowVisible, oldVisible, new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mSecondaryAnimating = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mSecondaryAnimating = false;
+ mIsSecondaryVisible = nowVisible;
+ if (onFinishedRunnable != null) {
+ onFinishedRunnable.run();
+ }
+ }
+ });
}
+ /**
+ * Check whether the secondary view is visible or not.<p/>
+ *
+ * @see #isVisible()
+ */
public boolean isSecondaryVisible() {
- return mSecondaryView != null && (mIsSecondaryVisible || mAnimating);
+ return mSecondaryView != null && (mIsSecondaryVisible ^ mSecondaryAnimating);
}
+ /**
+ * Check whether the whole view is visible or not.<p/>
+ * The view is considered visible if it matches one of following:
+ * <ul>
+ * <li> It's visible and there is no ongoing animation. </li>
+ * <li> It's not visible but is animating, thus being eventually visible. </li>
+ * </ul>
+ */
public boolean isVisible() {
- return mIsVisible || mAnimating;
+ return mIsVisible ^ mAnimating;
}
void setDuration(int duration) {
@@ -95,15 +137,18 @@
/**
* Animate the text to a new visibility.
*
- * @param nowVisible should it now be visible
- * @param onFinishedRunnable A runnable which should be run when the animation is
- * finished.
+ * @param view Target view, maybe content view or dissmiss view
+ * @param nowVisible Should it now be visible
+ * @param oldVisible Is it visible currently
+ * @param listener A listener that doing flag settings or other actions
*/
- private void animateText(View view, boolean nowVisible, final Runnable onFinishedRunnable) {
+ private void animateText(View view, boolean nowVisible, boolean oldVisible,
+ AnimatorListenerAdapter listener) {
if (view == null) {
return;
}
- if (nowVisible != mIsVisible) {
+
+ if (nowVisible != oldVisible) {
// Animate text
float endValue = nowVisible ? 1.0f : 0.0f;
Interpolator interpolator;
@@ -112,24 +157,11 @@
} else {
interpolator = Interpolators.ALPHA_OUT;
}
- mAnimating = true;
view.animate()
.alpha(endValue)
.setInterpolator(interpolator)
.setDuration(mDuration)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mAnimating = false;
- if (onFinishedRunnable != null) {
- onFinishedRunnable.run();
- }
- }
- });
- } else {
- if (onFinishedRunnable != null) {
- onFinishedRunnable.run();
- }
+ .setListener(listener);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index d7b211f..75b41ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -20,25 +20,30 @@
import android.util.Property;
import android.view.View;
-import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.R;
import java.util.function.BiConsumer;
-import java.util.function.Consumer;
import java.util.function.Function;
/**
* An animatable property of a view. Used with {@link PropertyAnimator}
*/
-public interface AnimatableProperty {
- int getAnimationStartTag();
+public abstract class AnimatableProperty {
- int getAnimationEndTag();
+ public static final AnimatableProperty X = AnimatableProperty.from(View.X,
+ R.id.x_animator_tag, R.id.x_animator_tag_start_value, R.id.x_animator_tag_end_value);
+ public static final AnimatableProperty Y = AnimatableProperty.from(View.Y,
+ R.id.y_animator_tag, R.id.y_animator_tag_start_value, R.id.y_animator_tag_end_value);
- int getAnimatorTag();
+ public abstract int getAnimationStartTag();
- Property getProperty();
+ public abstract int getAnimationEndTag();
- static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+ public abstract int getAnimatorTag();
+
+ public abstract Property getProperty();
+
+ public static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
Property<T, Float> property = new FloatProperty<T>(name) {
@@ -74,4 +79,29 @@
}
};
}
+
+ public static <T extends View> AnimatableProperty from(Property<T, Float> property,
+ int animatorTag, int startValueTag, int endValueTag) {
+ return new AnimatableProperty() {
+ @Override
+ public int getAnimationStartTag() {
+ return startValueTag;
+ }
+
+ @Override
+ public int getAnimationEndTag() {
+ return endValueTag;
+ }
+
+ @Override
+ public int getAnimatorTag() {
+ return animatorTag;
+ }
+
+ @Override
+ public Property getProperty() {
+ return property;
+ }
+ };
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 92dcc9e..2efcd16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -24,6 +24,7 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.stack.AnimationFilter;
import com.android.systemui.statusbar.stack.AnimationProperties;
@@ -115,4 +116,7 @@
view.setTag(animationEndTag, newEndValue);
}
+ public static <T extends View> boolean isAnimating(T view, AnimatableProperty property) {
+ return view.getTag(property.getAnimatorTag()) != null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index d9a55c5..042e4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -20,8 +20,8 @@
import android.content.res.Resources;
import android.util.MathUtils;
-import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -47,12 +47,6 @@
private int mClockNotificationsMargin;
/**
- * Current height of {@link NotificationPanelView}, considering how much the
- * user collapsed it.
- */
- private float mExpandedHeight;
-
- /**
* Height of the parent view - display size in px.
*/
private int mHeight;
@@ -84,9 +78,9 @@
private int mContainerTopPadding;
/**
- * @see NotificationPanelView#getMaxPanelHeight()
+ * @see NotificationPanelView#getExpandedFraction()
*/
- private float mMaxPanelHeight;
+ private float mPanelExpansion;
/**
* Burn-in prevention x translation.
@@ -140,13 +134,13 @@
}
public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight,
- float expandedHeight, float maxPanelHeight, int parentHeight, int keyguardStatusHeight,
- float dark, boolean secure, boolean pulsing, int bouncerTop) {
+ float panelExpansion, int parentHeight,
+ int keyguardStatusHeight, float dark, boolean secure, boolean pulsing,
+ int bouncerTop) {
mMinTopMargin = minTopMargin + mContainerTopPadding;
mMaxShadeBottom = maxShadeBottom;
mNotificationStackHeight = notificationStackHeight;
- mExpandedHeight = expandedHeight;
- mMaxPanelHeight = maxPanelHeight;
+ mPanelExpansion = panelExpansion;
mHeight = parentHeight;
mKeyguardStatusHeight = keyguardStatusHeight;
mDarkAmount = dark;
@@ -171,16 +165,12 @@
return mHeight / 2 - mKeyguardStatusHeight - mClockNotificationsMargin;
}
- public int getExpandedClockBottom() {
- return getExpandedClockPosition() + mKeyguardStatusHeight;
- }
-
/**
* Vertically align the clock and the shade in the available space considering only
* a percentage of the clock height defined by {@code CLOCK_HEIGHT_WEIGHT}.
* @return Clock Y in pixels.
*/
- private int getExpandedClockPosition() {
+ public int getExpandedClockPosition() {
final int availableHeight = mMaxShadeBottom - mMinTopMargin;
final int containerCenter = mMinTopMargin + availableHeight / 2;
@@ -212,8 +202,7 @@
mMinTopMargin : -mKeyguardStatusHeight;
// Move clock up while collapsing the shade
- float shadeExpansion = mExpandedHeight / mMaxPanelHeight;
- shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(shadeExpansion);
+ float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(mPanelExpansion);
final float clockY = MathUtils.lerp(clockYTarget, clockYRegular, shadeExpansion);
return (int) MathUtils.lerp(clockY, clockYDark, mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 04bfcdd..66176b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -544,7 +544,12 @@
// Set visibility, may fail if a11y service is active.
// If invisible, call will stop animation.
- mNavigationBarView.setRotateButtonVisibility(true);
+ int appliedVisibility = mNavigationBarView.setRotateButtonVisibility(true);
+ if (appliedVisibility == View.VISIBLE) {
+ // If the button will actually become visible and the navbar is about to hide,
+ // tell the statusbar to keep it around for longer
+ mStatusBar.touchAutoHide();
+ }
} else { // Hide
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index c2053b6..6dbe9f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -766,13 +766,13 @@
if (setIcon) getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
}
- public void setRotateButtonVisibility(final boolean visible) {
+ public int setRotateButtonVisibility(final boolean visible) {
// Never show if a11y is visible
final boolean adjVisible = visible && !mShowAccessibilityButton;
final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE;
// No need to do anything if the request matches the current state
- if (vis == getRotateSuggestionButton().getVisibility()) return;
+ if (vis == getRotateSuggestionButton().getVisibility()) return vis;
getRotateSuggestionButton().setVisibility(vis);
mShowRotateButton = visible;
@@ -789,6 +789,9 @@
// Hide/restore other button visibility, if necessary
updateNavButtonIcons();
+
+ // Return applied visibility
+ return vis;
}
public boolean isRotateButtonVisible() { return mShowRotateButton; }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 351633b..a0a97c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -20,7 +20,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
@@ -44,7 +43,6 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
@@ -69,8 +67,11 @@
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -101,6 +102,8 @@
public static final long DOZE_ANIMATION_DURATION = 700;
+ private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties()
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
private static final FloatProperty<NotificationPanelView> SET_DARK_AMOUNT_PROPERTY =
new FloatProperty<NotificationPanelView>("mDarkAmount") {
@Override
@@ -122,11 +125,10 @@
private QS mQs;
private FrameLayout mQsFrame;
private KeyguardStatusView mKeyguardStatusView;
- private View mReserveNotificationSpace;
private View mQsNavbarScrim;
protected NotificationsQuickSettingsContainer mNotificationContainerParent;
protected NotificationStackScrollLayout mNotificationStackScroller;
- private boolean mAnimateNextTopPaddingChange;
+ private boolean mAnimateNextPositionUpdate;
private int mTrackingPointer;
private VelocityTracker mQsVelocityTracker;
@@ -173,9 +175,6 @@
private int mUnlockMoveDistance;
private float mEmptyDragAmount;
- private Animator mClockAnimator;
- private int mClockAnimationTargetX = Integer.MIN_VALUE;
- private int mClockAnimationTargetY = Integer.MIN_VALUE;
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
new KeyguardClockPositionAlgorithm();
private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
@@ -183,7 +182,6 @@
private boolean mIsExpanding;
private boolean mBlockTouches;
- private int mNotificationScrimWaitDistance;
// Used for two finger gesture as well as accessibility shortcut to QS.
private boolean mQsExpandImmediate;
private boolean mTwoFingerQsExpandPossible;
@@ -310,8 +308,6 @@
getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
mClockPositionAlgorithm.loadDimens(getResources());
- mNotificationScrimWaitDistance =
- getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
mQsFalsingThreshold = getResources().getDimensionPixelSize(
R.dimen.qs_falsing_threshold);
mPositionMinSideMargin = getResources().getDimensionPixelSize(
@@ -461,19 +457,19 @@
*/
private void positionClockAndNotifications() {
boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+ boolean animateClock = animate || mAnimateNextPositionUpdate;
int stackScrollerPadding;
if (mStatusBarState != StatusBarState.KEYGUARD) {
stackScrollerPadding = (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
+ mQsNotificationTopPadding;
} else {
- final int totalHeight = getHeight();
- final int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
+ int totalHeight = getHeight();
+ int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
mClockPositionAlgorithm.setup(
mStatusBarMinHeight,
totalHeight - bottomPadding,
- calculatePanelHeightShade() - mNotificationStackScroller.getTopPadding(),
- getExpandedHeight(),
- getMaxPanelHeight(),
+ mNotificationStackScroller.getIntrinsicContentHeight(),
+ getExpandedFraction(),
totalHeight,
mKeyguardStatusView.getHeight(),
mDarkAmount,
@@ -481,12 +477,10 @@
mPulsing,
mBouncerTop);
mClockPositionAlgorithm.run(mClockPositionResult);
- if (animate || mClockAnimator != null) {
- startClockAnimation(mClockPositionResult.clockX, mClockPositionResult.clockY);
- } else {
- mKeyguardStatusView.setX(mClockPositionResult.clockX);
- mKeyguardStatusView.setY(mClockPositionResult.clockY);
- }
+ PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
+ mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
+ PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
+ mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
updateClock();
stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
}
@@ -497,6 +491,7 @@
mStackScrollerMeasuringPass++;
requestScrollerTopPaddingUpdate(animate);
mStackScrollerMeasuringPass = 0;
+ mAnimateNextPositionUpdate = false;
}
/**
@@ -558,42 +553,6 @@
positionClockAndNotifications();
}
- private void startClockAnimation(int x, int y) {
- if (mClockAnimationTargetX == x && mClockAnimationTargetY == y) {
- return;
- }
- mClockAnimationTargetX = x;
- mClockAnimationTargetY = y;
- getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
- if (mClockAnimator != null) {
- mClockAnimator.removeAllListeners();
- mClockAnimator.cancel();
- }
- AnimatorSet set = new AnimatorSet();
- set.play(ObjectAnimator.ofFloat(
- mKeyguardStatusView, View.Y, mClockAnimationTargetY))
- .with(ObjectAnimator.ofFloat(
- mKeyguardStatusView, View.X, mClockAnimationTargetX));
- mClockAnimator = set;
- mClockAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mClockAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mClockAnimator = null;
- mClockAnimationTargetX = Integer.MIN_VALUE;
- mClockAnimationTargetY = Integer.MIN_VALUE;
- }
- });
- mClockAnimator.start();
- return true;
- }
- });
- }
-
private void updateClock() {
if (!mKeyguardStatusViewAnimating) {
mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
@@ -601,9 +560,9 @@
}
public void animateToFullShade(long delay) {
- mAnimateNextTopPaddingChange = true;
mNotificationStackScroller.goToFullShade(delay);
requestLayout();
+ mAnimateNextPositionUpdate = true;
}
public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
@@ -1411,10 +1370,8 @@
protected void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
- mAnimateNextTopPaddingChange || animate,
- mKeyguardShowing
+ animate, mKeyguardShowing
&& (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
- mAnimateNextTopPaddingChange = false;
}
private void trackMovement(MotionEvent event) {
@@ -1535,7 +1492,7 @@
if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
maxHeight = calculatePanelHeightQsExpanded();
} else {
- maxHeight = Math.max(calculatePanelHeightShade(), calculatePanelHeightShadeExpanded());
+ maxHeight = calculatePanelHeightShade();
}
maxHeight = Math.max(maxHeight, min);
return maxHeight;
@@ -1606,14 +1563,15 @@
int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
- return maxHeight;
- }
- private int calculatePanelHeightShadeExpanded() {
- return mNotificationStackScroller.getHeight()
- - mNotificationStackScroller.getEmptyBottomMargin()
- - mNotificationStackScroller.getTopPadding()
- + mClockPositionAlgorithm.getExpandedClockBottom();
+ if (mStatusBarState == StatusBarState.KEYGUARD) {
+ int minKeyguardPanelBottom = mClockPositionAlgorithm.getExpandedClockPosition()
+ + mKeyguardStatusView.getHeight()
+ + mNotificationStackScroller.getIntrinsicContentHeight();
+ return Math.max(maxHeight, minKeyguardPanelBottom);
+ } else {
+ return maxHeight;
+ }
}
private int calculatePanelHeightQsExpanded() {
@@ -1652,7 +1610,8 @@
private void updateNotificationTranslucency() {
float alpha = 1f;
- if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
+ if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp &&
+ !mHeadsUpManager.hasPinnedHeadsUp()) {
alpha = getFadeoutAlpha();
}
mNotificationStackScroller.setAlpha(alpha);
@@ -1907,13 +1866,16 @@
if (view == null && mQsExpanded) {
return;
}
+ if (needsAnimation) {
+ mAnimateNextPositionUpdate = true;
+ }
ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow
? (ExpandableNotificationRow) firstChildNotGone
: null;
if (firstRow != null
&& (view == firstRow || (firstRow.getNotificationParent() == firstRow))) {
- requestScrollerTopPaddingUpdate(false);
+ requestScrollerTopPaddingUpdate(false /* animate */);
}
requestPanelHeightUpdate();
}
@@ -1965,14 +1927,12 @@
@Override
public void onClick(View v) {
- if (v.getId() == R.id.expand_indicator) {
- onQsExpansionStarted();
- if (mQsExpanded) {
- flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
- } else if (mQsExpansionEnabled) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
- flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
- }
+ onQsExpansionStarted();
+ if (mQsExpanded) {
+ flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
+ } else if (mQsExpansionEnabled) {
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+ flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
}
}
@@ -2339,15 +2299,15 @@
p.setColor(Color.YELLOW);
canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
calculatePanelHeightShade(), p);
- p.setColor(Color.GRAY);
- canvas.drawLine(0, calculatePanelHeightShadeExpanded(), getWidth(),
- calculatePanelHeightShadeExpanded(), p);
p.setColor(Color.MAGENTA);
canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
calculateQsTopPadding(), p);
p.setColor(Color.CYAN);
- canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(),
+ canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, getWidth(),
mNotificationStackScroller.getTopPadding(), p);
+ p.setColor(Color.GRAY);
+ canvas.drawLine(0, mClockPositionResult.clockY, getWidth(),
+ mClockPositionResult.clockY, p);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 304a499..347a4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -828,7 +828,7 @@
}
@Override
- protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mStatusBar.onPanelLaidOut();
requestPanelHeightUpdate();
@@ -1088,13 +1088,10 @@
}
cancelPeek();
notifyExpandingStarted();
- startUnlockHintAnimationPhase1(new Runnable() {
- @Override
- public void run() {
- notifyExpandingFinished();
- onUnlockHintFinished();
- mHintAnimationRunning = false;
- }
+ startUnlockHintAnimationPhase1(() -> {
+ notifyExpandingFinished();
+ onUnlockHintFinished();
+ mHintAnimationRunning = false;
});
onUnlockHintStarted();
mHintAnimationRunning = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index b34038a..1d64088 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -784,6 +784,12 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mZenController.addCallback(new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ updateEmptyShadeView();
+ }
+ });
mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow,
this,
mNotificationPanel,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index bc5a848..b1c0a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -111,6 +111,7 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.function.BiConsumer;
/**
@@ -163,6 +164,7 @@
private Paint mDebugPaint;
private int mContentHeight;
+ private int mIntrinsicContentHeight;
private int mCollapsedSize;
private int mPaddingBetweenElements;
private int mIncreasedPaddingBetweenElements;
@@ -537,15 +539,16 @@
canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint);
}
} else {
- float animProgress = Interpolators.FAST_OUT_SLOW_IN
- .getInterpolation(1f - mDarkAmount);
- float sidePaddingsProgress = Interpolators.FAST_OUT_SLOW_IN
- .getInterpolation((1f - mDarkAmount) * 2);
+ float inverseDark = 1 - mDarkAmount;
+ float yProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(inverseDark);
+ float xProgress = Interpolators.FAST_OUT_SLOW_IN
+ .getInterpolation(inverseDark * 2f);
+
mBackgroundAnimationRect.set(
- (int) MathUtils.lerp(darkLeft, lockScreenLeft, sidePaddingsProgress),
- (int) MathUtils.lerp(darkTop, lockScreenTop, animProgress),
- (int) MathUtils.lerp(darkRight, lockScreenRight, sidePaddingsProgress),
- (int) MathUtils.lerp(darkBottom, lockScreenBottom, animProgress));
+ (int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress),
+ (int) MathUtils.lerp(darkTop, lockScreenTop, yProgress),
+ (int) MathUtils.lerp(darkRight, lockScreenRight, xProgress),
+ (int) MathUtils.lerp(darkBottom, lockScreenBottom, yProgress));
if (!mAmbientState.isDark() || mFirstVisibleBackgroundChild != null) {
canvas.drawRoundRect(mBackgroundAnimationRect.left, mBackgroundAnimationRect.top,
mBackgroundAnimationRect.right, mBackgroundAnimationRect.bottom,
@@ -627,8 +630,12 @@
}
private void notifyHeightChangeListener(ExpandableView view) {
+ notifyHeightChangeListener(view, false /* needsAnimation */);
+ }
+
+ private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) {
if (mOnHeightChangedListener != null) {
- mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
+ mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
}
}
@@ -849,7 +856,7 @@
mNeedsAnimation = true;
}
requestChildrenUpdate();
- notifyHeightChangeListener(null);
+ notifyHeightChangeListener(null, animate);
}
}
@@ -914,6 +921,13 @@
updateClipping();
}
+ /**
+ * Return the height of the content ignoring the footer.
+ */
+ public int getIntrinsicContentHeight() {
+ return mIntrinsicContentHeight;
+ }
+
public void updateClipping() {
boolean animatingClipping = mDarkAmount > 0 && mDarkAmount < 1;
boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
@@ -2129,8 +2143,9 @@
for (int i = 0; i < getChildCount(); i++) {
ExpandableView expandableView = (ExpandableView) getChildAt(i);
+ boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard();
if (expandableView.getVisibility() != View.GONE
- && !expandableView.hasNoContentHeight()) {
+ && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
boolean limitReached = maxDisplayedNotifications != -1
&& numShownItems >= maxDisplayedNotifications;
boolean notificationOnAmbientThatIsNotPulsing = mAmbientState.isFullyDark()
@@ -2178,6 +2193,7 @@
}
}
}
+ mIntrinsicContentHeight = height;
mContentHeight = height + mTopPadding + mBottomMargin;
updateScrollability();
clampScrollPosition();
@@ -3655,7 +3671,7 @@
updateContentHeight();
updateScrollPositionOnExpandInBottom(view);
clampScrollPosition();
- notifyHeightChangeListener(view);
+ notifyHeightChangeListener(view, needsAnimation);
ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
? (ExpandableNotificationRow) view
: null;
@@ -3969,6 +3985,7 @@
mShelf.fadeInTranslating();
}
}
+ updateAlgorithmHeightAndPadding();
updateBackgroundDimming();
updateAntiBurnInTranslation();
requestChildrenUpdate();
@@ -4039,14 +4056,21 @@
public void updateEmptyShadeView(boolean visible) {
int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
int newVisibility = visible ? VISIBLE : GONE;
- if (oldVisibility != newVisibility) {
+
+ boolean changedVisibility = oldVisibility != newVisibility;
+ if (changedVisibility || newVisibility != GONE) {
if (newVisibility != GONE) {
+ int oldText = mEmptyShadeView.getTextResource();
+ int newText;
if (mStatusBar.areNotificationsHidden()) {
- mEmptyShadeView.setText(R.string.dnd_suppressing_shade_text);
+ newText = R.string.dnd_suppressing_shade_text;
} else {
- mEmptyShadeView.setText(R.string.empty_shade_text);
+ newText = R.string.empty_shade_text;
}
- showFooterView(mEmptyShadeView);
+ if (changedVisibility || !Objects.equals(oldText, newText)) {
+ mEmptyShadeView.setText(newText);
+ showFooterView(mEmptyShadeView);
+ }
} else {
hideFooterView(mEmptyShadeView, true);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index a153140..f0ca3ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.ViewState;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -227,4 +228,13 @@
assertNotNull(animator);
assertTrue(animator.getListeners().contains(mFinishListener));
}
+
+ @Test
+ public void testIsAnimating() {
+ mAnimationFilter.reset();
+ mAnimationFilter.animate(mProperty.getProperty());
+ assertFalse(PropertyAnimator.isAnimating(mView, mProperty));
+ PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
+ assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index d30e777..6591715 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -136,10 +136,9 @@
}
protected void setWifiEnabled(boolean enabled) {
- Intent i = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
- i.putExtra(WifiManager.EXTRA_WIFI_STATE,
+ when(mMockWm.getWifiState()).thenReturn(
enabled ? WifiManager.WIFI_STATE_ENABLED : WifiManager.WIFI_STATE_DISABLED);
- mNetworkController.onReceive(mContext, i);
+ mNetworkController.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
}
protected void setWifiState(boolean connected, String ssid) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
index dd2b581..eeb4209 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
@@ -141,6 +141,19 @@
}
@Test
+ public void updateEmptyView_noNotificationsToDndSuppressing() {
+ mStackScroller.setEmptyShadeView(mEmptyShadeView);
+ when(mEmptyShadeView.willBeGone()).thenReturn(true);
+ when(mBar.areNotificationsHidden()).thenReturn(false);
+ mStackScroller.updateEmptyShadeView(true);
+ verify(mEmptyShadeView).setText(R.string.empty_shade_text);
+
+ when(mBar.areNotificationsHidden()).thenReturn(true);
+ mStackScroller.updateEmptyShadeView(true);
+ verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
+ }
+
+ @Test
@UiThreadTest
public void testSetExpandedHeight_blockingHelperManagerReceivedCallbacks() {
mStackScroller.setExpandedHeight(0f);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1d62eb7..e6b2a35 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2151,11 +2151,8 @@
if (saveTriggerId != null) {
writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
}
- int flags = saveInfo.getFlags();
- if (mCompatMode) {
- flags |= SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE;
- }
- mSaveOnAllViewsInvisible = (flags & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
+ mSaveOnAllViewsInvisible =
+ (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
// We only need to track views if we want to save once they become invisible.
if (mSaveOnAllViewsInvisible) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index fc047bc..228171f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -24,15 +24,19 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.ServiceStartArgs;
+import android.content.ComponentName.WithComponentName;
import android.content.IIntentSender;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
@@ -55,6 +59,8 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.TransferPipe;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.server.AppStateTracker;
import com.android.server.LocalServices;
@@ -4063,57 +4069,26 @@
* - the first arg isn't the flattened component name of an existing service:
* dump all services whose component contains the first arg as a substring
*/
- protected boolean dumpService(FileDescriptor fd, PrintWriter pw, String name, String[] args,
- int opti, boolean dumpAll) {
- ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
+ protected boolean dumpService(FileDescriptor fd, PrintWriter pw, final String name,
+ String[] args, int opti, boolean dumpAll) {
+ final ArrayList<ServiceRecord> services = new ArrayList<>();
+
+ final Predicate<ServiceRecord> filter = DumpUtils.filterRecord(name);
synchronized (mAm) {
int[] users = mAm.mUserController.getUsers();
- if ("all".equals(name)) {
- for (int user : users) {
- ServiceMap smap = mServiceMap.get(user);
- if (smap == null) {
- continue;
- }
- ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
- for (int i=0; i<alls.size(); i++) {
- ServiceRecord r1 = alls.valueAt(i);
- services.add(r1);
- }
- }
- } else {
- ComponentName componentName = name != null
- ? ComponentName.unflattenFromString(name) : null;
- int objectId = 0;
- if (componentName == null) {
- // Not a '/' separated full component name; maybe an object ID?
- try {
- objectId = Integer.parseInt(name, 16);
- name = null;
- componentName = null;
- } catch (RuntimeException e) {
- }
- }
- for (int user : users) {
- ServiceMap smap = mServiceMap.get(user);
- if (smap == null) {
- continue;
- }
- ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
- for (int i=0; i<alls.size(); i++) {
- ServiceRecord r1 = alls.valueAt(i);
- if (componentName != null) {
- if (r1.name.equals(componentName)) {
- services.add(r1);
- }
- } else if (name != null) {
- if (r1.name.flattenToString().contains(name)) {
- services.add(r1);
- }
- } else if (System.identityHashCode(r1) == objectId) {
- services.add(r1);
- }
+ for (int user : users) {
+ ServiceMap smap = mServiceMap.get(user);
+ if (smap == null) {
+ continue;
+ }
+ ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+ for (int i=0; i<alls.size(); i++) {
+ ServiceRecord r1 = alls.valueAt(i);
+
+ if (filter.test(r1)) {
+ services.add(r1);
}
}
}
@@ -4123,6 +4098,9 @@
return false;
}
+ // Sort by component name.
+ services.sort(Comparator.comparing(WithComponentName::getComponentName));
+
boolean needSep = false;
for (int i=0; i<services.size(); i++) {
if (needSep) {
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index 7b9b659..cd39bcd 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -32,7 +32,7 @@
import java.util.ArrayList;
import java.util.HashMap;
-final class ContentProviderRecord {
+final class ContentProviderRecord implements ComponentName.WithComponentName {
final ActivityManagerService service;
public final ProviderInfo info;
final int uid;
@@ -260,4 +260,8 @@
}
}
}
+
+ public ComponentName getComponentName() {
+ return name;
+ }
}
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
index 8a905f8..2f52002 100644
--- a/services/core/java/com/android/server/am/ProviderMap.java
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -17,22 +17,28 @@
package com.android.server.am;
import android.content.ComponentName;
+import android.content.ComponentName.WithComponentName;
import android.os.Binder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.os.TransferPipe;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DumpUtils;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
/**
* Keeps track of content providers by authority (name) and class. It separates the mapping by
@@ -325,7 +331,9 @@
private ArrayList<ContentProviderRecord> getProvidersForName(String name) {
ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>();
- ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
+ final ArrayList<ContentProviderRecord> ret = new ArrayList<>();
+
+ final Predicate<ContentProviderRecord> filter = DumpUtils.filterRecord(name);
synchronized (mAm) {
allProviders.addAll(mSingletonByClass.values());
@@ -333,39 +341,11 @@
allProviders.addAll(mProvidersByClassPerUser.valueAt(i).values());
}
- if ("all".equals(name)) {
- providers.addAll(allProviders);
- } else {
- ComponentName componentName = name != null
- ? ComponentName.unflattenFromString(name) : null;
- int objectId = 0;
- if (componentName == null) {
- // Not a '/' separated full component name; maybe an object ID?
- try {
- objectId = Integer.parseInt(name, 16);
- name = null;
- componentName = null;
- } catch (RuntimeException e) {
- }
- }
-
- for (int i=0; i<allProviders.size(); i++) {
- ContentProviderRecord r1 = allProviders.get(i);
- if (componentName != null) {
- if (r1.name.equals(componentName)) {
- providers.add(r1);
- }
- } else if (name != null) {
- if (r1.name.flattenToString().contains(name)) {
- providers.add(r1);
- }
- } else if (System.identityHashCode(r1) == objectId) {
- providers.add(r1);
- }
- }
- }
+ CollectionUtils.addIf(allProviders, ret, filter);
}
- return providers;
+ // Sort by component name.
+ ret.sort(Comparator.comparing(WithComponentName::getComponentName));
+ return ret;
}
protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8a174ed..32887e4 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -56,7 +56,7 @@
/**
* A running application service.
*/
-final class ServiceRecord extends Binder {
+final class ServiceRecord extends Binder implements ComponentName.WithComponentName {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ServiceRecord" : TAG_AM;
// Maximum number of delivery attempts before giving up.
@@ -757,4 +757,8 @@
.append(' ').append(shortName).append('}');
return stringName = sb.toString();
}
+
+ public ComponentName getComponentName() {
+ return name;
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8212463..cc3a489 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5971,6 +5971,8 @@
mConnectedDevices.remove(deviceKey);
return true;
}
+ Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec="
+ + deviceSpec + ", connect=" + connect);
}
return false;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 2845383..c81624a 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -822,9 +822,9 @@
private void resolveStrictModeHostname() {
try {
// Do a blocking DNS resolution using the network-assigned nameservers.
- mPrivateDnsConfig = new PrivateDnsConfig(
- mPrivateDnsProviderHostname,
- mNetwork.getAllByName(mPrivateDnsProviderHostname));
+ final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally(
+ mNetwork, mPrivateDnsProviderHostname);
+ mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
} catch (UnknownHostException uhe) {
mPrivateDnsConfig = null;
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index decae18..a55870f 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1980,6 +1980,9 @@
}
static String formatTime(long time) {
+ if (time == 0) {
+ return "N/A";
+ }
Time tobj = new Time();
tobj.set(time);
return tobj.format("%Y-%m-%d %H:%M:%S");
@@ -2334,13 +2337,28 @@
pw.print("]");
pw.println();
+ pw.println(" Per source last syncs:");
+ for (int j = 0; j < SyncStorageEngine.SOURCES.length; j++) {
+ pw.print(" ");
+ pw.print(String.format("%8s", SyncStorageEngine.SOURCES[j]));
+ pw.print(" Success: ");
+ pw.print(formatTime(event.second.perSourceLastSuccessTimes[j]));
+
+ pw.print(" Failure: ");
+ pw.println(formatTime(event.second.perSourceLastFailureTimes[j]));
+ }
+
+ pw.println(" Last syncs:");
for (int j = 0; j < event.second.getEventCount(); j++) {
- pw.print(" ");
+ pw.print(" ");
pw.print(formatTime(event.second.getEventTime(j)));
pw.print(' ');
pw.print(event.second.getEvent(j));
pw.println();
}
+ if (event.second.getEventCount() == 0) {
+ pw.println(" N/A");
+ }
}
}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index f54a9a0..6a343f8 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -128,8 +128,13 @@
public static final long NOT_IN_BACKOFF_MODE = -1;
- /** String names for the sync source types. */
- public static final String[] SOURCES = { "OTHER",
+ /**
+ * String names for the sync source types.
+ *
+ * KEEP THIS AND {@link SyncStatusInfo#SOURCE_COUNT} IN SYNC.
+ */
+ public static final String[] SOURCES = {
+ "OTHER",
"LOCAL",
"POLL",
"USER",
@@ -1231,12 +1236,7 @@
if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
writeStatusNow = true;
}
- status.lastSuccessTime = lastSyncTime;
- status.lastSuccessSource = item.source;
- status.lastFailureTime = 0;
- status.lastFailureSource = -1;
- status.lastFailureMesg = null;
- status.initialFailureTime = 0;
+ status.setLastSuccess(item.source, lastSyncTime);
ds.successCount++;
ds.successTime += elapsedTime;
} else if (!MESG_CANCELED.equals(resultMessage)) {
@@ -1246,12 +1246,8 @@
status.totalStats.numFailures++;
status.todayStats.numFailures++;
- status.lastFailureTime = lastSyncTime;
- status.lastFailureSource = item.source;
- status.lastFailureMesg = resultMessage;
- if (status.initialFailureTime == 0) {
- status.initialFailureTime = lastSyncTime;
- }
+ status.setLastFailure(item.source, lastSyncTime, resultMessage);
+
ds.failureCount++;
ds.failureTime += elapsedTime;
} else {
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 0c444cb..febce31 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -1284,12 +1284,16 @@
if (r.showBadge != DEFAULT_SHOW_BADGE) {
record.put("showBadge", Boolean.valueOf(r.showBadge));
}
+ JSONArray channels = new JSONArray();
for (NotificationChannel channel : r.channels.values()) {
- record.put("channel", channel.toJson());
+ channels.put(channel.toJson());
}
+ record.put("channels", channels);
+ JSONArray groups = new JSONArray();
for (NotificationChannelGroup group : r.groups.values()) {
- record.put("group", group.toJson());
+ groups.put(group.toJson());
}
+ record.put("groups", groups);
} catch (JSONException e) {
// pass
}
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
index e0b8426..c64e745 100644
--- a/services/core/java/com/android/server/os/SchedulingPolicyService.java
+++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java
@@ -18,8 +18,10 @@
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.IBinder;
import android.os.ISchedulingPolicyService;
import android.os.Process;
+import android.os.RemoteException;
import android.util.Log;
/**
@@ -35,7 +37,36 @@
private static final int PRIORITY_MIN = 1;
private static final int PRIORITY_MAX = 3;
+ private static final String[] MEDIA_PROCESS_NAMES = new String[] {
+ "media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service
+ };
+ private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ requestCpusetBoost(false /*enable*/, null /*client*/);
+ }
+ };
+ // Current process that received a cpuset boost
+ private int mBoostedPid = -1;
+ // Current client registered to the death recipient
+ private IBinder mClient;
+
public SchedulingPolicyService() {
+ // system_server (our host) could have crashed before. The app may not survive
+ // it, but mediaserver/media.codec could have, and mediaserver probably tried
+ // to disable the boost while we were dead.
+ // We do a restore of media.codec to default cpuset upon service restart to
+ // catch this case. We can't leave media.codec in boosted state, because we've
+ // lost the death recipient of mClient from mediaserver after the restart,
+ // if mediaserver dies in the future we won't have a notification to reset.
+ // (Note that if mediaserver thinks we're in boosted state before the crash,
+ // the state could go out of sync temporarily until mediaserver enables/disable
+ // boost next time, but this won't be a big issue.)
+ int[] nativePids = Process.getPidsForCommands(MEDIA_PROCESS_NAMES);
+ if (nativePids != null && nativePids.length == 1) {
+ mBoostedPid = nativePids[0];
+ disableCpusetBoost(nativePids[0]);
+ }
}
// TODO(b/35196900) We should pass the period in time units, rather
@@ -74,6 +105,94 @@
return PackageManager.PERMISSION_GRANTED;
}
+ // Request to move media.codec process between SP_FOREGROUND and SP_TOP_APP.
+ public int requestCpusetBoost(boolean enable, IBinder client) {
+ if (!isPermitted()) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ int[] nativePids = Process.getPidsForCommands(MEDIA_PROCESS_NAMES);
+ if (nativePids == null || nativePids.length != 1) {
+ Log.e(TAG, "requestCpusetBoost: can't find media.codec process");
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ synchronized (mDeathRecipient) {
+ if (enable) {
+ return enableCpusetBoost(nativePids[0], client);
+ } else {
+ return disableCpusetBoost(nativePids[0]);
+ }
+ }
+ }
+
+ private int enableCpusetBoost(int pid, IBinder client) {
+ if (mBoostedPid == pid) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
+ // The mediacodec process has changed, clean up the old pid and
+ // client before we boost the new process, so that the state
+ // is left clean if things go wrong.
+ mBoostedPid = -1;
+ if (mClient != null) {
+ try {
+ mClient.unlinkToDeath(mDeathRecipient, 0);
+ } catch (Exception e) {
+ } finally {
+ mClient = null;
+ }
+ }
+
+ try {
+ client.linkToDeath(mDeathRecipient, 0);
+
+ Log.i(TAG, "Moving " + pid + " to group " + Process.THREAD_GROUP_TOP_APP);
+ Process.setProcessGroup(pid, Process.THREAD_GROUP_TOP_APP);
+
+ mBoostedPid = pid;
+ mClient = client;
+
+ return PackageManager.PERMISSION_GRANTED;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed enableCpusetBoost: " + e);
+ try {
+ // unlink if things go wrong and don't crash.
+ client.unlinkToDeath(mDeathRecipient, 0);
+ } catch (Exception e1) {}
+ }
+
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ private int disableCpusetBoost(int pid) {
+ int boostedPid = mBoostedPid;
+
+ // Clean up states first.
+ mBoostedPid = -1;
+ if (mClient != null) {
+ try {
+ mClient.unlinkToDeath(mDeathRecipient, 0);
+ } catch (Exception e) {
+ } finally {
+ mClient = null;
+ }
+ }
+
+ // Try restore the old thread group, no need to fail as the
+ // mediacodec process could be dead just now.
+ if (boostedPid == pid) {
+ try {
+ Log.i(TAG, "Moving " + pid + " back to group default");
+ Process.setProcessGroup(pid, Process.THREAD_GROUP_DEFAULT);
+ } catch (Exception e) {
+ Log.w(TAG, "Couldn't move pid " + pid + " back to group default");
+ }
+ }
+
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
private boolean isPermitted() {
// schedulerservice hidl
if (Binder.getCallingPid() == Process.myPid()) {
@@ -81,9 +200,10 @@
}
switch (Binder.getCallingUid()) {
- case Process.AUDIOSERVER_UID: // fastcapture, fastmixer
+ case Process.AUDIOSERVER_UID: // fastcapture, fastmixer
+ case Process.MEDIA_UID: // mediaserver
case Process.CAMERASERVER_UID: // camera high frame rate recording
- case Process.BLUETOOTH_UID: // Bluetooth audio playback
+ case Process.BLUETOOTH_UID: // Bluetooth audio playback
return true;
default:
return false;
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 1b6f882..19c6c31 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -586,7 +586,29 @@
out_resource->name.type = ResourceType::kId;
out_resource->name.entry = maybe_name.value().to_string();
- out_resource->value = util::make_unique<Id>();
+
+ // Ids either represent a unique resource id or reference another resource id
+ auto item = ParseItem(parser, out_resource, resource_format);
+ if (!item) {
+ return false;
+ }
+
+ String* empty = ValueCast<String>(out_resource->value.get());
+ if (empty && *empty->value == "") {
+ // If no inner element exists, represent a unique identifier
+ out_resource->value = util::make_unique<Id>();
+ } else {
+ // If an inner element exists, the inner element must be a reference to
+ // another resource id
+ Reference* ref = ValueCast<Reference>(out_resource->value.get());
+ if (!ref || ref->name.value().type != ResourceType::kId) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> inner element must either be a resource reference or empty");
+ return false;
+ }
+ }
+
return true;
}
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index fc1aeaa..c12b9fa 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -933,4 +933,32 @@
EXPECT_FALSE(TestParse(input));
}
+TEST_F(ResourceParserTest, ParseIdItem) {
+ std::string input = R"(
+ <item name="foo" type="id">@id/bar</item>
+ <item name="bar" type="id"/>
+ <item name="baz" type="id"></item>)";
+ ASSERT_TRUE(TestParse(input));
+
+ ASSERT_THAT(test::GetValue<Reference>(&table_, "id/foo"), NotNull());
+ ASSERT_THAT(test::GetValue<Id>(&table_, "id/bar"), NotNull());
+ ASSERT_THAT(test::GetValue<Id>(&table_, "id/baz"), NotNull());
+
+ // Reject attribute references
+ input = R"(<item name="foo2" type="id">?attr/bar"</item>)";
+ ASSERT_FALSE(TestParse(input));
+
+ // Reject non-references
+ input = R"(<item name="foo3" type="id">0x7f010001</item>)";
+ ASSERT_FALSE(TestParse(input));
+ input = R"(<item name="foo4" type="id">@drawable/my_image</item>)";
+ ASSERT_FALSE(TestParse(input));
+ input = R"(<item name="foo5" type="id"><string name="biz"></string></item>)";
+ ASSERT_FALSE(TestParse(input));
+
+ // Ids that reference other resource ids cannot be public
+ input = R"(<public name="foo6" type="id">@id/bar6</item>)";
+ ASSERT_FALSE(TestParse(input));
+}
+
} // namespace aapt