Merge "Add View.startDragAndDrop and deprecate View.startDrag"
diff --git a/Android.mk b/Android.mk
index 9d2ca0d..f545207 100644
--- a/Android.mk
+++ b/Android.mk
@@ -430,7 +430,7 @@
$(framework_res_source_path)/com/android/internal/R.java
LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-libart conscrypt okhttp core-junit bouncycastle ext
+LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp core-junit bouncycastle ext
LOCAL_MODULE := framework
@@ -719,6 +719,7 @@
$(framework_res_source_path)/com/android/internal/R.java
framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \
+ core-oj \
core-libart \
conscrypt \
bouncycastle \
@@ -1104,7 +1105,7 @@
LOCAL_SRC_FILES := $(ext_src_files)
LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-libart
+LOCAL_JAVA_LIBRARIES := core-oj core-libart
LOCAL_STATIC_JAVA_LIBRARIES := libphonenumber-platform
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := ext
diff --git a/api/current.txt b/api/current.txt
index 9c0ccb9..1231ff3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4867,6 +4867,7 @@
field public static final int PRIORITY_MAX = 2; // 0x2
field public static final int PRIORITY_MIN = -2; // 0xfffffffe
field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
+ field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
field public static final int VISIBILITY_PRIVATE = 0; // 0x0
field public static final int VISIBILITY_PUBLIC = 1; // 0x1
field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
@@ -5865,6 +5866,7 @@
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
+ field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN";
@@ -25795,6 +25797,7 @@
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String CONTENT_VCARD_TYPE = "text/x-vcard";
field public static final android.net.Uri CONTENT_VCARD_URI;
+ field public static final android.net.Uri CORP_CONTENT_FILTER_URI;
field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
@@ -25915,12 +25918,15 @@
}
public static final class ContactsContract.Directory implements android.provider.BaseColumns {
+ method public static boolean isEnterpriseDirectoryId(long);
+ method public static boolean isRemoteDirectory(long);
method public static void notifyDirectoryChange(android.content.ContentResolver);
field public static final java.lang.String ACCOUNT_NAME = "accountName";
field public static final java.lang.String ACCOUNT_TYPE = "accountType";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
field public static final android.net.Uri CONTENT_URI;
+ field public static final android.net.Uri CORP_CONTENT_URI;
field public static final long DEFAULT = 0L; // 0x0L
field public static final java.lang.String DIRECTORY_AUTHORITY = "authority";
field public static final java.lang.String DISPLAY_NAME = "displayName";
@@ -26280,6 +26286,7 @@
field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
+ field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
}
@@ -34445,6 +34452,7 @@
ctor public LocaleList(java.util.Locale[]);
method public static android.util.LocaleList forLanguageTags(java.lang.String);
method public java.util.Locale get(int);
+ method public java.util.Locale getBestMatch(java.lang.String[]);
method public static android.util.LocaleList getDefault();
method public static android.util.LocaleList getEmptyLocaleList();
method public java.util.Locale getPrimary();
diff --git a/api/system-current.txt b/api/system-current.txt
index b3052b7..ef897b4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4985,6 +4985,7 @@
field public static final int PRIORITY_MAX = 2; // 0x2
field public static final int PRIORITY_MIN = -2; // 0xfffffffe
field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
+ field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
field public static final int VISIBILITY_PRIVATE = 0; // 0x0
field public static final int VISIBILITY_PUBLIC = 1; // 0x1
field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
@@ -5999,6 +6000,7 @@
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
+ field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN";
@@ -27778,6 +27780,7 @@
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String CONTENT_VCARD_TYPE = "text/x-vcard";
field public static final android.net.Uri CONTENT_VCARD_URI;
+ field public static final android.net.Uri CORP_CONTENT_FILTER_URI;
field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
@@ -27898,12 +27901,15 @@
}
public static final class ContactsContract.Directory implements android.provider.BaseColumns {
+ method public static boolean isEnterpriseDirectoryId(long);
+ method public static boolean isRemoteDirectory(long);
method public static void notifyDirectoryChange(android.content.ContentResolver);
field public static final java.lang.String ACCOUNT_NAME = "accountName";
field public static final java.lang.String ACCOUNT_TYPE = "accountType";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
field public static final android.net.Uri CONTENT_URI;
+ field public static final android.net.Uri CORP_CONTENT_URI;
field public static final long DEFAULT = 0L; // 0x0L
field public static final java.lang.String DIRECTORY_AUTHORITY = "authority";
field public static final java.lang.String DISPLAY_NAME = "displayName";
@@ -28293,6 +28299,7 @@
field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
+ field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
}
@@ -36768,6 +36775,7 @@
ctor public LocaleList(java.util.Locale[]);
method public static android.util.LocaleList forLanguageTags(java.lang.String);
method public java.util.Locale get(int);
+ method public java.util.Locale getBestMatch(java.lang.String[]);
method public static android.util.LocaleList getDefault();
method public static android.util.LocaleList getEmptyLocaleList();
method public java.util.Locale getPrimary();
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 84ddd9f..c1d5b19 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -47,11 +47,11 @@
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
- void setPackagePriority(String pkg, int uid, int priority);
- int getPackagePriority(String pkg, int uid);
-
- void setPackageVisibilityOverride(String pkg, int uid, int visibility);
- int getPackageVisibilityOverride(String pkg, int uid);
+ ParceledListSlice getTopics(String pkg, int uid);
+ void setTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic, int visibility);
+ int getTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic);
+ void setTopicPriority(String pkg, int uid, in Notification.Topic topic, int priority);
+ int getTopicPriority(String pkg, int uid, in Notification.Topic topic);
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
diff --git a/core/java/android/app/Notification.aidl b/core/java/android/app/Notification.aidl
index 9d8129c..3f1d113 100644
--- a/core/java/android/app/Notification.aidl
+++ b/core/java/android/app/Notification.aidl
@@ -17,3 +17,4 @@
package android.app;
parcelable Notification;
+parcelable Notification.Topic;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4e6548b..848b33f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -1433,6 +1434,9 @@
};
}
+ @SystemApi
+ public static final String TOPIC_DEFAULT = "system_default_topic";
+
private Topic topic;
public Topic getTopic() {
@@ -3419,6 +3423,10 @@
mN.extras.putStringArray(EXTRA_PEOPLE,
mPersonList.toArray(new String[mPersonList.size()]));
}
+ if (mN.topic == null) {
+ mN.topic = new Topic(TOPIC_DEFAULT, mContext.getString(
+ R.string.default_notification_topic_label));
+ }
return mN;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index faed7a0..94b434c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -31,6 +31,7 @@
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.ProxyInfo;
+import android.net.Uri;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.Process;
@@ -130,6 +131,7 @@
* As of {@link android.os.Build.VERSION_CODES#M}, it should contain the extra
* {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, although specifying only
* {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported.
+ * This intent may also contain the extra {@link #EXTRA_PROVISIONING_LOGO_URI}.
*
* <p> When managed provisioning has completed, broadcasts are sent to the application specified
* in the provisioning intent. The
@@ -196,6 +198,7 @@
* <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
* </ul>
*
* <p> When device owner provisioning has completed, an intent of the type
@@ -573,6 +576,28 @@
"android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
/**
+ * A {@link Uri} extra pointing to a logo image. This image will be shown during the
+ * provisioning. If this extra is not passed, a default image will be shown.
+ * <h5>The following URI schemes are accepted:</h5>
+ * <ul>
+ * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
+ * </ul>
+ *
+ * <p> It is the responsability of the caller to provide an image with a reasonable
+ * pixed density for the device.
+ *
+ * <p> If a content: URI is passed, the intent should have the flag
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and the uri should be added to the
+ * {@link android.content.ClipData} of the intent too.
+ *
+ * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE} or
+ * {@link #ACTION_PROVISION_MANAGED_DEVICE}
+ */
+ public static final String EXTRA_PROVISIONING_LOGO_URI =
+ "android.app.extra.PROVISIONING_LOGO_URI";
+
+ /**
* This MIME type is used for starting the Device Owner provisioning.
*
* <p>During device owner provisioning a device admin app is set as the owner of the device.
@@ -3763,12 +3788,18 @@
* {@link UserManager#getUserRestrictions()}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @throws SecurityException if the {@code admin} is not an active admin.
*/
public Bundle getUserRestrictions(@NonNull ComponentName admin) {
+ return getUserRestrictions(admin, myUserId());
+ }
+
+ /** @hide per-user version */
+ public Bundle getUserRestrictions(@NonNull ComponentName admin, int userHandle) {
Bundle ret = null;
if (mService != null) {
try {
- ret = mService.getUserRestrictions(admin);
+ ret = mService.getUserRestrictions(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 4270e16..0a0d77d 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,8 +16,6 @@
package android.app.admin;
-import android.os.Bundle;
-
import java.util.List;
/**
@@ -71,13 +69,4 @@
* @return true if the uid is an active admin with the given policy.
*/
public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy);
-
- /**
- * Takes a {@link Bundle} containing "base" user restrictions stored in
- * {@link com.android.server.pm.UserManagerService}, mixes restrictions set by the device owner
- * and the profile owner and returns the merged restrictions.
- *
- * This method always returns a new {@link Bundle}.
- */
- public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7601cf2..93a5503 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -149,7 +149,7 @@
ComponentName getRestrictionsProvider(int userHandle);
void setUserRestriction(in ComponentName who, in String key, boolean enable);
- Bundle getUserRestrictions(in ComponentName who);
+ Bundle getUserRestrictions(in ComponentName who, int userId);
void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
void clearCrossProfileIntentFilters(in ComponentName admin);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9d941fd..4a7cbc7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2956,8 +2956,8 @@
* multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE}
* to indicate this.
* <p>
- * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
- * returned URIs can be opened with
+ * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain
+ * URIs that can be opened with
* {@link ContentResolver#openFileDescriptor(Uri, String)}.
* <p>
* Output: The URI of the item that was picked, returned in
@@ -2992,8 +2992,8 @@
* Callers can provide an initial display name through {@link #EXTRA_TITLE},
* but the user may change this value before creating the file.
* <p>
- * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
- * returned URIs can be opened with
+ * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain
+ * URIs that can be opened with
* {@link ContentResolver#openFileDescriptor(Uri, String)}.
* <p>
* Output: The URI of the item that was created. This must be a
diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java
index 1fbef7a..e7dc764 100644
--- a/core/java/android/content/pm/ManifestDigest.java
+++ b/core/java/android/content/pm/ManifestDigest.java
@@ -16,6 +16,8 @@
package android.content.pm;
+import com.android.internal.util.HexDump;
+
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -118,7 +120,7 @@
final int N = mDigest.length;
for (int i = 0; i < N; i++) {
final byte b = mDigest[i];
- IntegralToString.appendByteAsHex(sb, b, false);
+ HexDump.appendByteAsHex(sb, b, false);
sb.append(',');
}
sb.append('}');
@@ -142,4 +144,4 @@
}
};
-}
\ No newline at end of file
+}
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 5b60c0d..2715af0 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -16,6 +16,8 @@
package android.net.http;
+import com.android.internal.util.HexDump;
+
import android.content.Context;
import android.os.Bundle;
import android.text.format.DateFormat;
@@ -285,7 +287,7 @@
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
- IntegralToString.appendByteAsHex(sb, b, true);
+ HexDump.appendByteAsHex(sb, b, true);
if (i+1 != bytes.length) {
sb.append(':');
}
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 9178ec6..26c7a1e 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -15,6 +15,9 @@
*/
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
* @hide Only for use within the system server.
*/
@@ -31,32 +34,18 @@
}
/**
- * Lock that must be held when calling certain methods in this class.
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
+ * to set per-user as well as global user restrictions.
*
- * This is used to avoid dead lock between
- * {@link com.android.server.pm.UserManagerService} and
- * {@link com.android.server.devicepolicy.DevicePolicyManagerService}. This lock should not
- * be newly taken while holding the DPMS lock, which would cause a dead lock. Take this
- * lock first before taking the DPMS lock to avoid that.
+ * @param userId target user id for the local restrictions.
+ * @param localRestrictions per-user restrictions.
+ * Caller must not change it once passed to this method.
+ * @param globalRestrictions global restrictions set by DO. Must be null when PO changed user
+ * restrictions, in which case global restrictions won't change.
+ * Caller must not change it once passed to this method.
*/
- public abstract Object getUserRestrictionsLock();
-
- /**
- * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
- * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
- *
- * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
- */
- public abstract void updateEffectiveUserRestrictionsLR(int userId);
-
- /**
- * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
- * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
- *
- * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
- */
- public abstract void updateEffectiveUserRestrictionsForAllUsersLR();
-
+ public abstract void setDevicePolicyUserRestrictions(int userId,
+ @NonNull Bundle localRestrictions, @Nullable Bundle globalRestrictions);
/**
* Returns the "base" user restrictions.
*
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index e3694e8..b860bfb 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -394,6 +394,14 @@
Uri.withAppendedPath(AUTHORITY_URI, "directories");
/**
+ * The content:// style URI for enterprise Directory table. Requests to this URI can be
+ * performed on the UI thread because they are always unblocking.
+ *
+ */
+ public static final Uri CORP_CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "directories_corp");
+
+ /**
* The MIME-type of {@link #CONTENT_URI} providing a directory of
* contact directories.
*/
@@ -417,6 +425,22 @@
public static final long LOCAL_INVISIBLE = 1;
/**
+ * _ID of the work profile default directory, which represents locally stored contacts.
+ *
+ * @hide
+ */
+ public static final long ENTERPRISE_DEFAULT = Directory.ENTERPRISE_DIRECTORY_ID_BASE
+ + DEFAULT;
+
+ /**
+ * _ID of the work profile directory that represents locally stored invisible contacts.
+ *
+ * @hide
+ */
+ public static final long ENTERPRISE_LOCAL_INVISIBLE = Directory.ENTERPRISE_DIRECTORY_ID_BASE
+ + LOCAL_INVISIBLE;
+
+ /**
* The name of the package that owns this directory. Contacts Provider
* fill it in with the name of the package containing the directory provider.
* If the package is later uninstalled, the directories it owns are
@@ -472,6 +496,15 @@
public static final String ACCOUNT_NAME = "accountName";
/**
+ * Mimimal ID for corp directory returned from
+ * {@link Directory#CORP_CONTENT_URI}.
+ *
+ * @hide
+ */
+ // slightly smaller than 2 ** 30
+ public static final long ENTERPRISE_DIRECTORY_ID_BASE = 1000000000;
+
+ /**
* One of {@link #EXPORT_SUPPORT_NONE}, {@link #EXPORT_SUPPORT_ANY_ACCOUNT},
* {@link #EXPORT_SUPPORT_SAME_ACCOUNT_ONLY}. This is the expectation the
* directory has for data exported from it. Clients must obey this setting.
@@ -555,6 +588,24 @@
public static final int PHOTO_SUPPORT_FULL = 3;
/**
+ * Return TRUE if it is a remote stored directory.
+ */
+ public static boolean isRemoteDirectory(long directoryId) {
+ return directoryId != Directory.DEFAULT
+ && directoryId != Directory.LOCAL_INVISIBLE
+ && directoryId != Directory.ENTERPRISE_DEFAULT
+ && directoryId != Directory.ENTERPRISE_LOCAL_INVISIBLE;
+ }
+
+ /**
+ * Return TRUE if a directory ID is from the contacts provider on the enterprise profile.
+ *
+ */
+ public static boolean isEnterpriseDirectoryId(long directoryId) {
+ return directoryId >= ENTERPRISE_DIRECTORY_ID_BASE;
+ }
+
+ /**
* Notifies the system of a change in the list of directories handled by
* a particular directory provider. The Contacts provider will turn around
* and send a query to the directory provider for the full list of directories,
@@ -1589,6 +1640,14 @@
CONTENT_URI, "filter");
/**
+ * It supports the same semantics as {@link #CONTENT_FILTER_URI} and returns the same
+ * columns. If there is a corp profile linked to the current profile, it will query corp
+ * profile, otherwise it will return null.
+ */
+ public static final Uri CORP_CONTENT_FILTER_URI = Uri.withAppendedPath(
+ CORP_CONTENT_URI, "filter");
+
+ /**
* The content:// style URI for this table joined with useful data from
* {@link ContactsContract.Data}, filtered to include only starred contacts
* and the most frequently contacted contacts.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index af7f472..8ae899f 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -233,6 +233,7 @@
* @see #FLAG_SUPPORTS_TYPED_DOCUMENT
* @see #FLAG_DIR_PREFERS_GRID
* @see #FLAG_DIR_PREFERS_LAST_MODIFIED
+ * @see #FLAG_VIRTUAL_DOCUMENT
*/
public static final String COLUMN_FLAGS = "flags";
@@ -356,6 +357,17 @@
public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 1 << 9;
/**
+ * Flag indicating that a document is virtual, and doesn't have byte
+ * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
+ *
+ * @see #COLUMN_FLAGS
+ * @see #COLUMN_MIME_TYPE
+ * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
+ * android.os.CancellationSignal)
+ */
+ public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 10;
+
+ /**
* Flag indicating that document titles should be hidden when viewing
* this directory in a larger format grid. For example, a directory
* containing only images may want the image thumbnails to speak for
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9034cc9..f560f8a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7758,6 +7758,7 @@
AUTO_TIME_ZONE,
POWER_SOUNDS_ENABLED,
DOCK_SOUNDS_ENABLED,
+ CHARGING_SOUNDS_ENABLED,
USB_MASS_STORAGE_ENABLED,
ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index 0d5c135..c1d23bb 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -49,6 +49,7 @@
return location < mList.length ? mList[location] : null;
}
+ @Nullable
public Locale getPrimary() {
return mList.length == 0 ? null : get(0);
}
@@ -179,6 +180,12 @@
}
}
+ @Nullable
+ public Locale getBestMatch(String[] locales) {
+ // TODO: Fix this to actually do locale negotiation and choose the best match
+ return getPrimary();
+ }
+
private final static Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 47df4e8..41f1ce7 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -937,10 +937,11 @@
}
@Override
- public void onDismiss() {
- super.onDismiss();
+ protected void onDismiss() {
mMenu.close();
mOverflowPopup = null;
+
+ super.onDismiss();
}
}
@@ -959,10 +960,11 @@
}
@Override
- public void onDismiss() {
- super.onDismiss();
+ protected void onDismiss() {
mActionButtonPopup = null;
mOpenSubMenuId = 0;
+
+ super.onDismiss();
}
}
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index a53df88..027f874 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -19,7 +19,6 @@
import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
-import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.ShowableListMenu;
import android.annotation.MenuRes;
@@ -45,7 +44,7 @@
private final MenuPopupHelper mPopup;
private OnMenuItemClickListener mMenuItemClickListener;
- private OnDismissListener mDismissListener;
+ private OnDismissListener mOnDismissListener;
private OnTouchListener mDragListener;
/**
@@ -114,20 +113,13 @@
mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
mPopup.setGravity(gravity);
- mPopup.setCallback(new MenuPresenter.Callback() {
+ mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss(PopupMenu.this);
+ public void onDismiss() {
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss(PopupMenu.this);
}
}
-
- @Override
- public boolean onOpenSubMenu(MenuBuilder subMenu) {
- // The menu presenter will handle opening the submenu itself.
- // Nothing to do here.
- return false;
- }
});
}
@@ -259,7 +251,7 @@
* @param listener the listener to notify
*/
public void setOnDismissListener(OnDismissListener listener) {
- mDismissListener = listener;
+ mOnDismissListener = listener;
}
/**
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index b523b84..a24fb40 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -84,6 +84,8 @@
private final RadialTimePickerView mRadialTimePickerView;
private final TextView mSeparatorView;
+ private final Calendar mTempCalendar;
+
private boolean mIsEnabled = true;
private boolean mAllowAutoAdvance;
private int mInitialHourOfDay;
@@ -103,8 +105,6 @@
private CharSequence mLastAnnouncedText;
private boolean mLastAnnouncedIsHour;
- private Calendar mTempCalendar;
-
public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(delegator, context);
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 2ed230b..863d409 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -32,6 +32,7 @@
import com.android.internal.R;
import java.util.Calendar;
+import java.util.Locale;
import libcore.icu.LocaleData;
@@ -45,11 +46,6 @@
private static final boolean DEFAULT_ENABLED_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
- // state
- private boolean mIs24HourView;
- private boolean mIsAm;
-
- // ui components
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
@@ -66,11 +62,15 @@
private final String[] mAmPmStrings;
+ private final Calendar mTempCalendar;
+
private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
- private Calendar mTempCalendar;
private boolean mHourWithTwoDigit;
private char mHourFormat;
+ private boolean mIs24HourView;
+ private boolean mIsAm;
+
public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(delegator, context);
@@ -202,6 +202,7 @@
updateAmPmControl();
// set to current time
+ mTempCalendar = Calendar.getInstance(mLocale);
setHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
setMinute(mTempCalendar.get(Calendar.MINUTE));
diff --git a/core/java/com/android/internal/app/SystemUserHomeActivity.java b/core/java/com/android/internal/app/SystemUserHomeActivity.java
new file mode 100644
index 0000000..26fbf6f
--- /dev/null
+++ b/core/java/com/android/internal/app/SystemUserHomeActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 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.app;
+
+import android.app.Activity;
+
+/**
+ * Placeholder home activity, which is always installed on the system user. At least one home
+ * activity must be present and enabled in order for the system to boot.
+ */
+public class SystemUserHomeActivity extends Activity {
+}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 08c7935..c992c70 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -33,6 +33,7 @@
public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
public static final int ACTION_ZEN_ALLOW_PEEK = 261;
public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
+ public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f73df00..9391c60 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -105,7 +105,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 135 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 136 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -1975,8 +1975,14 @@
private int buildBatteryLevelInt(HistoryItem h) {
return ((((int)h.batteryLevel)<<25)&0xfe000000)
- | ((((int)h.batteryTemperature)<<14)&0x01ff8000)
- | ((((int)h.batteryVoltage)<<1)&0x00007fff);
+ | ((((int)h.batteryTemperature)<<15)&0x01ff8000)
+ | ((((int)h.batteryVoltage)<<1)&0x00007ffe);
+ }
+
+ private void readBatteryLevelInt(int batteryLevelInt, HistoryItem out) {
+ out.batteryLevel = (byte)((batteryLevelInt & 0xfe000000) >>> 25);
+ out.batteryTemperature = (short)((batteryLevelInt & 0x01ff8000) >>> 15);
+ out.batteryVoltage = (char)((batteryLevelInt & 0x00007ffe) >>> 1);
}
private int buildStateInt(HistoryItem h) {
@@ -2117,9 +2123,7 @@
final int batteryLevelInt;
if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
batteryLevelInt = src.readInt();
- cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f);
- cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21);
- cur.batteryVoltage = (char)(batteryLevelInt&0x3fff);
+ readBatteryLevelInt(batteryLevelInt, cur);
cur.numReadInts += 1;
if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
+ Integer.toHexString(batteryLevelInt)
diff --git a/core/java/com/android/internal/util/HexDump.java b/core/java/com/android/internal/util/HexDump.java
index 3c7b7ac..7be95d8 100644
--- a/core/java/com/android/internal/util/HexDump.java
+++ b/core/java/com/android/internal/util/HexDump.java
@@ -19,28 +19,29 @@
public class HexDump
{
private final static char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
-
+ private final static char[] HEX_LOWER_CASE_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
public static String dumpHexString(byte[] array)
{
return dumpHexString(array, 0, array.length);
}
-
+
public static String dumpHexString(byte[] array, int offset, int length)
{
StringBuilder result = new StringBuilder();
-
+
byte[] line = new byte[16];
int lineIndex = 0;
-
+
result.append("\n0x");
result.append(toHexString(offset));
-
+
for (int i = offset ; i < offset + length ; i++)
{
if (lineIndex == 16)
{
result.append(" ");
-
+
for (int j = 0 ; j < 16 ; j++)
{
if (line[j] > ' ' && line[j] < '~')
@@ -52,20 +53,20 @@
result.append(".");
}
}
-
+
result.append("\n0x");
result.append(toHexString(i));
lineIndex = 0;
}
-
+
byte b = array[i];
result.append(" ");
result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
result.append(HEX_DIGITS[b & 0x0F]);
-
+
line[lineIndex++] = b;
}
-
+
if (lineIndex != 16)
{
int count = (16 - lineIndex) * 3;
@@ -74,7 +75,7 @@
{
result.append(" ");
}
-
+
for (int i = 0 ; i < lineIndex ; i++)
{
if (line[i] > ' ' && line[i] < '~')
@@ -87,10 +88,10 @@
}
}
}
-
+
return result.toString();
}
-
+
public static String toHexString(byte b)
{
return toHexString(toByteArray(b));
@@ -98,48 +99,59 @@
public static String toHexString(byte[] array)
{
- return toHexString(array, 0, array.length);
+ return toHexString(array, 0, array.length, true);
}
-
+
+ public static String toHexString(byte[] array, boolean upperCase)
+ {
+ return toHexString(array, 0, array.length, upperCase);
+ }
+
public static String toHexString(byte[] array, int offset, int length)
{
+ return toHexString(array, offset, length, true);
+ }
+
+ public static String toHexString(byte[] array, int offset, int length, boolean upperCase)
+ {
+ char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
char[] buf = new char[length * 2];
int bufIndex = 0;
- for (int i = offset ; i < offset + length; i++)
+ for (int i = offset ; i < offset + length; i++)
{
byte b = array[i];
- buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
- buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+ buf[bufIndex++] = digits[(b >>> 4) & 0x0F];
+ buf[bufIndex++] = digits[b & 0x0F];
}
- return new String(buf);
+ return new String(buf);
}
-
+
public static String toHexString(int i)
{
return toHexString(toByteArray(i));
}
-
+
public static byte[] toByteArray(byte b)
{
byte[] array = new byte[1];
array[0] = b;
return array;
}
-
+
public static byte[] toByteArray(int i)
{
byte[] array = new byte[4];
-
+
array[3] = (byte)(i & 0xFF);
array[2] = (byte)((i >> 8) & 0xFF);
array[1] = (byte)((i >> 16) & 0xFF);
array[0] = (byte)((i >> 24) & 0xFF);
-
+
return array;
}
-
+
private static int toByte(char c)
{
if (c >= '0' && c <= '9') return (c - '0');
@@ -148,7 +160,7 @@
throw new RuntimeException ("Invalid hex char '" + c + "'");
}
-
+
public static byte[] hexStringToByteArray(String hexString)
{
int length = hexString.length();
@@ -158,7 +170,15 @@
{
buffer[i / 2] = (byte)((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i+1)));
}
-
+
return buffer;
- }
+ }
+
+ public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
+ char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
+ sb.append(digits[(b >> 4) & 0xf]);
+ sb.append(digits[b & 0xf]);
+ return sb;
+ }
+
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index e674ecc..59d5f94 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -18,82 +18,104 @@
import com.android.internal.view.menu.MenuPresenter.Callback;
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
-import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
/**
* Presents a menu as a small, simple popup anchored to another view.
- *
- * @hide
*/
-public class MenuPopupHelper implements PopupWindow.OnDismissListener {
+public class MenuPopupHelper {
private final Context mContext;
+
+ // Immutable cached popup menu properties.
private final MenuBuilder mMenu;
private final boolean mOverflowOnly;
private final int mPopupStyleAttr;
private final int mPopupStyleRes;
+ // Mutable cached popup menu properties.
private View mAnchorView;
- private MenuPopup mPopup;
-
- private int mDropDownGravity = Gravity.NO_GRAVITY;
+ private int mDropDownGravity = Gravity.START;
private boolean mForceShowIcon;
- private boolean mShowTitle;
private Callback mPresenterCallback;
- private int mInitXOffset;
- private int mInitYOffset;
- public MenuPopupHelper(Context context, MenuBuilder menu) {
+ private MenuPopup mPopup;
+ private OnDismissListener mOnDismissListener;
+
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView) {
this(context, menu, anchorView, false, com.android.internal.R.attr.popupMenuStyle, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly, int popupStyleAttr) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView,
+ boolean overflowOnly, @AttrRes int popupStyleAttr) {
this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
+ @StyleRes int popupStyleRes) {
mContext = context;
mMenu = menu;
+ mAnchorView = anchorView;
mOverflowOnly = overflowOnly;
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
- mAnchorView = anchorView;
- mPopup = createMenuPopup();
}
- private MenuPopup createMenuPopup() {
- if (mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enableCascadingSubmenus)) {
- return new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr, mPopupStyleRes,
- mOverflowOnly);
- }
- return new StandardMenuPopup(
- mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly);
+ public void setOnDismissListener(@Nullable OnDismissListener listener) {
+ mOnDismissListener = listener;
}
- public void setAnchorView(View anchor) {
+ /**
+ * Sets the view to which the popup window is anchored.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param anchor the view to which the popup window should be anchored
+ */
+ public void setAnchorView(@NonNull View anchor) {
mAnchorView = anchor;
- mPopup.setAnchorView(anchor);
}
- public void setForceShowIcon(boolean forceShow) {
- mForceShowIcon = forceShow;
- mPopup.setForceShowIcon(forceShow);
+ /**
+ * Sets whether the popup menu's adapter is forced to show icons in the
+ * menu item views.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param forceShowIcon {@code true} to force icons to be shown, or
+ * {@code false} for icons to be optionally shown
+ */
+ public void setForceShowIcon(boolean forceShowIcon) {
+ mForceShowIcon = forceShowIcon;
}
+ /**
+ * Sets the alignment of the popup window relative to the anchor view.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param gravity alignment of the popup relative to the anchor
+ */
public void setGravity(int gravity) {
mDropDownGravity = gravity;
- mPopup.setGravity(gravity);
}
+ /**
+ * @return alignment of the popup relative to the anchor
+ */
public int getGravity() {
return mDropDownGravity;
}
@@ -110,7 +132,11 @@
}
}
- public ShowableListMenu getPopup() {
+ @NonNull
+ public MenuPopup getPopup() {
+ if (mPopup == null) {
+ mPopup = createPopup();
+ }
return mPopup;
}
@@ -129,14 +155,28 @@
return false;
}
- mInitXOffset = 0;
- mInitYOffset = 0;
- mShowTitle = false;
-
- showPopup();
+ showPopup(0, 0, false, false);
return true;
}
+ /**
+ * Shows the popup menu and makes a best-effort to anchor it to the
+ * specified (x,y) coordinate relative to the anchor view.
+ * <p>
+ * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
+ * display the popup with its top-left corner at (x,y) relative to the
+ * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
+ * popup's top-right corner will be at (x,y).
+ * <p>
+ * If the popup cannot be displayed fully on-screen, this method will
+ * attempt to scroll the anchor view's ancestors and/or offset the popup
+ * such that it may be displayed fully on-screen.
+ *
+ * @param x x coordinate relative to the anchor view
+ * @param y y coordinate relative to the anchor view
+ * @return {@code true} if the popup was shown or was already showing prior
+ * to calling this method, {@code false} otherwise
+ */
public boolean tryShow(int x, int y) {
if (isShowing()) {
return true;
@@ -146,51 +186,104 @@
return false;
}
- mInitXOffset = x;
- mInitYOffset = y;
- mShowTitle = true;
-
- showPopup();
+ showPopup(x, y, true, true);
return true;
}
- private void showPopup() {
- mPopup = createMenuPopup();
- mPopup.setAnchorView(mAnchorView);
- mPopup.setCallback(mPresenterCallback);
- mPopup.setForceShowIcon(mForceShowIcon);
- mPopup.setGravity(mDropDownGravity);
- mPopup.setHorizontalOffset(mInitXOffset);
- mPopup.setShowTitle(mShowTitle);
- mPopup.setVerticalOffset(mInitYOffset);
+ /**
+ * Creates the popup and assigns cached properties.
+ *
+ * @return an initialized popup
+ */
+ @NonNull
+ private MenuPopup createPopup() {
+ final boolean enableCascadingSubmenus = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableCascadingSubmenus);
- // In order for subclasses of MenuPopupHelper to satisfy the OnDismissedListener interface,
- // we must set the listener to this outer Helper rather than to the inner MenuPopup.
- // Not to worry -- the inner MenuPopup will call our own #onDismiss method after it's done
- // its own handling.
- mPopup.setOnDismissListener(this);
+ final MenuPopup popup;
+ if (enableCascadingSubmenus) {
+ popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
+ mPopupStyleRes, mOverflowOnly);
+ } else {
+ popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
+ mPopupStyleRes, mOverflowOnly);
+ }
- mPopup.addMenu(mMenu);
- mPopup.show();
+ // Assign immutable properties.
+ popup.addMenu(mMenu);
+ popup.setOnDismissListener(mInternalOnDismissListener);
+
+ // Assign mutable properties. These may be reassigned later.
+ popup.setAnchorView(mAnchorView);
+ popup.setCallback(mPresenterCallback);
+ popup.setForceShowIcon(mForceShowIcon);
+ popup.setGravity(mDropDownGravity);
+
+ return popup;
}
+ private void showPopup(int xOffset, int yOffset, boolean resolveOffsets, boolean showTitle) {
+ if (resolveOffsets) {
+ // If the resolved drop-down gravity is RIGHT, the popup's right
+ // edge will be aligned with the anchor view. Adjust by the anchor
+ // width such that the top-right corner is at the X offset.
+ final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
+ mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (hgrav == Gravity.RIGHT) {
+ xOffset -= mAnchorView.getWidth();
+ }
+ }
+
+ final MenuPopup popup = getPopup();
+ popup.setHorizontalOffset(xOffset);
+ popup.setVerticalOffset(yOffset);
+ popup.setShowTitle(showTitle);
+ popup.show();
+ }
+
+ /**
+ * Dismisses the popup, if showing.
+ */
public void dismiss() {
if (isShowing()) {
mPopup.dismiss();
}
}
- @Override
- public void onDismiss() {
+ /**
+ * Called after the popup has been dismissed.
+ * <p>
+ * <strong>Note:</strong> Subclasses should call the super implementation
+ * last to ensure that any necessary tear down has occurred before the
+ * listener specified by {@link #setOnDismissListener(OnDismissListener)}
+ * is called.
+ */
+ protected void onDismiss() {
mPopup = null;
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
+ }
}
public boolean isShowing() {
return mPopup != null && mPopup.isShowing();
}
- public void setCallback(MenuPresenter.Callback cb) {
+ public void setCallback(@Nullable MenuPresenter.Callback cb) {
mPresenterCallback = cb;
- mPopup.setCallback(cb);
+ if (mPopup != null) {
+ mPopup.setCallback(cb);
+ }
}
+
+ /**
+ * Listener used to proxy dismiss callbacks to the helper's owner.
+ */
+ private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ MenuPopupHelper.this.onDismiss();
+ }
+ };
}
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 3b7bce4..a694aca 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -30,6 +30,10 @@
* orientation when it can't fit its child views horizontally.
*/
public class ButtonBarLayout extends LinearLayout {
+ // Whether to allow vertically stacked button bars. This is disabled for
+ // configurations with a small (e.g. less than 320dp) screen height. -->
+ private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
+
/** Whether the current configuration allows stacking. */
private boolean mAllowStacking;
@@ -38,8 +42,12 @@
public ButtonBarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
+ final boolean allowStackingDefault =
+ context.getResources().getConfiguration().screenHeightDp
+ >= ALLOW_STACKING_MIN_HEIGHT_DP;
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
- mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false);
+ mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
+ allowStackingDefault);
ta.recycle();
}
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index 7fd288a..9b774b3 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -63,6 +63,11 @@
layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint);
}
+bool MinikinUtils::hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs) {
+ const TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
+ return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
+}
+
float MinikinUtils::xOffsetForTextAlign(Paint* paint, const Layout& layout) {
switch (paint->getTextAlign()) {
case Paint::kCenter_Align:
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index 1ee6245..5bf1eec 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -40,6 +40,8 @@
TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
size_t bufSize);
+ static bool hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs);
+
static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
static float hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index b50046f..9c11dd1 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -39,6 +39,7 @@
#include <minikin/GraphemeBreak.h>
#include <minikin/Measurement.h>
+#include <unicode/utf16.h>
#include "MinikinSkia.h"
#include "MinikinUtils.h"
#include "Paint.h"
@@ -852,45 +853,44 @@
return false;
}
- static jboolean hasGlyphVariation(const Paint* paint, TypefaceImpl* typeface, jint bidiFlags,
- const jchar* chars, size_t size) {
- // TODO: query font for whether character has variation selector; requires a corresponding
- // function in Minikin.
- return false;
- }
-
static jboolean hasGlyph(JNIEnv *env, jclass, jlong paintHandle, jlong typefaceHandle,
jint bidiFlags, jstring string) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
ScopedStringChars str(env, string);
- /* start by rejecting variation selectors (not supported yet) */
+ /* Start by rejecting unsupported base code point and variation selector pairs. */
size_t nChars = 0;
+ const uint32_t kStartOfString = 0xFFFFFFFF;
+ uint32_t prevCp = kStartOfString;
for (size_t i = 0; i < str.size(); i++) {
- jchar c = str[i];
- if (0xDC00 <= c && c <= 0xDFFF) {
+ jchar cu = str[i];
+ uint32_t cp = cu;
+ if (U16_IS_TRAIL(cu)) {
// invalid UTF-16, unpaired trailing surrogate
return false;
- } else if (0xD800 <= c && c <= 0xDBFF) {
+ } else if (U16_IS_LEAD(cu)) {
if (i + 1 == str.size()) {
// invalid UTF-16, unpaired leading surrogate at end of string
return false;
}
i++;
- jchar c2 = str[i];
- if (!(0xDC00 <= c2 && c2 <= 0xDFFF)) {
+ jchar cu2 = str[i];
+ if (!U16_IS_TRAIL(cu2)) {
// invalid UTF-16, unpaired leading surrogate
return false;
}
- // UTF-16 encoding of range U+E0100..U+E01EF is DB40 DD00 .. DB40 DDEF
- if (c == 0xDB40 && 0xDD00 <= c2 && c2 <= 0xDDEF) {
- return hasGlyphVariation(paint, typeface, bidiFlags, str.get(), str.size());
- }
- } else if (0xFE00 <= c && c <= 0xFE0F) {
- return hasGlyphVariation(paint, typeface, bidiFlags, str.get(), str.size());
+ cp = U16_GET_SUPPLEMENTARY(cu, cu2);
+ }
+
+ if (prevCp != kStartOfString &&
+ ((0xFE00 <= cp && cp <= 0xFE0F) || (0xE0100 <= cp && cp <= 0xE01EF)) &&
+ !MinikinUtils::hasVariationSelector(typeface, prevCp, cp)) {
+ // No font has a glyph for the code point and variation selector pair.
+ return false;
}
nChars++;
+ prevCp = cp;
}
Layout layout;
MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, str.get(), 0, str.size(),
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 42ede31..6338088 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2841,6 +2841,17 @@
android:process=":ui">
</activity>
+ <activity android:name="com.android.internal.app.SystemUserHomeActivity"
+ android:enabled="false"
+ android:process=":ui"
+ android:systemUserOnly="true"
+ android:theme="@style/Theme.Translucent.NoTitleBar">
+ <intent-filter android:priority="-100">
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+
<receiver android:name="com.android.server.BootReceiver"
android:systemUserOnly="true">
<intent-filter android:priority="1000">
diff --git a/core/res/res/layout/alert_dialog_button_bar_material.xml b/core/res/res/layout/alert_dialog_button_bar_material.xml
index 6e102f3..f7974a5 100644
--- a/core/res/res/layout/alert_dialog_button_bar_material.xml
+++ b/core/res/res/layout/alert_dialog_button_bar_material.xml
@@ -27,7 +27,6 @@
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="bottom"
- android:allowStacking="@bool/allow_stacked_button_bar"
style="?attr/buttonBarStyle">
<Button
diff --git a/core/res/res/values-h320dp/bools.xml b/core/res/res/values-h320dp/bools.xml
deleted file mode 100644
index 3bbfe96..0000000
--- a/core/res/res/values-h320dp/bools.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-
-<resources>
- <bool name="allow_stacked_button_bar">true</bool>
-</resources>
diff --git a/core/res/res/values-mcc208-mnc01/config.xml b/core/res/res/values-mcc208-mnc01/config.xml
index c56da24..5930e3a 100644
--- a/core/res/res/values-mcc208-mnc01/config.xml
+++ b/core/res/res/values-mcc208-mnc01/config.xml
@@ -28,9 +28,6 @@
note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
<string-array translatable="false" name="config_tether_apndata">
<item>Orange Internet,orange.fr,,,orange,orange,,,,,208,01,1,DUN</item>
- <item>[ApnSettingV3]Carrefour WAP,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,33</item>
- <item>[ApnSettingV3]VM WAP,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,52</item>
- <item>[ApnSettingV3]NRJWEB,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,4E</item>
</string-array>
</resources>
diff --git a/core/res/res/values-mcc208-mnc10/config.xml b/core/res/res/values-mcc208-mnc10/config.xml
index a32f266..d3640e5 100644
--- a/core/res/res/values-mcc208-mnc10/config.xml
+++ b/core/res/res/values-mcc208-mnc10/config.xml
@@ -29,11 +29,6 @@
<string-array translatable="false" name="config_tether_apndata">
<item>SFR option modem,websfr,,,,,,,,,208,10,,DUN</item>
<item>[ApnSettingV3]INTERNET NRJ,internetnrj,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4E</item>
- <item>[ApnSettingV3]Auchan,wap65,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,spn,A MOBILE</item>
- <item>[ApnSettingV3]LeclercMobile,wap66,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,spn,LeclercMobile</item>
- <item>[ApnSettingV3]Coriolis,fnetcoriolis,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,12</item>
- <item>[ApnSettingV3]WEB La Poste Mobile,wapdebitel,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4C</item>
- <item>[ApnSettingV3]Darty Surf Mails,wap68,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,44</item>
</string-array>
<string-array translatable="false" name="config_operatorConsideredNonRoaming">
diff --git a/core/res/res/values-mcc214-mnc07/config.xml b/core/res/res/values-mcc214-mnc07/config.xml
index 91571a5..4b7cc7c 100644
--- a/core/res/res/values-mcc214-mnc07/config.xml
+++ b/core/res/res/values-mcc214-mnc07/config.xml
@@ -28,7 +28,6 @@
note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
<string-array translatable="false" name="config_tether_apndata">
<item>Conexión Compartida,movistar.es,,,MOVISTAR,MOVISTAR,,,,,214,07,1,DUN</item>
- <item>[ApnSettingV3]Jazztel Internet,jazzinternet,,,,,,,,,214,07,,DUN,,,true,0,,,,,,,spn,JAZZTEL</item>
</string-array>
</resources>
diff --git a/core/res/res/values-mcc222-mnc10/config.xml b/core/res/res/values-mcc222-mnc10/config.xml
index 5a74462..cd6e8c6 100644
--- a/core/res/res/values-mcc222-mnc10/config.xml
+++ b/core/res/res/values-mcc222-mnc10/config.xml
@@ -20,16 +20,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. Do not translate. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
- <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
- <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
- <integer-array translatable="false" name="config_tether_upstream_types">
- <item>1</item>
- <item>4</item>
- <item>7</item>
- <item>9</item>
- </integer-array>
-
<!-- String containing the apn value for tethering. May be overriden by secure settings
TETHER_DUN_APN. Value is a comma separated series of strings:
"name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
diff --git a/core/res/res/values-mcc234-mnc20/config.xml b/core/res/res/values-mcc234-mnc20/config.xml
index 814960a..27c91d2 100644
--- a/core/res/res/values-mcc234-mnc20/config.xml
+++ b/core/res/res/values-mcc234-mnc20/config.xml
@@ -21,16 +21,6 @@
for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
- <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
- <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
- <integer-array translatable="false" name="config_tether_upstream_types">
- <item>1</item>
- <item>4</item>
- <item>7</item>
- <item>9</item>
- </integer-array>
-
<!-- String containing the apn value for tethering. May be overriden by secure settings
TETHER_DUN_APN. Value is a comma separated series of strings:
"name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 7c63950..457131a 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -25,8 +25,4 @@
<bool name="show_ongoing_ime_switcher">true</bool>
<bool name="action_bar_expanded_action_views_exclusive">true</bool>
<bool name="target_honeycomb_needs_options_menu">true</bool>
-
- <!-- Whether to allow vertically stacked button bars. This is disabled for
- configurations with a small (e.g. less than 320dp) screen height. -->
- <bool name="allow_stacked_button_bar">false</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 057790a..539baa5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1946,9 +1946,9 @@
See {@link com.android.server.notification.NotificationSignalExtractor} -->
<string-array name="config_notificationSignalExtractors">
<item>com.android.server.notification.ValidateNotificationPeople</item>
- <item>com.android.server.notification.PackagePriorityExtractor</item>
+ <item>com.android.server.notification.TopicPriorityExtractor</item>
<item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
- <item>com.android.server.notification.PackageVisibilityExtractor</item>
+ <item>com.android.server.notification.TopicVisibilityExtractor</item>
</string-array>
<!-- Flag indicating that this device does not rotate and will always remain in its default
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 00c0fe8..1964bec 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4072,4 +4072,6 @@
<item quantity="one"><xliff:g id="count" example="1">%1$d</xliff:g> selected</item>
<item quantity="other"><xliff:g id="count" example="3">%1$d</xliff:g> selected</item>
</plurals>
+
+ <string name="default_notification_topic_label">Miscellaneous</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1e325b1..edd3555 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2321,7 +2321,6 @@
<java-symbol type="string" name="lockscreen_access_pattern_area" />
- <java-symbol type="bool" name="allow_stacked_button_bar" />
<java-symbol type="bool" name="config_eap_sim_based_auth_supported" />
<java-symbol type="array" name="config_cell_retries_per_error_code" />
@@ -2337,4 +2336,5 @@
<java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
<java-symbol type="string" name="config_packagedKeyboardName" />
+ <java-symbol type="string" name="default_notification_topic_label" />
</resources>
diff --git a/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf
new file mode 100644
index 0000000..add3f40
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx
new file mode 100644
index 0000000..7038f46
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx
@@ -0,0 +1,365 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="BaseChar1"/>
+ <GlyphID id="2" name="BaseChar1_VS1"/>
+ <GlyphID id="3" name="BaseChar1_VS17"/>
+ <GlyphID id="4" name="BaseChar1_VS18"/>
+ <GlyphID id="5" name="BaseChar2"/>
+ <GlyphID id="6" name="BaseChar2_VS2"/>
+ <GlyphID id="7" name="BaseChar2_VS18"/>
+ <GlyphID id="8" name="BaseChar2_VS19"/>
+ <GlyphID id="9" name="BaseChar3"/>
+ <GlyphID id="10" name="BaseChar4_VS3"/>
+ <GlyphID id="11" name="BaseChar4_VS19"/>
+ <GlyphID id="12" name="BaseChar4_VS20"/>
+ <GlyphID id="13" name="BaseChar5"/>
+ <GlyphID id="14" name="BaseChar5_VS1"/>
+ <GlyphID id="15" name="BaseChar5_VS17"/>
+ <GlyphID id="16" name="BaseChar5_VS18"/>
+ <GlyphID id="17" name="BaseChar6"/>
+ <GlyphID id="18" name="BaseChar6_VS2"/>
+ <GlyphID id="19" name="BaseChar6_VS18"/>
+ <GlyphID id="20" name="BaseChar6_VS19"/>
+ <GlyphID id="21" name="BaseChar7"/>
+ <GlyphID id="22" name="BaseChar8_VS3"/>
+ <GlyphID id="23" name="BaseChar8_VS19"/>
+ <GlyphID id="24" name="BaseChar8_VS20"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Sep 9 08:01:17 2015"/>
+ <modified value="Wed Sep 9 08:48:07 2015"/>
+ <xMin value="30"/>
+ <yMin value="-200"/>
+ <xMax value="629"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="1.0"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="659"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="30"/>
+ <xMaxExtent value="629"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="18"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="54"/>
+ <maxPoints value="73"/>
+ <maxContours value="10"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="2"/>
+ <maxTwilightPoints value="12"/>
+ <maxStorage value="28"/>
+ <maxFunctionDefs value="119"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="61"/>
+ <maxSizeOfInstructions value="2967"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="BaseChar1" width="500" lsb="93"/>
+ <mtx name="BaseChar1_VS1" width="500" lsb="93"/>
+ <mtx name="BaseChar1_VS17" width="500" lsb="93"/>
+ <mtx name="BaseChar1_VS18" width="500" lsb="93"/>
+ <mtx name="BaseChar2" width="500" lsb="93"/>
+ <mtx name="BaseChar2_VS2" width="500" lsb="93"/>
+ <mtx name="BaseChar2_VS18" width="500" lsb="93"/>
+ <mtx name="BaseChar2_VS19" width="500" lsb="93"/>
+ <mtx name="BaseChar3" width="500" lsb="93"/>
+ <mtx name="BaseChar4_VS3" width="500" lsb="93"/>
+ <mtx name="BaseChar4_VS19" width="500" lsb="93"/>
+ <mtx name="BaseChar4_VS20" width="500" lsb="93"/>
+ <mtx name="BaseChar5" width="500" lsb="93"/>
+ <mtx name="BaseChar5_VS1" width="500" lsb="93"/>
+ <mtx name="BaseChar5_VS17" width="500" lsb="93"/>
+ <mtx name="BaseChar5_VS18" width="500" lsb="93"/>
+ <mtx name="BaseChar6" width="500" lsb="93"/>
+ <mtx name="BaseChar6_VS2" width="500" lsb="93"/>
+ <mtx name="BaseChar6_VS18" width="500" lsb="93"/>
+ <mtx name="BaseChar6_VS19" width="500" lsb="93"/>
+ <mtx name="BaseChar7" width="500" lsb="93"/>
+ <mtx name="BaseChar8_VS3" width="500" lsb="93"/>
+ <mtx name="BaseChar8_VS19" width="500" lsb="93"/>
+ <mtx name="BaseChar8_VS20" width="500" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name="BaseChar1" />
+ <map code="0x0062" name="BaseChar2" />
+ <map code="0x0063" name="BaseChar3" />
+ <!-- No cmap4 entry for BaseChar4 -->
+ <map code="0x1F000" name="BaseChar5" />
+ <map code="0x1F001" name="BaseChar6" />
+ <map code="0x1F002" name="BaseChar7" />
+ <!-- No cmap4 entry for BaseChar8 -->
+ </cmap_format_12>
+ <cmap_format_14 format="14" platformID="0" platEncID="5" length="24" numVarSelectorRecords="3">
+ <map uvs="0xFE00" uv="0x0061" name="BaseChar1_VS1" />
+ <map uvs="0xE0100" uv="0x0061" name="BaseChar1_VS17" />
+ <map uvs="0xE0101" uv="0x0061" name="BaseChar1_VS18" />
+ <map uvs="0xE0102" uv="0x0061" name="None" />
+
+ <map uvs="0xFE01" uv="0x0062" name="BaseChar2_VS2" />
+ <map uvs="0xE0101" uv="0x0062" name="BaseChar2_VS18" />
+ <map uvs="0xE0102" uv="0x0062" name="BaseChar2_VS19" />
+ <map uvs="0xE0103" uv="0x0062" name="None" />
+
+ <map uvs="0xFE02" uv="0x0064" name="BaseChar4_VS3" />
+ <map uvs="0xE0102" uv="0x0064" name="BaseChar4_VS19" />
+ <map uvs="0xE0103" uv="0x0064" name="BaseChar4_VS20" />
+ <!-- There is no default glyph for U+0064 U+E0104 but there is a entry for
+ default UVS entry. hasGlyph should return false in this
+ case. -->
+ <map uvs="0xE0104" uv="0x0064" name="None" />
+
+ <map uvs="0xFE00" uv="0x1F000" name="BaseChar5_VS1" />
+ <map uvs="0xE0100" uv="0x1F000" name="BaseChar5_VS17" />
+ <map uvs="0xE0101" uv="0x1F000" name="BaseChar5_VS18" />
+ <map uvs="0xE0102" uv="0x1F000" name="None" />
+
+ <map uvs="0xFE01" uv="0x1F001" name="BaseChar6_VS2" />
+ <map uvs="0xE0101" uv="0x1F001" name="BaseChar6_VS18" />
+ <map uvs="0xE0102" uv="0x1F001" name="BaseChar6_VS19" />
+ <map uvs="0xE0103" uv="0x1F001" name="None" />
+
+ <map uvs="0xFE02" uv="0x1F003" name="BaseChar8_VS3" />
+ <map uvs="0xE0102" uv="0x1F003" name="BaseChar8_VS19" />
+ <map uvs="0xE0103" uv="0x1F003" name="BaseChar8_VS20" />
+ <!-- There is no default glyph for U+1F003 U+E0104 but there is a entry for
+ default UVS entry. hasGlyph should return false in this
+ case. -->
+ <map uvs="0xE0104" uv="0x1F003" name="None" />
+ </cmap_format_14>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+
+ <TTGlyph name="BaseChar1" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar1_VS1" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar1_VS17" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar1_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar2" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar2_VS2" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar2_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar2_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar3" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar4_VS3" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar4_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar4_VS20" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar5" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar5_VS1" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar5_VS17" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar5_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar6" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar6_VS2" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar6_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar6_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar7" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar8_VS3" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar8_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="BaseChar8_VS20" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour></contour><instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Paint.hasGlyph Test
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Paint.hasGlyph Test
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ hasGlyphTestFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Paint.hasGlyph Test
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ hasGlyphTestFont Test
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ hasGlyphTestFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index e97bb33..2a3d463 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -20,6 +20,9 @@
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import java.util.Arrays;
+import java.util.HashSet;
+
/**
* PaintTest tests {@link Paint}.
*/
@@ -94,4 +97,63 @@
testCase.mWidthWithHinting, widths);
}
}
+
+ private static class HasGlyphTestCase {
+ public final int mBaseCodepoint;
+ public final HashSet<Integer> mVariationSelectors;
+
+ public HasGlyphTestCase(int baseCodepoint, Integer[] variationSelectors) {
+ mBaseCodepoint = baseCodepoint;
+ mVariationSelectors = new HashSet<>(Arrays.asList(variationSelectors));
+ }
+ }
+
+ private static String codePointsToString(int[] codepoints) {
+ StringBuilder sb = new StringBuilder();
+ for (int codepoint : codepoints) {
+ sb.append(Character.toChars(codepoint));
+ }
+ return sb.toString();
+ }
+
+ public void testHasGlyph_variationSelectors() {
+ final Typeface fontTypeface = Typeface.createFromAsset(
+ getInstrumentation().getContext().getAssets(), "fonts/hasGlyphTestFont.ttf");
+ Paint p = new Paint();
+ p.setTypeface(fontTypeface);
+
+ // Usually latin letters U+0061..U+0064 and Mahjong Tiles U+1F000..U+1F003 don't have
+ // variation selectors. This test may fail if system pre-installed fonts have a variation
+ // selector support for U+0061..U+0064 and U+1F000..U+1F003.
+ HasGlyphTestCase[] HAS_GLYPH_TEST_CASES = {
+ new HasGlyphTestCase(0x0061, new Integer[] {0xFE00, 0xE0100, 0xE0101, 0xE0102}),
+ new HasGlyphTestCase(0x0062, new Integer[] {0xFE01, 0xE0101, 0xE0102, 0xE0103}),
+ new HasGlyphTestCase(0x0063, new Integer[] {}),
+ new HasGlyphTestCase(0x0064, new Integer[] {0xFE02, 0xE0102, 0xE0103}),
+
+ new HasGlyphTestCase(0x1F000, new Integer[] {0xFE00, 0xE0100, 0xE0101, 0xE0102}),
+ new HasGlyphTestCase(0x1F001, new Integer[] {0xFE01, 0xE0101, 0xE0102, 0xE0103}),
+ new HasGlyphTestCase(0x1F002, new Integer[] {}),
+ new HasGlyphTestCase(0x1F003, new Integer[] {0xFE02, 0xE0102, 0xE0103}),
+ };
+
+ for (HasGlyphTestCase testCase : HAS_GLYPH_TEST_CASES) {
+ for (int vs = 0xFE00; vs <= 0xE01EF; ++vs) {
+ // Move to variation selector supplements after variation selectors.
+ if (vs == 0xFF00) {
+ vs = 0xE0100;
+ }
+ final String signature =
+ "hasGlyph(U+" + Integer.toHexString(testCase.mBaseCodepoint) +
+ " U+" + Integer.toHexString(vs) + ")";
+ final String testString =
+ codePointsToString(new int[] {testCase.mBaseCodepoint, vs});
+ if (testCase.mVariationSelectors.contains(vs)) {
+ assertTrue(signature + " is expected to be true", p.hasGlyph(testString));
+ } else {
+ assertFalse(signature + " is expected to be false", p.hasGlyph(testString));
+ }
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
new file mode 100644
index 0000000..951e87a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 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;
+
+public final class HexDumpTest extends TestCase {
+ public void testBytesToHexString() {
+ assertEquals("abcdef", HexDump.toHexString(
+ new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, false));
+ assertEquals("ABCDEF", HexDump.toHexString(
+ new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, true));
+ }
+}
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
index 58bf654..4186007 100644
--- a/data/keyboards/qwerty.kl
+++ b/data/keyboards/qwerty.kl
@@ -81,7 +81,7 @@
key 39 SEMICOLON
key 40 APOSTROPHE
key 14 DEL
-
+
key 44 Z
key 45 X
key 46 C
@@ -93,7 +93,7 @@
key 52 PERIOD
key 53 SLASH
key 28 ENTER
-
+
key 56 ALT_LEFT
key 100 ALT_RIGHT
key 42 SHIFT_LEFT
@@ -101,7 +101,7 @@
key 15 TAB
key 57 SPACE
key 150 EXPLORER
-key 155 ENVELOPE
+key 155 ENVELOPE
key 12 MINUS
key 13 EQUALS
@@ -110,3 +110,16 @@
# On an AT keyboard: ESC, F10
key 1 BACK
key 68 MENU
+
+# App switch = Overview key
+key 580 APP_SWITCH
+
+# Media control keys
+key 160 MEDIA_CLOSE
+key 161 MEDIA_EJECT
+key 163 MEDIA_NEXT
+key 164 MEDIA_PLAY_PAUSE
+key 165 MEDIA_PREVIOUS
+key 166 MEDIA_STOP
+key 167 MEDIA_RECORD
+key 168 MEDIA_REWIND
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index d45345e..595928a 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -71,7 +71,7 @@
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<intent-filter>
- <action android:name="android.provider.action.BROWSE_DOCUMENT_ROOT" />
+ <action android:name="android.provider.action.BROWSE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.document/root" />
</intent-filter>
diff --git a/packages/DocumentsUI/res/drawable/ic_root_home.xml b/packages/DocumentsUI/res/drawable/ic_root_home.xml
new file mode 100644
index 0000000..0a258ac
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/ic_root_home.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M20 6h-8l-2-2H4c-1.1 0-1.99 .9 -1.99 2L2 18c0 1.1 .9 2 2 2h16c1.1 0 2-.9
+2-2V8c0-1.1-.9-2-2-2zm-5 3c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm4
+8h-8v-1c0-1.33 2.67-2 4-2s4 .67 4 2v1z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index e965050..4f4649c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -172,6 +172,11 @@
Intent.EXTRA_ALLOW_MULTIPLE, false);
}
+ if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT
+ || state.action == ACTION_CREATE) {
+ state.openableOnly = intent.hasCategory(Intent.CATEGORY_OPENABLE);
+ }
+
if (state.action == ACTION_PICK_COPY_DESTINATION) {
state.directoryCopy = intent.getBooleanExtra(
Shared.EXTRA_DIRECTORY_COPY, false);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index beff196..4c844c4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -297,7 +297,7 @@
for (final RootInfo root : roots) {
final RootItem item = new RootItem(root);
- if (root.isLibrary()) {
+ if (root.isLibrary() || root.isHome()) {
libraries.add(item);
} else {
others.add(item);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 49a1e66..c81d4fb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -52,6 +52,7 @@
public boolean stackTouched;
public boolean restored;
public boolean directoryCopy;
+ public boolean openableOnly;
/** Transfer mode for file copy/move operations. */
public int transferMode;
@@ -119,6 +120,7 @@
out.writeMap(dirState);
out.writeList(selectedDocumentsForCopy);
out.writeList(excludedAuthorities);
+ out.writeInt(openableOnly ? 1 : 0);
}
public static final Creator<State> CREATOR = new Creator<State>() {
@@ -142,6 +144,7 @@
in.readMap(state.dirState, null);
in.readList(state.selectedDocumentsForCopy, null);
in.readList(state.excludedAuthorities, null);
+ state.openableOnly = in.readInt() != 0;
return state;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 8b3893f..b0421b0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -660,8 +660,7 @@
checkNotNull(cursor, "Cursor cannot be null.");
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
- return mTuner.canSelectType(docMimeType)
- && mTuner.isDocumentEnabled(docMimeType, docFlags);
+ return mTuner.canSelectType(docMimeType, docFlags);
}
return true;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index a0ff165..38d3805 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -63,7 +63,7 @@
// Subtly different from isDocumentEnabled. The reason may be illuminated as follows.
// A folder is enabled such that it may be double clicked, even in settings
// when the folder itself cannot be selected. This may also be true of container types.
- public boolean canSelectType(String docMimeType) {
+ public boolean canSelectType(String docMimeType, int docFlags) {
return true;
}
@@ -85,31 +85,44 @@
}
@Override
- public boolean canSelectType(String docMimeType) {
- switch (mState.action) {
- case ACTION_OPEN:
- case ACTION_CREATE:
- case ACTION_GET_CONTENT:
- return !isDirectory(docMimeType);
- case ACTION_OPEN_TREE:
- // In this case nothing *ever* is selectable...the expected user behavior is
- // they navigate *into* a folder, then click a confirmation button indicating
- // that the current directory is the directory they are picking.
- return false;
+ public boolean canSelectType(String docMimeType, int docFlags) {
+ if (!isDocumentEnabled(docMimeType, docFlags)) {
+ return false;
}
+
+ if (isDirectory(docMimeType)) {
+ return false;
+ }
+
+ if (mState.action == ACTION_OPEN_TREE) {
+ // In this case nothing *ever* is selectable...the expected user behavior is
+ // they navigate *into* a folder, then click a confirmation button indicating
+ // that the current directory is the directory they are picking.
+ return false;
+ }
+
return true;
}
@Override
public boolean isDocumentEnabled(String docMimeType, int docFlags) {
- // Directories are always enabled
+ // Directories are always enabled.
if (isDirectory(docMimeType)) {
return true;
}
- // Read-only files are disabled when creating
- if (mState.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
- return false;
+ switch (mState.action) {
+ case ACTION_CREATE:
+ // Read-only files are disabled when creating.
+ if ((docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
+ return false;
+ }
+ case ACTION_OPEN:
+ case ACTION_GET_CONTENT:
+ final boolean isVirtual = (docFlags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
+ if (isVirtual && mState.openableOnly) {
+ return false;
+ }
}
return MimePredicate.mimeMatches(mState.acceptMimes, docMimeType);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 723700d..ae5644d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -52,7 +52,7 @@
public static final int TYPE_DOWNLOADS = 5;
public static final int TYPE_LOCAL = 6;
public static final int TYPE_MTP = 7;
- public static final int TYPE_CLOUD = 8;
+ public static final int TYPE_OTHER = 8;
public String authority;
public String rootId;
@@ -168,7 +168,10 @@
derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null;
// TODO: remove these special case icons
- if (isExternalStorage()) {
+ if (isHome()) {
+ derivedIcon = R.drawable.ic_root_home;
+ derivedType = TYPE_LOCAL;
+ } else if (isExternalStorage()) {
derivedIcon = R.drawable.ic_root_sdcard;
derivedType = TYPE_LOCAL;
} else if (isDownloads()) {
@@ -188,7 +191,7 @@
} else if (isMtp()) {
derivedType = TYPE_MTP;
} else {
- derivedType = TYPE_CLOUD;
+ derivedType = TYPE_OTHER;
}
}
@@ -196,6 +199,13 @@
return authority == null && rootId == null;
}
+ public boolean isHome() {
+ // Note that "home" is the expected root id for the auto-created
+ // user home directory on external storage. The "home" value should
+ // match ExternalStorageProvider.ROOT_ID_HOME.
+ return isExternalStorage() && "home".equals(rootId);
+ }
+
public boolean isExternalStorage() {
return "com.android.externalstorage.documents".equals(authority);
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index ba91c83..71d8b34 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -125,6 +125,7 @@
"Videos",
"Audio",
"Downloads",
+ "Home",
ROOT_0_ID,
ROOT_1_ID);
}
@@ -136,6 +137,13 @@
mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
}
+ public void testRootClickSetsWindowTitle() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot("Home");
+ mBot.assertWindowTitle("Home");
+ }
+
public void testFilesList_LiveUpdate() throws Exception {
initTestFiles();
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 5c09794..ecad061 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -16,6 +16,8 @@
package com.android.documentsui;
+import static junit.framework.Assert.assertEquals;
+
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
@@ -80,6 +82,20 @@
mDevice.waitForIdle();
}
+ void assertWindowTitle(String expected) {
+ // Turns out the title field on a window does not have
+ // an id associated with it at runtime (which confuses the hell out of me)
+ // In code we address this via "android.R.id.title".
+ UiObject2 o = find(By.text(expected));
+ // It's a bit of a conceit that we then *assert* that the title
+ // is the value that we used to identify the UiObject2.
+ // If the preceeding lookup fails, this'll choke with an NPE.
+ // But given the issue described in the comment above, we're
+ // going to do it anyway. Because we shouldn't be looking up
+ // the uiobject by it's expected content :|
+ assertEquals(expected, o.getText());
+ }
+
void assertHasRoots(String... labels) throws UiObjectNotFoundException {
List<String> missing = new ArrayList<>();
for (String label : labels) {
diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml
index f1c1ade..e48436e 100644
--- a/packages/ExternalStorageProvider/res/values/strings.xml
+++ b/packages/ExternalStorageProvider/res/values/strings.xml
@@ -20,6 +20,6 @@
<!-- Title for documents backend that offers internal storage. [CHAR LIMIT=24] -->
<string name="root_internal_storage">Internal storage</string>
- <!-- Title for documents backend that offers documents. [CHAR LIMIT=24] -->
- <string name="root_documents">Documents</string>
+ <!-- Title for user home dir. [CHAR LIMIT=24] -->
+ <string name="root_home">Home</string>
</resources>
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index fcd45f2..2cedc72 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -85,9 +85,11 @@
public String docId;
public File visiblePath;
public File path;
+ public boolean reportAvailableBytes = true;
}
private static final String ROOT_ID_PRIMARY_EMULATED = "primary";
+ private static final String ROOT_ID_HOME = "home";
private StorageManager mStorageManager;
private Handler mHandler;
@@ -118,6 +120,7 @@
private void updateVolumesLocked() {
mRoots.clear();
+ VolumeInfo primaryVolume = null;
final int userId = UserHandle.myUserId();
final List<VolumeInfo> volumes = mStorageManager.getVolumes();
for (VolumeInfo volume : volumes) {
@@ -126,6 +129,9 @@
final String rootId;
final String title;
if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
+ // save off the primary volume for subsequent "Home" dir initialization.
+ primaryVolume = volume;
+
// We currently only support a single emulated volume mounted at
// a time, and it's always considered the primary
rootId = ROOT_ID_PRIMARY_EMULATED;
@@ -152,25 +158,58 @@
continue;
}
+ final RootInfo root = new RootInfo();
+ mRoots.put(rootId, root);
+
+ root.rootId = rootId;
+ root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
+ | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
+
+ // Dunno when this would NOT be the case, but never hurts to be correct.
+ if (volume.isMountedWritable()) {
+ root.flags |= Root.FLAG_SUPPORTS_CREATE;
+ }
+ root.title = title;
+ if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
+ root.flags |= Root.FLAG_HAS_SETTINGS;
+ }
+ if (volume.isVisibleForRead(userId)) {
+ root.visiblePath = volume.getPathForUser(userId);
+ } else {
+ root.visiblePath = null;
+ }
+ root.path = volume.getInternalPathForUser(userId);
try {
- final RootInfo root = new RootInfo();
- mRoots.put(rootId, root);
-
- root.rootId = rootId;
- root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
- | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
- root.title = title;
- if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
- root.flags |= Root.FLAG_HAS_SETTINGS;
- }
- if (volume.isVisibleForRead(userId)) {
- root.visiblePath = volume.getPathForUser(userId);
- } else {
- root.visiblePath = null;
- }
- root.path = volume.getInternalPathForUser(userId);
root.docId = getDocIdForFile(root.path);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ // Finally, if primary storage is available we add the "Home" directory,
+ // creating it as needed.
+ if (primaryVolume != null && primaryVolume.isVisible()) {
+ final RootInfo root = new RootInfo();
+ root.rootId = ROOT_ID_HOME;
+ mRoots.put(root.rootId, root);
+ root.title = getContext().getString(R.string.root_home);
+
+ // Only report bytes on *volumes*...as a matter of policy.
+ root.reportAvailableBytes = false;
+ root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH
+ | Root.FLAG_SUPPORTS_IS_CHILD;
+
+ // Dunno when this would NOT be the case, but never hurts to be correct.
+ if (primaryVolume.isMountedWritable()) {
+ root.flags |= Root.FLAG_SUPPORTS_CREATE;
+ }
+
+ root.visiblePath = new File(
+ primaryVolume.getPathForUser(userId), root.rootId);
+ root.path = new File(
+ primaryVolume.getInternalPathForUser(userId), root.rootId);
+ try {
+ root.docId = getDocIdForFile(root.path);
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
@@ -312,7 +351,8 @@
row.add(Root.COLUMN_FLAGS, root.flags);
row.add(Root.COLUMN_TITLE, root.title);
row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
- row.add(Root.COLUMN_AVAILABLE_BYTES, root.path.getFreeSpace());
+ row.add(Root.COLUMN_AVAILABLE_BYTES,
+ root.reportAvailableBytes ? root.path.getFreeSpace() : -1);
}
}
return result;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index b9a9c24..2e96f18 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -159,6 +159,7 @@
String key_mgmt = "";
boolean certUsed = false;
boolean hasWepKey = false;
+ boolean isEap = false;
final ArrayList<String> rawLines = new ArrayList<String>();
public static Network readFromStream(BufferedReader in) {
@@ -189,6 +190,9 @@
ssid = line;
} else if (line.startsWith("key_mgmt=")) {
key_mgmt = line;
+ if (line.contains("EAP")) {
+ isEap = true;
+ }
} else if (line.startsWith("client_cert=")) {
certUsed = true;
} else if (line.startsWith("ca_cert=")) {
@@ -197,6 +201,8 @@
certUsed = true;
} else if (line.startsWith("wep_")) {
hasWepKey = true;
+ } else if (line.startsWith("eap=")) {
+ isEap = true;
}
}
@@ -325,6 +331,13 @@
continue;
}
}
+ // Don't propagate EAP network definitions
+ if (net.isEap) {
+ if (DEBUG_BACKUP) {
+ Log.v(TAG, "Skipping EAP network " + net.ssid + " / " + net.key_mgmt);
+ }
+ continue;
+ }
if (! mKnownNetworks.contains(net)) {
if (DEBUG_BACKUP) {
Log.v(TAG, "Adding " + net.ssid + " / " + net.key_mgmt);
@@ -353,6 +366,12 @@
continue;
}
+ if (net.isEap) {
+ // Similarly, omit EAP network definitions to avoid propagating
+ // controlled enterprise network definitions.
+ continue;
+ }
+
net.write(w);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 8b1caf9..3971706 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -904,12 +904,12 @@
}
}
- // Enforce what the calling package can mutate the system settings.
- enforceRestrictedSystemSettingsMutationForCallingPackage(operation, name, runAsUserId);
-
// Resolve the userId on whose behalf the call is made.
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId);
+ // Enforce what the calling package can mutate the system settings.
+ enforceRestrictedSystemSettingsMutationForCallingPackage(operation, name, callingUserId);
+
// Determine the owning user as some profile settings are cloned from the parent.
final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 9a4cd93..f5f6acf 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -31,5 +31,20 @@
/**
* Docks the top-most task and opens recents.
*/
- void dockTopTask();
+ void dockTopTask(boolean draggingInRecents);
+
+ /**
+ * Called during a drag-from-navbar-in gesture.
+ *
+ * @param distanceFromTop the distance of the current drag in gesture from the top of the
+ * screen
+ */
+ void onDraggingInRecents(float distanceFromTop);
+
+ /**
+ * Called when the gesture to drag in recents ended.
+ *
+ * @param velocity the velocity of the finger when releasing it in pixels per second
+ */
+ void onDraggingInRecentsEnded(float velocity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 949efc5..19e299254 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -48,12 +48,12 @@
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
+ Divider.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.keyboard.KeyboardUI.class,
- Divider.class
};
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
index 79eca30d..7cfe38e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -24,8 +24,10 @@
oneway interface IRecentsNonSystemUserCallbacks {
void preloadRecents();
void cancelPreloadingRecents();
- void showRecents(boolean triggeredFromAltTab);
+ void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents);
void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecents();
void onConfigurationChanged();
+ void onDraggingInRecents(float distanceFromTop);
+ void onDraggingInRecentsEnded(float velocity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 3806b46..348bd87 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -73,6 +73,7 @@
private Handler mHandler;
private RecentsImpl mImpl;
+ private int mDraggingInRecentsCurrentUser;
// Only For system user, this is the callbacks instance we return to each secondary user
private RecentsSystemUser mSystemUserCallbacks;
@@ -213,14 +214,14 @@
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
- mImpl.showRecents(triggeredFromAltTab);
+ mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
} else {
if (mSystemUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
- callbacks.showRecents(triggeredFromAltTab);
+ callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
@@ -361,8 +362,57 @@
}
@Override
- public void dockTopTask() {
- mImpl.dockTopTask();
+ public void dockTopTask(boolean draggingInRecents) {
+ mImpl.dockTopTask(draggingInRecents);
+ if (draggingInRecents) {
+ mDraggingInRecentsCurrentUser = sSystemServicesProxy.getCurrentUser();
+ }
+ }
+
+ @Override
+ public void onDraggingInRecents(float distanceFromTop) {
+ if (sSystemServicesProxy.isSystemUser(mDraggingInRecentsCurrentUser)) {
+ mImpl.onDraggingInRecents(distanceFromTop);
+ } else {
+ if (mSystemUserCallbacks != null) {
+ IRecentsNonSystemUserCallbacks callbacks =
+ mSystemUserCallbacks.getNonSystemUserRecentsForUser(
+ mDraggingInRecentsCurrentUser);
+ if (callbacks != null) {
+ try {
+ callbacks.onDraggingInRecents(distanceFromTop);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ } else {
+ Log.e(TAG, "No SystemUI callbacks found for user: "
+ + mDraggingInRecentsCurrentUser);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDraggingInRecentsEnded(float velocity) {
+ if (sSystemServicesProxy.isSystemUser(mDraggingInRecentsCurrentUser)) {
+ mImpl.onDraggingInRecentsEnded(velocity);
+ } else {
+ if (mSystemUserCallbacks != null) {
+ IRecentsNonSystemUserCallbacks callbacks =
+ mSystemUserCallbacks.getNonSystemUserRecentsForUser(
+ mDraggingInRecentsCurrentUser);
+ if (callbacks != null) {
+ try {
+ callbacks.onDraggingInRecentsEnded(velocity);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ } else {
+ Log.e(TAG, "No SystemUI callbacks found for user: "
+ + mDraggingInRecentsCurrentUser);
+ }
+ }
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 85a2eda..618eb6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -46,6 +46,8 @@
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ForegroundThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -140,6 +142,7 @@
RecentsAppWidgetHost mAppWidgetHost;
boolean mBootCompleted;
boolean mCanReuseTaskStackViews = true;
+ boolean mDraggingInRecents;
// Task launching
Rect mSearchBarBounds = new Rect();
@@ -164,14 +167,13 @@
public void run() {
// When this fires, then the user has not released alt-tab for at least
// FAST_ALT_TAB_DELAY_MS milliseconds
- showRecents(mTriggeredFromAltTab);
+ showRecents(mTriggeredFromAltTab, false /* draggingInRecents */);
}
});
Bitmap mThumbnailTransitionBitmapCache;
Task mThumbnailTransitionBitmapCacheKey;
-
public RecentsImpl(Context context) {
mContext = context;
mHandler = new Handler();
@@ -248,8 +250,9 @@
}
@Override
- public void showRecents(boolean triggeredFromAltTab) {
+ public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents) {
mTriggeredFromAltTab = triggeredFromAltTab;
+ mDraggingInRecents = draggingInRecents;
if (mFastAltTabTrigger.hasTriggered()) {
// We are calling this from the doze trigger, so just fall through to show Recents
mFastAltTabTrigger.resetTrigger();
@@ -315,6 +318,7 @@
return;
}
+ mDraggingInRecents = false;
mTriggeredFromAltTab = false;
try {
@@ -385,6 +389,16 @@
// Do nothing
}
+ @Override
+ public void onDraggingInRecents(float distanceFromTop) {
+ EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
+ }
+
+ @Override
+ public void onDraggingInRecentsEnded(float velocity) {
+ EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
+ }
+
/**
* Transitions to the next recent task in the stack.
*/
@@ -521,13 +535,13 @@
showRelativeAffiliatedTask(false);
}
- public void dockTopTask() {
+ public void dockTopTask(boolean draggingInRecents) {
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
ssp.startTaskInDockedMode(topTask.id,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
- showRecents(false /* triggeredFromAltTab */);
+ showRecents(false /* triggeredFromAltTab */, draggingInRecents);
}
}
@@ -856,7 +870,8 @@
launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
launchState.launchedHasConfigurationChanged = false;
- launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID;
+ launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID
+ || mDraggingInRecents;
Intent intent = new Intent();
intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index b091f05..d4d13f0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -502,6 +502,19 @@
queueEvent(event);
}
+ /**
+ * If this method is called from the main thread, it will be handled directly. If this method
+ * is not called from the main thread, it will be posted onto the main thread.
+ */
+ public void sendOntoMainThread(Event event) {
+ long callingThreadId = Thread.currentThread().getId();
+ if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+ post(event);
+ } else {
+ send(event);
+ }
+ }
+
/** Prevent post()ing an InterprocessEvent */
@Deprecated
public void post(InterprocessEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java
new file mode 100644
index 0000000..9be8eb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java
@@ -0,0 +1,15 @@
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus.Event;
+
+/**
+ * This event is sent when the user finished dragging in recents.
+ */
+public class DraggingInRecentsEndedEvent extends Event {
+
+ public final float velocity;
+
+ public DraggingInRecentsEndedEvent(float velocity) {
+ this.velocity = velocity;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java
new file mode 100644
index 0000000..5e8bfd4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java
@@ -0,0 +1,15 @@
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus.Event;
+
+/**
+ * This event is sent when the user changed how far they are dragging in recents.
+ */
+public class DraggingInRecentsEvent extends Event {
+
+ public final float distanceFromTop;
+
+ public DraggingInRecentsEvent(float distanceFromTop) {
+ this.distanceFromTop = distanceFromTop;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index e37b7dc..d18389f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -41,6 +41,8 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -542,6 +544,15 @@
}
}
+ public final void onBusEvent(DraggingInRecentsEvent event) {
+ setStackViewVisibility(View.VISIBLE);
+ setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
+ }
+
+ public final void onBusEvent(DraggingInRecentsEndedEvent event) {
+ animate().translationY(0f);
+ }
+
/**
* Updates the dock region to match the specified dock state.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index dd894ce..50e010f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -32,6 +32,7 @@
private static final String TAG = "Divider";
private int mDividerWindowWidth;
private DividerWindowManager mWindowManager;
+ private DividerView mView;
@Override
public void start() {
@@ -39,6 +40,7 @@
mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
update(mContext.getResources().getConfiguration());
+ putComponent(Divider.class, this);
}
@Override
@@ -47,14 +49,18 @@
update(newConfig);
}
+ public DividerView getView() {
+ return mView;
+ }
+
private void addDivider(Configuration configuration) {
- DividerView view = (DividerView)
+ mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
- mWindowManager.add(view, width, height);
- view.setWindowManager(mWindowManager);
+ mWindowManager.add(mView, width, height);
+ mView.setWindowManager(mWindowManager);
}
private void removeDivider() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
index 5f983c5..69e90cc 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
@@ -98,10 +98,10 @@
// TODO: Better calculation
targets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
- targets.add(new SnapTarget((int) (0.35f * dividerMax) - mDividerSize / 2,
+ targets.add(new SnapTarget((int) (0.38f * dividerMax) - mDividerSize / 2,
SnapTarget.FLAG_NONE));
targets.add(new SnapTarget(dividerMax / 2 - mDividerSize / 2, SnapTarget.FLAG_NONE));
- targets.add(new SnapTarget((int) (0.65f * dividerMax) - mDividerSize / 2,
+ targets.add(new SnapTarget((int) (0.62f * dividerMax) - mDividerSize / 2,
SnapTarget.FLAG_NONE));
targets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
return targets;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index a520a33..3e317ff 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -127,6 +127,28 @@
mWindowManager = windowManager;
}
+ public WindowManagerProxy getWindowManagerProxy() {
+ return mWindowManagerProxy;
+ }
+
+ public boolean startDragging() {
+ mDockSide = mWindowManagerProxy.getDockSide();
+ if (mDockSide != WindowManager.DOCKED_INVALID) {
+ mWindowManagerProxy.setResizing(true);
+ mWindowManager.setSlippery(false);
+ liftBackground();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void stopDragging(int position, float velocity) {
+ fling(position, velocity);
+ mWindowManager.setSlippery(true);
+ releaseBackground();
+ }
+
@Override
public boolean onTouch(View v, MotionEvent event) {
convertToScreenCoordinates(event);
@@ -138,20 +160,13 @@
mStartX = (int) event.getX();
mStartY = (int) event.getY();
getLocationOnScreen(mTempInt2);
- mDockSide = mWindowManagerProxy.getDockSide();
+ boolean result = startDragging();
if (isHorizontalDivision()) {
mStartPosition = mTempInt2[1] + mDividerInsets;
} else {
mStartPosition = mTempInt2[0] + mDividerInsets;
}
- if (mDockSide != WindowManager.DOCKED_INVALID) {
- mWindowManagerProxy.setResizing(true);
- mWindowManager.setSlippery(false);
- liftBackground();
- return true;
- } else {
- return false;
- }
+ return result;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
@@ -168,10 +183,9 @@
y = (int) event.getRawY();
mVelocityTracker.computeCurrentVelocity(1000);
- fling(x, y, mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
- mWindowManager.setSlippery(true);
- releaseBackground();
+ int position = calculatePosition(x, y);
+ stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
+ : mVelocityTracker.getXVelocity());
break;
}
return true;
@@ -181,9 +195,7 @@
event.setLocation(event.getRawX(), event.getRawY());
}
- private void fling(int x, int y, float xVelocity, float yVelocity) {
- int position = calculatePosition(x, y);
- float velocity = isHorizontalDivision() ? yVelocity : xVelocity;
+ private void fling(int position, float velocity) {
final SnapTarget snapTarget = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils,
mDividerSize, isHorizontalDivision()).calculateSnapTarget(position, velocity);
@@ -277,9 +289,8 @@
return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
}
- private boolean isHorizontalDivision() {
- return mDockSide == WindowManager.DOCKED_TOP
- || mDockSide == WindowManager.DOCKED_BOTTOM;
+ public boolean isHorizontalDivision() {
+ return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
}
private int calculateXPosition(int touchX) {
@@ -290,7 +301,7 @@
return mStartPosition + touchY - mStartY;
}
- private void resizeStack(int position) {
+ public void resizeStack(int position) {
mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
switch (mDockSide) {
case WindowManager.DOCKED_LEFT:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
new file mode 100644
index 0000000..0466c14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.RecentsComponent;
+import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.BaseStatusBar;
+
+import static android.view.WindowManager.*;
+
+/**
+ * Class to detect gestures on the navigation bar.
+ */
+public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener {
+
+ private static final String DOCK_WINDOW_GESTURE_ENABLED_PROP = "persist.dock_gesture_enabled";
+
+ /**
+ * When dragging from the navigation bar, we drag in recents.
+ */
+ private static final int DRAG_MODE_RECENTS = 0;
+
+ /**
+ * When dragging from the navigation bar, we drag the divider.
+ */
+ private static final int DRAG_MODE_DIVIDER = 1;
+
+ private RecentsComponent mRecentsComponent;
+ private Divider mDivider;
+ private boolean mIsVertical;
+ private boolean mIsRTL;
+
+ private final GestureDetector mTaskSwitcherDetector;
+ private final int mScrollTouchSlop;
+ private final int mTouchSlop;
+ private final int mMinFlingVelocity;
+ private int mTouchDownX;
+ private int mTouchDownY;
+ private VelocityTracker mVelocityTracker;
+
+ private boolean mDockWindowEnabled;
+ private boolean mDockWindowTouchSlopExceeded;
+ private int mDragMode;
+
+ public NavigationBarGestureHelper(Context context) {
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ Resources r = context.getResources();
+ mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+ mTaskSwitcherDetector = new GestureDetector(context, this);
+ mDockWindowEnabled = SystemProperties.getBoolean(DOCK_WINDOW_GESTURE_ENABLED_PROP, false);
+ }
+
+ public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+ mRecentsComponent = recentsComponent;
+ mDivider = divider;
+ }
+
+ public void setBarState(boolean isVertical, boolean isRTL) {
+ mIsVertical = isVertical;
+ mIsRTL = isRTL;
+ }
+
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ // If we move more than a fixed amount, then start capturing for the
+ // task switcher detector
+ mTaskSwitcherDetector.onTouchEvent(event);
+ int action = event.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ mTouchDownX = (int) event.getX();
+ mTouchDownY = (int) event.getY();
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int xDiff = Math.abs(x - mTouchDownX);
+ int yDiff = Math.abs(y - mTouchDownY);
+ boolean exceededTouchSlop = !mIsVertical
+ ? xDiff > mScrollTouchSlop && xDiff > yDiff
+ : yDiff > mScrollTouchSlop && yDiff > xDiff;
+ if (exceededTouchSlop) {
+ return true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ break;
+ }
+ return mDockWindowEnabled && interceptDockWindowEvent(event);
+ }
+
+ private boolean interceptDockWindowEvent(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ handleDragActionDownEvent(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ return handleDragActionMoveEvent(event);
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ handleDragActionUpEvent(event);
+ break;
+ }
+ return false;
+ }
+
+ private boolean handleDockWindowEvent(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ handleDragActionDownEvent(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ handleDragActionMoveEvent(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ handleDragActionUpEvent(event);
+ break;
+ }
+ return true;
+ }
+
+ private void handleDragActionDownEvent(MotionEvent event) {
+ mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker.addMovement(event);
+ mDockWindowTouchSlopExceeded = false;
+ mTouchDownX = (int) event.getX();
+ mTouchDownY = (int) event.getY();
+ }
+
+ private boolean handleDragActionMoveEvent(MotionEvent event) {
+ mVelocityTracker.addMovement(event);
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int xDiff = Math.abs(x - mTouchDownX);
+ int yDiff = Math.abs(y - mTouchDownY);
+ if (!mDockWindowTouchSlopExceeded) {
+ boolean touchSlopExceeded = !mIsVertical
+ ? yDiff > mTouchSlop && yDiff > xDiff
+ : xDiff > mTouchSlop && xDiff > yDiff;
+ if (touchSlopExceeded && mDivider.getView().getWindowManagerProxy().getDockSide()
+ == DOCKED_INVALID) {
+ mDragMode = calculateDragMode();
+ mRecentsComponent.dockTopTask(mDragMode == DRAG_MODE_RECENTS);
+ if (mDragMode == DRAG_MODE_DIVIDER) {
+ mDivider.getView().startDragging();
+ }
+ mDockWindowTouchSlopExceeded = true;
+ return true;
+ }
+ } else {
+ if (mDragMode == DRAG_MODE_DIVIDER) {
+ mDivider.getView().resizeStack(
+ !mIsVertical ? (int) event.getRawY() : (int) event.getRawX());
+ } else if (mDragMode == DRAG_MODE_RECENTS) {
+ mRecentsComponent.onDraggingInRecents(event.getRawY());
+ }
+ }
+ return false;
+ }
+
+ private void handleDragActionUpEvent(MotionEvent event) {
+ mVelocityTracker.addMovement(event);
+ mVelocityTracker.computeCurrentVelocity(1000);
+ if (mDockWindowTouchSlopExceeded) {
+ if (mDragMode == DRAG_MODE_DIVIDER) {
+ mDivider.getView().stopDragging(!mIsVertical
+ ? (int) event.getRawY()
+ : (int) event.getRawX(),
+ !mIsVertical
+ ? mVelocityTracker.getXVelocity()
+ : mVelocityTracker.getYVelocity());
+ } else if (mDragMode == DRAG_MODE_RECENTS) {
+ mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity());
+ }
+ }
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+
+ private int calculateDragMode() {
+ if (mIsVertical && !mDivider.getView().isHorizontalDivision()) {
+ return DRAG_MODE_DIVIDER;
+ }
+ if (!mIsVertical && mDivider.getView().isHorizontalDivision()) {
+ return DRAG_MODE_DIVIDER;
+ }
+ return DRAG_MODE_RECENTS;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean result = mTaskSwitcherDetector.onTouchEvent(event);
+ if (mDockWindowEnabled) {
+ result |= handleDockWindowEvent(event);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ float absVelX = Math.abs(velocityX);
+ float absVelY = Math.abs(velocityY);
+ boolean isValidFling = absVelX > mMinFlingVelocity &&
+ mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
+ if (isValidFling) {
+ boolean showNext;
+ if (!mIsRTL) {
+ showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
+ } else {
+ // In RTL, vertical is still the same, but horizontal is flipped
+ showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0);
+ }
+ if (showNext) {
+ mRecentsComponent.showNextAffiliatedTask();
+ } else {
+ mRecentsComponent.showPrevAffiliatedTask();
+ }
+ }
+ return true;
+ }
+}
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 33e514d..e1aec6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -40,7 +40,6 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
@@ -48,6 +47,8 @@
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.RecentsComponent;
+import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonView;
@@ -78,7 +79,7 @@
private Drawable mRecentIcon;
private Drawable mRecentLandIcon;
- private NavigationBarViewTaskSwitchHelper mTaskSwitchHelper;
+ private NavigationBarGestureHelper mGestureHelper;
private DeadZone mDeadZone;
private final NavigationBarTransitions mBarTransitions;
@@ -180,7 +181,7 @@
mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
mVertical = false;
mShowMenu = false;
- mTaskSwitchHelper = new NavigationBarViewTaskSwitchHelper(context);
+ mGestureHelper = new NavigationBarGestureHelper(context);
getIcons(res);
@@ -191,8 +192,8 @@
return mBarTransitions;
}
- public void setBar(PhoneStatusBar phoneStatusBar) {
- mTaskSwitchHelper.setBar(phoneStatusBar);
+ public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+ mGestureHelper.setComponents(recentsComponent, divider);
}
public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
@@ -202,7 +203,7 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mTaskSwitchHelper.onTouchEvent(event)) {
+ if (mGestureHelper.onTouchEvent(event)) {
return true;
}
if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
@@ -213,7 +214,7 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- return mTaskSwitchHelper.onInterceptTouchEvent(event);
+ return mGestureHelper.onInterceptTouchEvent(event);
}
public void abortCurrentGesture() {
@@ -488,7 +489,7 @@
private void updateTaskSwitchHelper() {
boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
- mTaskSwitchHelper.setBarState(mVertical, isRtl);
+ mGestureHelper.setBarState(mVertical, isRtl);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java
deleted file mode 100644
index fdfcdfb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-
-public class NavigationBarViewTaskSwitchHelper extends GestureDetector.SimpleOnGestureListener {
-
- private BaseStatusBar mBar;
- private boolean mIsVertical;
- private boolean mIsRTL;
-
- private final GestureDetector mTaskSwitcherDetector;
- private final int mScrollTouchSlop;
- private final int mMinFlingVelocity;
- private int mTouchDownX;
- private int mTouchDownY;
-
- public NavigationBarViewTaskSwitchHelper(Context context) {
- ViewConfiguration configuration = ViewConfiguration.get(context);
- Resources r = context.getResources();
- mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
- mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
- mTaskSwitcherDetector = new GestureDetector(context, this);
- }
-
- public void setBar(BaseStatusBar phoneStatusBar) {
- mBar = phoneStatusBar;
- }
-
- public void setBarState(boolean isVertical, boolean isRTL) {
- mIsVertical = isVertical;
- mIsRTL = isRTL;
- }
-
- public boolean onInterceptTouchEvent(MotionEvent event) {
- // If we move more than a fixed amount, then start capturing for the
- // task switcher detector
- mTaskSwitcherDetector.onTouchEvent(event);
- int action = event.getAction();
- boolean intercepted = false;
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- mTouchDownX = (int) event.getX();
- mTouchDownY = (int) event.getY();
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- int x = (int) event.getX();
- int y = (int) event.getY();
- int xDiff = Math.abs(x - mTouchDownX);
- int yDiff = Math.abs(y - mTouchDownY);
- boolean exceededTouchSlop = !mIsVertical
- ? xDiff > mScrollTouchSlop && xDiff > yDiff
- : yDiff > mScrollTouchSlop && yDiff > xDiff;
- if (exceededTouchSlop) {
- return true;
- }
- break;
- }
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- break;
- }
- return intercepted;
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- return mTaskSwitcherDetector.onTouchEvent(event);
- }
-
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- float absVelX = Math.abs(velocityX);
- float absVelY = Math.abs(velocityY);
- boolean isValidFling = absVelX > mMinFlingVelocity &&
- mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
- if (isValidFling) {
- boolean showNext;
- if (!mIsRTL) {
- showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
- } else {
- // In RTL, vertical is still the same, but horizontal is flipped
- showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0);
- }
- if (showNext) {
- mBar.showNextAffiliatedTask();
- } else {
- mBar.showPreviousAffiliatedTask();
- }
- }
- return true;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 51bcf0c..781d651 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -117,6 +117,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.BaseStatusBar;
@@ -739,7 +740,7 @@
context, R.layout.navigation_bar, null);
}
mNavigationBarView.setDisabledFlags(mDisabled1);
- mNavigationBarView.setBar(this);
+ mNavigationBarView.setComponents(mRecents, getComponent(Divider.class));
mNavigationBarView.setOnVerticalChangedListener(
new NavigationBarView.OnVerticalChangedListener() {
@Override
@@ -1135,7 +1136,7 @@
@Override
public boolean onLongClick(View v) {
if (mRecents != null) {
- mRecents.dockTopTask();
+ mRecents.dockTopTask(false /* draggingInRecents */);
return true;
}
return false;
diff --git a/preloaded-classes b/preloaded-classes
index e48255c..79c0957 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -3560,11 +3560,6 @@
org.apache.harmony.security.asn1.BerOutputStream
org.apache.harmony.security.asn1.DerInputStream
org.apache.harmony.security.asn1.DerOutputStream
-org.apache.harmony.security.fortress.Engine
-org.apache.harmony.security.fortress.Engine$ServiceCacheEntry
-org.apache.harmony.security.fortress.Engine$SpiAndProvider
-org.apache.harmony.security.fortress.SecurityAccess
-org.apache.harmony.security.fortress.Services
org.apache.harmony.security.provider.crypto.CryptoProvider
org.apache.harmony.security.utils.AlgNameMapper
org.apache.harmony.security.utils.AlgNameMapperSource
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9f1dc0a..3d358ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -64,21 +64,17 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
-import android.util.Pools.Pool;
-import android.util.Pools.SimplePool;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
import android.view.InputDevice;
-import android.view.InputEventConsistencyVerifier;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.WindowManagerInternal;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
@@ -146,8 +142,6 @@
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
- private static final int MAX_POOL_SIZE = 10;
-
private static final int WINDOW_ID_UNKNOWN = -1;
private static int sIdCounter = 0;
@@ -158,9 +152,6 @@
private final Object mLock = new Object();
- private final Pool<PendingEvent> mPendingEventPool =
- new SimplePool<>(MAX_POOL_SIZE);
-
private final SimpleStringSplitter mStringColonSplitter =
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
@@ -193,6 +184,8 @@
private boolean mHasInputFilter;
+ private KeyEventDispatcher mKeyEventDispatcher;
+
private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
@@ -756,12 +749,11 @@
boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
synchronized (mLock) {
- KeyEvent localClone = KeyEvent.obtain(event);
- boolean handled = notifyKeyEventLocked(localClone, policyFlags, false);
- if (!handled) {
- handled = notifyKeyEventLocked(localClone, policyFlags, true);
+ List<Service> boundServices = getCurrentUserStateLocked().mBoundServices;
+ if (boundServices.isEmpty()) {
+ return false;
}
- return handled;
+ return getKeyEventDispatcher().notifyKeyEventLocked(event, policyFlags, boundServices);
}
}
@@ -935,31 +927,6 @@
return false;
}
- private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) {
- // TODO: Now we are giving the key events to the last enabled
- // service that can handle them Ideally, the user should
- // make the call which service handles key events. However,
- // only one service should handle key events to avoid user
- // frustration when different behavior is observed from
- // different combinations of enabled accessibility services.
- UserState state = getCurrentUserStateLocked();
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- Service service = state.mBoundServices.get(i);
- // Key events are handled only by services that declared
- // this capability and requested to filter key events.
- if (!service.mRequestFilterKeyEvents ||
- (service.mAccessibilityServiceInfo.getCapabilities() & AccessibilityServiceInfo
- .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
- continue;
- }
- if (service.mIsDefault == isDefault) {
- service.notifyKeyEvent(event, policyFlags);
- return true;
- }
- }
- return false;
- }
-
private void notifyClearAccessibilityCacheLocked() {
UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
@@ -1754,6 +1721,14 @@
return null;
}
+ private KeyEventDispatcher getKeyEventDispatcher() {
+ if (mKeyEventDispatcher == null) {
+ mKeyEventDispatcher = new KeyEventDispatcher(
+ mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock);
+ }
+ return mKeyEventDispatcher;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
@@ -1954,22 +1929,6 @@
}
}
- private PendingEvent obtainPendingEventLocked(KeyEvent event, int policyFlags, int sequence) {
- PendingEvent pendingEvent = mPendingEventPool.acquire();
- if (pendingEvent == null) {
- pendingEvent = new PendingEvent();
- }
- pendingEvent.event = event;
- pendingEvent.policyFlags = policyFlags;
- pendingEvent.sequence = sequence;
- return pendingEvent;
- }
-
- private void recyclePendingEventLocked(PendingEvent pendingEvent) {
- pendingEvent.clear();
- mPendingEventPool.release(pendingEvent);
- }
-
private int findWindowIdLocked(IBinder token) {
final int globalIndex = mGlobalWindowTokens.indexOfValue(token);
if (globalIndex >= 0) {
@@ -2082,8 +2041,6 @@
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<>();
- final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher();
-
boolean mWasConnectedAndDied;
// Handler only for dispatching accessibility events since we use event
@@ -2195,7 +2152,7 @@
return false;
}
UserState userState = getUserStateLocked(mUserId);
- mKeyEventDispatcher.flush();
+ getKeyEventDispatcher().flush(this);
if (!mIsAutomation) {
mContext.unbindService(this);
} else {
@@ -2212,7 +2169,7 @@
@Override
public void setOnKeyEventResult(boolean handled, int sequence) {
- mKeyEventDispatcher.setOnKeyEventResult(handled, sequence);
+ getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
}
@Override
@@ -2926,7 +2883,7 @@
return;
}
mWasConnectedAndDied = true;
- mKeyEventDispatcher.flush();
+ getKeyEventDispatcher().flush(this);
UserState userState = getUserStateLocked(mUserId);
// The death recipient is unregistered in removeServiceLocked
removeServiceLocked(this, userState);
@@ -3035,11 +2992,6 @@
gestureId, 0).sendToTarget();
}
- public void notifyKeyEvent(KeyEvent event, int policyFlags) {
- mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_KEY_EVENT,
- policyFlags, 0, event).sendToTarget();
- }
-
public void notifyClearAccessibilityNodeInfoCache() {
mInvocationHandler.sendEmptyMessage(
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
@@ -3084,10 +3036,6 @@
}
}
- private void notifyKeyEventInternal(KeyEvent event, int policyFlags) {
- mKeyEventDispatcher.notifyKeyEvent(event, policyFlags);
- }
-
private void notifyClearAccessibilityCacheInternal() {
final IAccessibilityServiceClient listener;
synchronized (mLock) {
@@ -3205,9 +3153,7 @@
private final class InvocationHandler extends Handler {
public static final int MSG_ON_GESTURE = 1;
- public static final int MSG_ON_KEY_EVENT = 2;
- public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3;
- public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
+ public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
@@ -3226,21 +3172,10 @@
notifyGestureInternal(gestureId);
} break;
- case MSG_ON_KEY_EVENT: {
- KeyEvent event = (KeyEvent) message.obj;
- final int policyFlags = message.arg1;
- notifyKeyEventInternal(event, policyFlags);
- } break;
-
case MSG_CLEAR_ACCESSIBILITY_CACHE: {
notifyClearAccessibilityCacheInternal();
} break;
- case MSG_ON_KEY_EVENT_TIMEOUT: {
- PendingEvent eventState = (PendingEvent) message.obj;
- setOnKeyEventResult(false, eventState.sequence);
- } break;
-
case MSG_ON_MAGNIFICATION_CHANGED: {
final SomeArgs args = (SomeArgs) message.obj;
final Region region = (Region) args.arg1;
@@ -3278,140 +3213,6 @@
}
}
- private final class KeyEventDispatcher {
-
- private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
-
- private PendingEvent mPendingEvents;
-
- private final InputEventConsistencyVerifier mSentEventsVerifier =
- InputEventConsistencyVerifier.isInstrumentationEnabled()
- ? new InputEventConsistencyVerifier(
- this, 0, KeyEventDispatcher.class.getSimpleName()) : null;
-
- public void notifyKeyEvent(KeyEvent event, int policyFlags) {
- final PendingEvent pendingEvent;
-
- synchronized (mLock) {
- pendingEvent = addPendingEventLocked(event, policyFlags);
- }
-
- Message message = mInvocationHandler.obtainMessage(
- InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
- mInvocationHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
-
- try {
- // Accessibility services are exclusively not in the system
- // process, therefore no need to clone the motion event to
- // prevent tampering. It will be cloned in the IPC call.
- mServiceInterface.onKeyEvent(pendingEvent.event, pendingEvent.sequence);
- } catch (RemoteException re) {
- setOnKeyEventResult(false, pendingEvent.sequence);
- }
- }
-
- public void setOnKeyEventResult(boolean handled, int sequence) {
- synchronized (mLock) {
- PendingEvent pendingEvent = removePendingEventLocked(sequence);
- if (pendingEvent != null) {
- mInvocationHandler.removeMessages(
- InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
- pendingEvent);
- pendingEvent.handled = handled;
- finishPendingEventLocked(pendingEvent);
- }
- }
- }
-
- public void flush() {
- synchronized (mLock) {
- cancelAllPendingEventsLocked();
- if (mSentEventsVerifier != null) {
- mSentEventsVerifier.reset();
- }
- }
- }
-
- private PendingEvent addPendingEventLocked(KeyEvent event, int policyFlags) {
- final int sequence = event.getSequenceNumber();
- PendingEvent pendingEvent = obtainPendingEventLocked(event, policyFlags, sequence);
- pendingEvent.next = mPendingEvents;
- mPendingEvents = pendingEvent;
- return pendingEvent;
- }
-
- private PendingEvent removePendingEventLocked(int sequence) {
- PendingEvent previous = null;
- PendingEvent current = mPendingEvents;
-
- while (current != null) {
- if (current.sequence == sequence) {
- if (previous != null) {
- previous.next = current.next;
- } else {
- mPendingEvents = current.next;
- }
- current.next = null;
- return current;
- }
- previous = current;
- current = current.next;
- }
- return null;
- }
-
- private void finishPendingEventLocked(PendingEvent pendingEvent) {
- if (!pendingEvent.handled) {
- sendKeyEventToInputFilter(pendingEvent.event, pendingEvent.policyFlags);
- }
- // Nullify the event since we do not want it to be
- // recycled yet. It will be sent to the input filter.
- pendingEvent.event = null;
- recyclePendingEventLocked(pendingEvent);
- }
-
- private void sendKeyEventToInputFilter(KeyEvent event, int policyFlags) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Injecting event: " + event);
- }
- if (mSentEventsVerifier != null) {
- mSentEventsVerifier.onKeyEvent(event, 0);
- }
- policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
- mMainHandler.obtainMessage(MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER,
- policyFlags, 0, event).sendToTarget();
- }
-
- private void cancelAllPendingEventsLocked() {
- while (mPendingEvents != null) {
- PendingEvent pendingEvent = removePendingEventLocked(mPendingEvents.sequence);
- pendingEvent.handled = false;
- mInvocationHandler.removeMessages(InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
- pendingEvent);
- finishPendingEventLocked(pendingEvent);
- }
- }
- }
- }
-
- private static final class PendingEvent {
- PendingEvent next;
-
- KeyEvent event;
- int policyFlags;
- int sequence;
- boolean handled;
-
- public void clear() {
- if (event != null) {
- event.recycle();
- event = null;
- }
- next = null;
- policyFlags = 0;
- sequence = 0;
- handled = false;
- }
}
final class WindowsForAccessibilityCallback implements
diff --git a/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java
new file mode 100644
index 0000000..3469565
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java
@@ -0,0 +1,285 @@
+/*
+ ** Copyright 2015, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Pools;
+import android.util.Pools.Pool;
+import android.util.Slog;
+import android.view.InputEventConsistencyVerifier;
+import android.view.KeyEvent;
+import android.view.WindowManagerPolicy;
+
+import com.android.server.accessibility.AccessibilityManagerService.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Dispatcher to send KeyEvents to all accessibility services that are able to process them.
+ * Events that are handled by one or more services are consumed. Events that are not processed
+ * by any service (or time out before a service reports them as handled) are passed along to
+ * the rest of the system.
+ *
+ * The class assumes that services report their return values in order, which is valid because
+ * they process each call to {@code AccessibilityService.onKeyEvent} on a single thread, and so
+ * don't see the N+1th event until they have processed the Nth event.
+ */
+public class KeyEventDispatcher {
+ // Debugging
+ private static final String LOG_TAG = "KeyEventDispatcher";
+ private static final boolean DEBUG = false;
+ /* KeyEvents must be processed in this time interval */
+ private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
+ private static final int MSG_ON_KEY_EVENT_TIMEOUT = 1;
+ private static final int MAX_POOL_SIZE = 10;
+
+ private final Pool<PendingKeyEvent> mPendingEventPool = new Pools.SimplePool<>(MAX_POOL_SIZE);
+ private final Object mLock;
+
+ /*
+ * Track events sent to each service. If a KeyEvent is to be sent to at least one service,
+ * a corresponding PendingKeyEvent is created for it. This PendingKeyEvent is placed in
+ * the list for each service its KeyEvent is sent to. It is removed from the list when
+ * the service calls setOnKeyEventResult, or when we time out waiting for the service to
+ * respond.
+ */
+ private final Map<Service, ArrayList<PendingKeyEvent>> mPendingEventsMap = new ArrayMap<>();
+
+ private final InputEventConsistencyVerifier mSentEventsVerifier;
+ private final Handler mHandlerToSendKeyEventsToInputFilter;
+ private final int mMessageTypeForSendKeyEvent;
+ private final Handler mKeyEventTimeoutHandler;
+
+ /**
+ * @param handlerToSendKeyEventsToInputFilter The handler to which to post {@code KeyEvent}s
+ * that have not been handled by any accessibility service.
+ * @param messageTypeForSendKeyEvent The field to populate {@code message.what} for the
+ * message that carries a {@code KeyEvent} to be sent to the input filter
+ * @param lock The lock used for all synchronization in this package. This lock must be held
+ * when calling {@code notifyKeyEventLocked}
+ */
+ public KeyEventDispatcher(Handler handlerToSendKeyEventsToInputFilter,
+ int messageTypeForSendKeyEvent, Object lock) {
+ if (InputEventConsistencyVerifier.isInstrumentationEnabled()) {
+ mSentEventsVerifier = new InputEventConsistencyVerifier(
+ this, 0, KeyEventDispatcher.class.getSimpleName());
+ } else {
+ mSentEventsVerifier = null;
+ }
+ mHandlerToSendKeyEventsToInputFilter = handlerToSendKeyEventsToInputFilter;
+ mMessageTypeForSendKeyEvent = messageTypeForSendKeyEvent;
+ mKeyEventTimeoutHandler =
+ new Handler(mHandlerToSendKeyEventsToInputFilter.getLooper(), new Callback());
+ mLock = lock;
+ }
+
+ /**
+ * Notify that a new KeyEvent is available to accessibility services. Must be called with the
+ * lock used to construct this object held. The boundServices list must also be protected
+ * by a lock.
+ *
+ * @param event The new key event
+ * @param policyFlags Flags for the event
+ * @param boundServices A list of currently bound AccessibilityServices
+ *
+ * @return {@code true} if the event was passed to at least one AccessibilityService,
+ * {@code false} otherwise.
+ */
+ // TODO: The locking policy for boundServices needs some thought.
+ public boolean notifyKeyEventLocked(
+ KeyEvent event, int policyFlags, List<Service> boundServices) {
+ PendingKeyEvent pendingKeyEvent = null;
+ KeyEvent localClone = KeyEvent.obtain(event);
+ for (int i = 0; i < boundServices.size(); i++) {
+ Service service = boundServices.get(i);
+ // Key events are handled only by services that declared
+ // this capability and requested to filter key events.
+ if (!service.mRequestFilterKeyEvents) {
+ continue;
+ }
+ int filterKeyEventBit = service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+ if (filterKeyEventBit == 0) {
+ continue;
+ }
+
+ try {
+ // The event will be cloned in the IPC call, so it doesn't need to be here.
+ service.mServiceInterface.onKeyEvent(localClone, localClone.getSequenceNumber());
+ } catch (RemoteException re) {
+ continue;
+ }
+
+ if (pendingKeyEvent == null) {
+ pendingKeyEvent = obtainPendingEventLocked(localClone, policyFlags);
+ }
+ ArrayList<PendingKeyEvent> pendingEventList = mPendingEventsMap.get(service);
+ if (pendingEventList == null) {
+ pendingEventList = new ArrayList<>();
+ mPendingEventsMap.put(service, pendingEventList);
+ }
+ pendingEventList.add(pendingKeyEvent);
+ pendingKeyEvent.referenceCount++;
+ }
+
+ if (pendingKeyEvent == null) {
+ localClone.recycle();
+ return false;
+ }
+
+ Message message = mKeyEventTimeoutHandler.obtainMessage(
+ MSG_ON_KEY_EVENT_TIMEOUT, pendingKeyEvent);
+ mKeyEventTimeoutHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
+ return true;
+ }
+
+ /**
+ * Set the result from onKeyEvent from one service.
+ *
+ * @param service The service setting the result
+ * @param handled {@code true} if the service handled the {@code KeyEvent}
+ * @param sequence The sequence number of the {@code KeyEvent}
+ */
+ public void setOnKeyEventResult(Service service, boolean handled, int sequence) {
+ synchronized (mLock) {
+ PendingKeyEvent pendingEvent =
+ removeEventFromListLocked(mPendingEventsMap.get(service), sequence);
+ if (pendingEvent != null) {
+ pendingEvent.handled |= handled;
+ removeReferenceToPendingEventLocked(pendingEvent);
+ }
+ }
+ }
+
+ /**
+ * Flush all pending key events for a service, treating all of them as unhandled
+ *
+ * @param service The service for which to flush events
+ */
+ public void flush(Service service) {
+ synchronized (mLock) {
+ List<PendingKeyEvent> pendingEvents = mPendingEventsMap.get(service);
+ if (pendingEvents != null) {
+ for (int i = 0; i < pendingEvents.size(); i++) {
+ PendingKeyEvent pendingEvent = pendingEvents.get(i);
+ removeReferenceToPendingEventLocked(pendingEvent);
+ }
+ mPendingEventsMap.remove(service);
+ }
+ }
+ }
+
+ private PendingKeyEvent obtainPendingEventLocked(KeyEvent event, int policyFlags) {
+ PendingKeyEvent pendingEvent = mPendingEventPool.acquire();
+ if (pendingEvent == null) {
+ pendingEvent = new PendingKeyEvent();
+ }
+ pendingEvent.event = event;
+ pendingEvent.policyFlags = policyFlags;
+ pendingEvent.referenceCount = 0;
+ pendingEvent.handled = false;
+ return pendingEvent;
+ }
+
+ private static PendingKeyEvent removeEventFromListLocked(
+ List<PendingKeyEvent> listOfEvents, int sequence) {
+ /* In normal operation, the event should be first */
+ for (int i = 0; i < listOfEvents.size(); i++) {
+ PendingKeyEvent pendingKeyEvent = listOfEvents.get(i);
+ if (pendingKeyEvent.event.getSequenceNumber() == sequence) {
+ /*
+ * Removing the first element of the ArrayList can be slow if there are a lot
+ * of events backed up, but for a handful of events it's better than incurring
+ * the fixed overhead of LinkedList. An ArrayList optimized for removing the
+ * first element (by treating the underlying array as a circular buffer) would
+ * be ideal.
+ */
+ listOfEvents.remove(pendingKeyEvent);
+ return pendingKeyEvent;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param pendingEvent The event whose reference count should be decreased
+ * @return {@code true} if the event was release, {@code false} if not.
+ */
+ private boolean removeReferenceToPendingEventLocked(PendingKeyEvent pendingEvent) {
+ if (--pendingEvent.referenceCount > 0) {
+ return false;
+ }
+ mKeyEventTimeoutHandler.removeMessages(MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
+ if (!pendingEvent.handled) {
+ /* Pass event to input filter */
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Injecting event: " + pendingEvent.event);
+ }
+ if (mSentEventsVerifier != null) {
+ mSentEventsVerifier.onKeyEvent(pendingEvent.event, 0);
+ }
+ int policyFlags = pendingEvent.policyFlags | WindowManagerPolicy.FLAG_PASS_TO_USER;
+ mHandlerToSendKeyEventsToInputFilter
+ .obtainMessage(mMessageTypeForSendKeyEvent, policyFlags, 0, pendingEvent.event)
+ .sendToTarget();
+ } else {
+ pendingEvent.event.recycle();
+ }
+ mPendingEventPool.release(pendingEvent);
+ return true;
+ }
+
+ private static final class PendingKeyEvent {
+ /* Event and policyFlag provided in notifyKeyEventLocked */
+ KeyEvent event;
+ int policyFlags;
+ /*
+ * The referenceCount optimizes the process of determining the number of services
+ * still holding a KeyEvent. It must be equal to the number of times the PendingEvent
+ * appears in mPendingEventsMap, or PendingEvents will leak.
+ */
+ int referenceCount;
+ /* Whether or not at least one service had handled this event */
+ boolean handled;
+ }
+
+ private class Callback implements Handler.Callback {
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message.what != MSG_ON_KEY_EVENT_TIMEOUT) {
+ throw new IllegalArgumentException("Unknown message: " + message.what);
+ }
+ PendingKeyEvent pendingKeyEvent = (PendingKeyEvent) message.obj;
+ synchronized (mLock) {
+ for (ArrayList<PendingKeyEvent> listForService : mPendingEventsMap.values()) {
+ if (listForService.remove(pendingKeyEvent)) {
+ if(removeReferenceToPendingEventLocked(pendingKeyEvent)) {
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3a0d80b..29173a5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -28,6 +28,7 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.ProcessStats;
+import com.android.internal.app.SystemUserHomeActivity;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.IResultReceiver;
@@ -192,6 +193,7 @@
import android.util.AtomicFile;
import android.util.DebugUtils;
import android.util.EventLog;
+import android.util.LocaleList;
import android.util.Log;
import android.util.Pair;
import android.util.PrintWriterPrinter;
@@ -2445,7 +2447,7 @@
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
mConfiguration.setToDefaults();
- mConfiguration.setLocale(Locale.getDefault());
+ mConfiguration.setLocales(LocaleList.getDefault());
mConfigurationSeq = mConfiguration.seq = 1;
mProcessCpuTracker.init();
@@ -12268,6 +12270,17 @@
// Start up initial activity.
mBooting = true;
+ // Enable home activity for system user, so that the system can always boot
+ if (UserManager.isSplitSystemUser()) {
+ ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
+ try {
+ AppGlobals.getPackageManager().setComponentEnabledSetting(cName,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,
+ UserHandle.USER_SYSTEM);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
startHomeActivityLocked(currentUserId, "systemReady");
try {
@@ -15455,6 +15468,10 @@
pw.print(" Lost RAM: "); pw.println(stringifyKBSize(memInfo.getTotalSizeKb()
- totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
- memInfo.getKernelUsedSizeKb()));
+ } else {
+ pw.print("lostram,"); pw.println(memInfo.getTotalSizeKb()
+ - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
+ - memInfo.getKernelUsedSizeKb());
}
if (!brief) {
if (memInfo.getZramTotalSizeKb() != 0) {
@@ -17709,6 +17726,9 @@
UserHandle.USER_NULL);
}
+ // To cache the list of supported system locales
+ private String[] mSupportedSystemLocales = null;
+
/**
* Do either or both things: (1) change the current configuration, and (2)
* make sure the given activity is running with the (now) current
@@ -17732,11 +17752,14 @@
EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
- if (!initLocale && values.locale != null && values.userSetLocale) {
- final String languageTag = values.locale.toLanguageTag();
- SystemProperties.set("persist.sys.locale", languageTag);
+ if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
+ if (mSupportedSystemLocales == null) {
+ mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
+ }
+ final Locale locale = values.getLocales().getBestMatch(mSupportedSystemLocales);
+ SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
- values.locale));
+ locale));
}
mConfigurationSeq++;
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 3fc6846..02cf87a 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1277,7 +1277,7 @@
}
final Configuration serviceConfig = mService.mConfiguration;
- mOverrideConfig = new Configuration(serviceConfig);
+ mOverrideConfig = new Configuration(Configuration.EMPTY);
// TODO(multidisplay): Update Dp to that of display stack is on.
final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
mOverrideConfig.screenWidthDp =
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e04f138..6d8bd1c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -522,6 +522,9 @@
}
if (uss.mState == UserState.STATE_BOOTING) {
+ // Let user manager propagate user restrictions to other services.
+ getUserManager().onBeforeStartUser(userId);
+
// Booting up a new user, need to tell system services about it.
// Note that this is on the same handler as scheduling of broadcasts,
// which is important because it needs to go first.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4424838..946fbb1 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1211,29 +1211,36 @@
}
@Override
- public void setPackagePriority(String pkg, int uid, int priority) {
+ public ParceledListSlice<Notification.Topic> getTopics(String pkg, int uid) {
checkCallerIsSystem();
- mRankingHelper.setPackagePriority(pkg, uid, priority);
+ return new ParceledListSlice<Notification.Topic>(mRankingHelper.getTopics(pkg, uid));
+ }
+
+ @Override
+ public void setTopicPriority(String pkg, int uid, Notification.Topic topic, int priority) {
+ checkCallerIsSystem();
+ mRankingHelper.setTopicPriority(pkg, uid, topic, priority);
savePolicyFile();
}
@Override
- public int getPackagePriority(String pkg, int uid) {
+ public int getTopicPriority(String pkg, int uid, Notification.Topic topic) {
checkCallerIsSystem();
- return mRankingHelper.getPackagePriority(pkg, uid);
+ return mRankingHelper.getTopicPriority(pkg, uid, topic);
}
@Override
- public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
+ public void setTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic,
+ int visibility) {
checkCallerIsSystem();
- mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
+ mRankingHelper.setTopicVisibilityOverride(pkg, uid, topic, visibility);
savePolicyFile();
}
@Override
- public int getPackageVisibilityOverride(String pkg, int uid) {
+ public int getTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic) {
checkCallerIsSystem();
- return mRankingHelper.getPackageVisibilityOverride(pkg, uid);
+ return mRankingHelper.getTopicVisibilityOverride(pkg, uid, topic);
}
/**
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index aea137b..7ee29e4 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -15,12 +15,20 @@
*/
package com.android.server.notification;
+import android.app.Notification;
+
+import java.util.List;
+
public interface RankingConfig {
- int getPackagePriority(String packageName, int uid);
- void setPackagePriority(String packageName, int uid, int priority);
+ List<Notification.Topic> getTopics(String packageName, int uid);
- int getPackageVisibilityOverride(String packageName, int uid);
+ int getTopicPriority(String packageName, int uid, Notification.Topic topic);
- void setPackageVisibilityOverride(String packageName, int uid, int visibility);
+ void setTopicPriority(String packageName, int uid, Notification.Topic topic, int priority);
+
+ int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic);
+
+ void setTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic,
+ int visibility);
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index f8b661f..4d33248 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -27,6 +27,8 @@
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -35,6 +37,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
public class RankingHelper implements RankingConfig {
@@ -45,11 +49,14 @@
private static final String TAG_RANKING = "ranking";
private static final String TAG_PACKAGE = "package";
private static final String ATT_VERSION = "version";
+ private static final String TAG_TOPIC = "topic";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
private static final String ATT_PRIORITY = "priority";
private static final String ATT_VISIBILITY = "visibility";
+ private static final String ATT_TOPIC_ID = "id";
+ private static final String ATT_TOPIC_LABEL = "label";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY =
@@ -161,12 +168,14 @@
} else {
r = getOrCreateRecord(name, uid);
}
- if (priority != DEFAULT_PRIORITY) {
- r.priority = priority;
- }
- if (vis != DEFAULT_VISIBILITY) {
- r.visibility = vis;
- }
+
+ // Migrate package level settings to the default topic.
+ // Might be overwritten by parseTopics.
+ Topic defaultTopic = r.topics.get(Notification.TOPIC_DEFAULT);
+ defaultTopic.priority = priority;
+ defaultTopic.visibility = vis;
+
+ parseTopics(r, parser);
}
}
}
@@ -174,6 +183,38 @@
throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
+ public void parseTopics(Record r, XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ final int innerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (TAG_TOPIC.equals(tagName)) {
+ int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+ int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+ String id = parser.getAttributeValue(null, ATT_TOPIC_ID);
+ CharSequence label = parser.getAttributeValue(null, ATT_TOPIC_LABEL);
+
+ if (!TextUtils.isEmpty(id)) {
+ Topic topic = new Topic(new Notification.Topic(id, label));
+
+ if (priority != DEFAULT_PRIORITY) {
+ topic.priority = priority;
+ }
+ if (vis != DEFAULT_VISIBILITY) {
+ topic.visibility = vis;
+ }
+ r.topics.put(id, topic);
+ }
+ }
+ }
+ }
+
private static String recordKey(String pkg, int uid) {
return pkg + "|" + uid;
}
@@ -185,21 +226,14 @@
r = new Record();
r.pkg = pkg;
r.uid = uid;
+ r.topics.put(Notification.TOPIC_DEFAULT,
+ new Topic(new Notification.Topic(Notification.TOPIC_DEFAULT,
+ mContext.getString(R.string.default_notification_topic_label))));
mRecords.put(key, r);
}
return r;
}
- private void removeDefaultRecords() {
- final int N = mRecords.size();
- for (int i = N - 1; i >= 0; i--) {
- final Record r = mRecords.valueAt(i);
- if (r.priority == DEFAULT_PRIORITY && r.visibility == DEFAULT_VISIBILITY) {
- mRecords.remove(i);
- }
- }
- }
-
public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
out.startTag(null, TAG_RANKING);
out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
@@ -213,20 +247,32 @@
}
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
- if (r.priority != DEFAULT_PRIORITY) {
- out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
- }
+
if (!forBackup) {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
}
+
+ writeTopicsXml(out, r);
out.endTag(null, TAG_PACKAGE);
}
out.endTag(null, TAG_RANKING);
}
+ public void writeTopicsXml(XmlSerializer out, Record r) throws IOException {
+ for (Topic t : r.topics.values()) {
+ out.startTag(null, TAG_TOPIC);
+ out.attribute(null, ATT_TOPIC_ID, t.topic.getId());
+ out.attribute(null, ATT_TOPIC_LABEL, t.topic.getLabel().toString());
+ if (t.priority != DEFAULT_PRIORITY) {
+ out.attribute(null, ATT_PRIORITY, Integer.toString(t.priority));
+ }
+ if (t.visibility != DEFAULT_VISIBILITY) {
+ out.attribute(null, ATT_VISIBILITY, Integer.toString(t.visibility));
+ }
+ out.endTag(null, TAG_TOPIC);
+ }
+ }
+
private void updateConfig() {
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
@@ -322,37 +368,54 @@
}
@Override
- public int getPackagePriority(String packageName, int uid) {
- final Record r = mRecords.get(recordKey(packageName, uid));
- return r != null ? r.priority : DEFAULT_PRIORITY;
+ public List<Notification.Topic> getTopics(String packageName, int uid) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ List<Notification.Topic> topics = new ArrayList<>();
+ for (Topic t : r.topics.values()) {
+ topics.add(t.topic);
+ }
+ return topics;
}
@Override
- public void setPackagePriority(String packageName, int uid, int priority) {
- if (priority == getPackagePriority(packageName, uid)) {
- return;
- }
- getOrCreateRecord(packageName, uid).priority = priority;
- removeDefaultRecords();
+ public int getTopicPriority(String packageName, int uid, Notification.Topic topic) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ return getOrCreateTopic(r, topic).priority;
+ }
+
+ @Override
+ public void setTopicPriority(String packageName, int uid, Notification.Topic topic,
+ int priority) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ getOrCreateTopic(r, topic).priority = priority;
updateConfig();
}
@Override
- public int getPackageVisibilityOverride(String packageName, int uid) {
- final Record r = mRecords.get(recordKey(packageName, uid));
- return r != null ? r.visibility : DEFAULT_VISIBILITY;
+ public int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ return getOrCreateTopic(r, topic).visibility;
}
@Override
- public void setPackageVisibilityOverride(String packageName, int uid, int visibility) {
- if (visibility == getPackageVisibilityOverride(packageName, uid)) {
- return;
- }
- getOrCreateRecord(packageName, uid).visibility = visibility;
- removeDefaultRecords();
+ public void setTopicVisibilityOverride(String pkgName, int uid, Notification.Topic topic,
+ int visibility) {
+ final Record r = getOrCreateRecord(pkgName, uid);
+ getOrCreateTopic(r, topic).visibility = visibility;
updateConfig();
}
+ private Topic getOrCreateTopic(Record r, Notification.Topic topic) {
+ Topic t = r.topics.get(topic.getId());
+ if (t != null) {
+ return t;
+ } else {
+ t = new Topic(topic);
+ r.topics.put(topic.getId(), t);
+ return t;
+ }
+ }
+
public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
if (filter == null) {
final int N = mSignalExtractors.length;
@@ -385,15 +448,22 @@
pw.print(" (");
pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
pw.print(')');
- if (r.priority != DEFAULT_PRIORITY) {
- pw.print(" priority=");
- pw.print(Notification.priorityToString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- pw.print(" visibility=");
- pw.print(Notification.visibilityToString(r.visibility));
- }
pw.println();
+ for (Topic t : r.topics.values()) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(" ");
+ pw.print(t.topic.getId());
+ if (t.priority != DEFAULT_PRIORITY) {
+ pw.print(" priority=");
+ pw.print(Notification.priorityToString(t.priority));
+ }
+ if (t.visibility != DEFAULT_VISIBILITY) {
+ pw.print(" visibility=");
+ pw.print(Notification.visibilityToString(t.visibility));
+ }
+ pw.println();
+ }
}
}
}
@@ -429,8 +499,16 @@
String pkg;
int uid = UNKNOWN_UID;
+ Map<String, Topic> topics = new ArrayMap<>();
+ }
+
+ private static class Topic {
+ Notification.Topic topic;
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
- }
+ public Topic(Notification.Topic topic) {
+ this.topic = topic;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java b/services/core/java/com/android/server/notification/TopicPriorityExtractor.java
similarity index 79%
rename from services/core/java/com/android/server/notification/PackagePriorityExtractor.java
rename to services/core/java/com/android/server/notification/TopicPriorityExtractor.java
index 6beed9c..5bf989ae 100644
--- a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java
+++ b/services/core/java/com/android/server/notification/TopicPriorityExtractor.java
@@ -18,8 +18,11 @@
import android.content.Context;
import android.util.Slog;
-public class PackagePriorityExtractor implements NotificationSignalExtractor {
- private static final String TAG = "ImportantPackageExtractor";
+/**
+ * Determines if the given notification can bypass Do Not Disturb.
+ */
+public class TopicPriorityExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "ImportantTopicExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
@@ -39,8 +42,8 @@
return null;
}
- final int packagePriority = mConfig.getPackagePriority(
- record.sbn.getPackageName(), record.sbn.getUid());
+ final int packagePriority = mConfig.getTopicPriority(record.sbn.getPackageName(),
+ record.sbn.getUid(), record.sbn.getNotification().getTopic());
record.setPackagePriority(packagePriority);
return null;
diff --git a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java b/services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
similarity index 80%
rename from services/core/java/com/android/server/notification/PackageVisibilityExtractor.java
rename to services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
index af99db7..e053382 100644
--- a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java
+++ b/services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
@@ -18,8 +18,11 @@
import android.content.Context;
import android.util.Slog;
-public class PackageVisibilityExtractor implements NotificationSignalExtractor {
- private static final String TAG = "PackageVisibilityExtractor";
+/**
+ * Determines if the given notification can display sensitive content on the lockscreen.
+ */
+public class TopicVisibilityExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "TopicVisibilityExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
@@ -39,8 +42,9 @@
return null;
}
- final int packageVisibility = mConfig.getPackageVisibilityOverride(
- record.sbn.getPackageName(), record.sbn.getUid());
+ final int packageVisibility = mConfig.getTopicVisibilityOverride(
+ record.sbn.getPackageName(), record.sbn.getUid(),
+ record.sbn.getNotification().getTopic());
record.setPackageVisibilityOverride(packageVisibility);
return null;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index baeccb4..b3c40d3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,7 +25,6 @@
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.admin.DevicePolicyManager;
-import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -36,6 +35,7 @@
import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
@@ -48,7 +48,6 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCommand;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
@@ -104,7 +103,8 @@
*/
public class UserManagerService extends IUserManager.Stub {
private static final String LOG_TAG = "UserManagerService";
- private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+ static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+ private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG_NAME = "name";
private static final String ATTR_FLAGS = "flags";
@@ -123,6 +123,7 @@
private static final String TAG_USERS = "users";
private static final String TAG_USER = "user";
private static final String TAG_RESTRICTIONS = "restrictions";
+ private static final String TAG_DEVICE_POLICY_RESTRICTIONS = "device_policy_restrictions";
private static final String TAG_ENTRY = "entry";
private static final String TAG_VALUE = "value";
private static final String ATTR_KEY = "key";
@@ -206,13 +207,27 @@
private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
/**
- * User restrictions that have already been applied in {@link #applyUserRestrictionsLR}. We
- * use it to detect restrictions that have changed since the last
- * {@link #applyUserRestrictionsLR} call.
+ * User restrictions that have already been applied in
+ * {@link #updateUserRestrictionsInternalLR(Bundle, int)}. We use it to detect restrictions
+ * that have changed since the last
+ * {@link #updateUserRestrictionsInternalLR(Bundle, int)} call.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+ /**
+ * User restrictions set by {@link DevicePolicyManager} that should be applied to all users,
+ * including guests.
+ */
+ @GuardedBy("mRestrictionsLock")
+ private Bundle mDevicePolicyGlobalUserRestrictions;
+
+ /**
+ * User restrictions set by {@link DevicePolicyManager} for each user.
+ */
+ @GuardedBy("mRestrictionsLock")
+ private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
+
@GuardedBy("mGuestRestrictions")
private final Bundle mGuestRestrictions = new Bundle();
@@ -302,16 +317,14 @@
+ " (name=" + ui.name + ")");
removeUserState(ui.id);
}
+
onUserForeground(UserHandle.USER_SYSTEM);
+
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
- for (int i = 0; i < mUserIds.length; ++i) {
- final int userId = mUserIds[i];
- try {
- mAppOpsService.setUserRestrictions(getEffectiveUserRestrictions(userId), userId);
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
- }
+
+ synchronized (mRestrictionsLock) {
+ applyUserRestrictionsLR(UserHandle.USER_SYSTEM);
}
}
@@ -661,19 +674,72 @@
}
}
+ /**
+ * See {@link UserManagerInternal#setDevicePolicyUserRestrictions(int, Bundle, Bundle)}
+ */
+ void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle local,
+ @Nullable Bundle global) {
+ Preconditions.checkNotNull(local);
+ boolean globalChanged = false;
+ boolean localChanged;
+ synchronized (mRestrictionsLock) {
+ if (global != null) {
+ // Update global.
+ globalChanged = !UserRestrictionsUtils.areEqual(
+ mDevicePolicyGlobalUserRestrictions, global);
+ if (globalChanged) {
+ mDevicePolicyGlobalUserRestrictions = global;
+ }
+ }
+ {
+ // Update local.
+ final Bundle prev = mDevicePolicyLocalUserRestrictions.get(userId);
+ localChanged = !UserRestrictionsUtils.areEqual(prev, local);
+ if (localChanged) {
+ mDevicePolicyLocalUserRestrictions.put(userId, local);
+ }
+ }
+ }
+ if (DBG) {
+ Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: userId=" + userId
+ + " global=" + global + (globalChanged ? " (changed)" : "")
+ + " local=" + local + (localChanged ? " (changed)" : "")
+ );
+ }
+ // Don't call them within the mRestrictionsLock.
+ synchronized (mPackagesLock) {
+ if (globalChanged) {
+ writeUserListLP();
+ }
+ if (localChanged) {
+ writeUserLP(getUserInfoNoChecks(userId));
+ }
+ }
+
+ synchronized (mRestrictionsLock) {
+ if (globalChanged) {
+ applyUserRestrictionsForAllUsersLR();
+ } else if (localChanged) {
+ applyUserRestrictionsLR(userId);
+ }
+ }
+ }
+
@GuardedBy("mRestrictionsLock")
private Bundle computeEffectiveUserRestrictionsLR(int userId) {
- final DevicePolicyManagerInternal dpmi =
- LocalServices.getService(DevicePolicyManagerInternal.class);
- final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
+ final Bundle baseRestrictions =
+ UserRestrictionsUtils.nonNull(mBaseUserRestrictions.get(userId));
+ final Bundle global = mDevicePolicyGlobalUserRestrictions;
+ final Bundle local = mDevicePolicyLocalUserRestrictions.get(userId);
- final Bundle effective;
- if (dpmi == null) {
- // TODO Make sure it's because DPMS is disabled and not because we called it too early.
- effective = systemRestrictions;
- } else {
- effective = dpmi.getComposedUserRestrictions(userId, systemRestrictions);
+ if (UserRestrictionsUtils.isEmpty(global) && UserRestrictionsUtils.isEmpty(local)) {
+ // Common case first.
+ return baseRestrictions;
}
+ final Bundle effective = UserRestrictionsUtils.clone(baseRestrictions);
+ UserRestrictionsUtils.merge(effective, global);
+ UserRestrictionsUtils.merge(effective, local);
+
return effective;
}
@@ -709,14 +775,13 @@
*/
@Override
public Bundle getUserRestrictions(int userId) {
- Bundle restrictions = getEffectiveUserRestrictions(userId);
- return restrictions != null ? new Bundle(restrictions) : new Bundle();
+ return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
}
@Override
public void setUserRestriction(String key, boolean value, int userId) {
checkManageUsersPermission("setUserRestriction");
- if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
+ if (!UserRestrictionsUtils.isSystemControlled(key)) {
setUserRestrictionNoCheck(key, value, userId);
}
}
@@ -731,8 +796,8 @@
synchronized (mRestrictionsLock) {
// Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
// a copy.
- final Bundle newRestrictions = new Bundle();
- UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
+ final Bundle newRestrictions = UserRestrictionsUtils.clone(
+ mBaseUserRestrictions.get(userId));
newRestrictions.putBoolean(key, value);
updateUserRestrictionsInternalLR(newRestrictions, userId);
@@ -740,75 +805,70 @@
}
/**
- * Optionally updating user restrictions, calculate the effective user restrictions by
- * consulting {@link com.android.server.devicepolicy.DevicePolicyManagerService} and also
- * apply it to {@link com.android.server.AppOpsService}.
- * TODO applyUserRestrictionsLocked() should also apply to system settings.
+ * Optionally updating user restrictions, calculate the effective user restrictions and also
+ * propagate to other services and system settings.
*
- * @param newRestrictions User restrictions to set. If null, only the effective restrictions
- * will be updated. Note don't pass an existing Bundle in {@link #mBaseUserRestrictions}
- * or {@link #mCachedEffectiveUserRestrictions}; that'll most likely cause a sub
+ * @param newRestrictions User restrictions to set.
+ * If null, will not update user restrictions and only does the propagation.
* @param userId target user ID.
*/
@GuardedBy("mRestrictionsLock")
private void updateUserRestrictionsInternalLR(
@Nullable Bundle newRestrictions, int userId) {
- if (DBG) {
- Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
- + " bundle=" + newRestrictions);
- }
- // Update system restrictions.
+
+ final Bundle prevAppliedRestrictions = UserRestrictionsUtils.nonNull(
+ mAppliedUserRestrictions.get(userId));
+
+ // Update base restrictions.
if (newRestrictions != null) {
// If newRestrictions == the current one, it's probably a bug.
- Preconditions.checkState(mBaseUserRestrictions.get(userId) != newRestrictions);
+ final Bundle prevBaseRestrictions = mBaseUserRestrictions.get(userId);
+
+ Preconditions.checkState(prevBaseRestrictions != newRestrictions);
Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
!= newRestrictions);
- mBaseUserRestrictions.put(userId, newRestrictions);
- scheduleWriteUser(getUserInfoNoChecks(userId));
+
+ if (!UserRestrictionsUtils.areEqual(prevBaseRestrictions, newRestrictions)) {
+ mBaseUserRestrictions.put(userId, newRestrictions);
+ scheduleWriteUser(getUserInfoNoChecks(userId));
+ }
}
final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
mCachedEffectiveUserRestrictions.put(userId, effective);
- applyUserRestrictionsLR(userId, effective);
- }
-
- @GuardedBy("mRestrictionsLock")
- private void applyUserRestrictionsLR(int userId, Bundle newRestrictions) {
- if (newRestrictions == null) {
- newRestrictions = Bundle.EMPTY;
- }
-
- Bundle prevRestrictions = mAppliedUserRestrictions.get(userId);
- if (prevRestrictions == null) {
- prevRestrictions = Bundle.EMPTY;
- }
-
+ // Apply the new restrictions.
if (DBG) {
- Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId
- + " new=" + newRestrictions + " prev=" + prevRestrictions);
+ debug("Applying user restrictions: userId=" + userId
+ + " new=" + effective + " prev=" + prevAppliedRestrictions);
}
- final long token = Binder.clearCallingIdentity();
- try {
- mAppOpsService.setUserRestrictions(newRestrictions, userId);
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
- } finally {
- Binder.restoreCallingIdentity(token);
+ if (mAppOpsService != null) { // We skip it until system-ready.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mAppOpsService.setUserRestrictions(effective, userId);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
- UserRestrictionsUtils.applyUserRestrictionsLR(
- mContext, userId, newRestrictions, prevRestrictions);
+ propagateUserRestrictionsLR(userId, effective, prevAppliedRestrictions);
- notifyUserRestrictionsListeners(userId, newRestrictions, prevRestrictions);
-
- mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
+ mAppliedUserRestrictions.put(userId, new Bundle(effective));
}
- private void notifyUserRestrictionsListeners(final int userId,
+ private void propagateUserRestrictionsLR(final int userId,
Bundle newRestrictions, Bundle prevRestrictions) {
+ // Note this method doesn't touch any state, meaning it doesn't require mRestrictionsLock
+ // actually, but we still need some kind of synchronization otherwise we might end up
+ // calling listeners out-of-order, thus "LR".
+
+ if (UserRestrictionsUtils.areEqual(newRestrictions, prevRestrictions)) {
+ return;
+ }
final Bundle newRestrictionsFinal = new Bundle(newRestrictions);
final Bundle prevRestrictionsFinal = new Bundle(prevRestrictions);
@@ -816,6 +876,11 @@
mHandler.post(new Runnable() {
@Override
public void run() {
+ synchronized (mRestrictionsLock) {
+ UserRestrictionsUtils.applyUserRestrictionsLR(
+ mContext, userId, newRestrictionsFinal, prevRestrictionsFinal);
+ }
+
final UserRestrictionsListener[] listeners;
synchronized (mUserRestrictionsListeners) {
listeners = new UserRestrictionsListener[mUserRestrictionsListeners.size()];
@@ -829,13 +894,17 @@
});
}
- @GuardedBy("mRestrictionsLock")
- private void updateEffectiveUserRestrictionsLR(int userId) {
+ // Package private for the inner class.
+ void applyUserRestrictionsLR(int userId) {
updateUserRestrictionsInternalLR(null, userId);
}
@GuardedBy("mRestrictionsLock")
- private void updateEffectiveUserRestrictionsForAllUsersLR() {
+ // Package private for the inner class.
+ void applyUserRestrictionsForAllUsersLR() {
+ if (DBG) {
+ debug("applyUserRestrictionsForAllUsersLR");
+ }
// First, invalidate all cached values.
mCachedEffectiveUserRestrictions.clear();
@@ -856,10 +925,9 @@
// It's okay if a new user has started after the getRunningUserIds() call,
// because we'll do the same thing (re-calculate the restrictions and apply)
// when we start a user.
- // TODO: "Apply restrictions upon user start hasn't been implemented. Implement it.
synchronized (mRestrictionsLock) {
for (int i = 0; i < runningUsers.length; i++) {
- updateUserRestrictionsInternalLR(null, runningUsers[i]);
+ applyUserRestrictionsLR(runningUsers[i]);
}
}
}
@@ -1020,6 +1088,8 @@
}
}
+ final Bundle newDevicePolicyGlobalUserRestrictions = new Bundle();
+
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG) {
final String name = parser.getName();
@@ -1044,6 +1114,10 @@
UserRestrictionsUtils
.readRestrictions(parser, mGuestRestrictions);
}
+ } else if (parser.getName().equals(TAG_DEVICE_POLICY_RESTRICTIONS)
+ ) {
+ UserRestrictionsUtils.readRestrictions(parser,
+ newDevicePolicyGlobalUserRestrictions);
}
break;
}
@@ -1051,6 +1125,9 @@
}
}
}
+ synchronized (mRestrictionsLock) {
+ mDevicePolicyGlobalUserRestrictions = newDevicePolicyGlobalUserRestrictions;
+ }
updateUserIds();
upgradeIfNecessaryLP();
} catch (IOException | XmlPullParserException e) {
@@ -1064,6 +1141,7 @@
* Upgrade steps between versions, either for fixing bugs or changing the data format.
*/
private void upgradeIfNecessaryLP() {
+ final int originalVersion = mUserVersion;
int userVersion = mUserVersion;
if (userVersion < 1) {
// Assign a proper name for the owner, if not initialized correctly before
@@ -1116,7 +1194,10 @@
+ USER_VERSION);
} else {
mUserVersion = userVersion;
- writeUserListLP();
+
+ if (originalVersion < mUserVersion) {
+ writeUserListLP();
+ }
}
}
@@ -1150,6 +1231,9 @@
}
private void scheduleWriteUser(UserInfo userInfo) {
+ if (DBG) {
+ debug("scheduleWriteUser");
+ }
// No need to wrap it within a lock -- worst case, we'll just post the same message
// twice.
if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
@@ -1166,6 +1250,9 @@
* </user>
*/
private void writeUserLP(UserInfo userInfo) {
+ if (DBG) {
+ debug("writeUserLP " + userInfo);
+ }
FileOutputStream fos = null;
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
try {
@@ -1205,13 +1292,12 @@
serializer.startTag(null, TAG_NAME);
serializer.text(userInfo.name);
serializer.endTag(null, TAG_NAME);
- Bundle restrictions;
synchronized (mRestrictionsLock) {
- restrictions = mBaseUserRestrictions.get(userInfo.id);
- }
- if (restrictions != null) {
- UserRestrictionsUtils
- .writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
+ UserRestrictionsUtils.writeRestrictions(serializer,
+ mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
+ UserRestrictionsUtils.writeRestrictions(serializer,
+ mDevicePolicyLocalUserRestrictions.get(userInfo.id),
+ TAG_DEVICE_POLICY_RESTRICTIONS);
}
serializer.endTag(null, TAG_USER);
@@ -1232,6 +1318,9 @@
* </users>
*/
private void writeUserListLP() {
+ if (DBG) {
+ debug("writeUserList");
+ }
FileOutputStream fos = null;
AtomicFile userListFile = new AtomicFile(mUserListFile);
try {
@@ -1254,6 +1343,10 @@
.writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
}
serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
+ synchronized (mRestrictionsLock) {
+ UserRestrictionsUtils.writeRestrictions(serializer,
+ mDevicePolicyGlobalUserRestrictions, TAG_DEVICE_POLICY_RESTRICTIONS);
+ }
int[] userIdsToWrite;
synchronized (mUsersLock) {
userIdsToWrite = new int[mUsers.size()];
@@ -1289,7 +1382,8 @@
int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
boolean partial = false;
boolean guestToRemove = false;
- Bundle restrictions = new Bundle();
+ Bundle baseRestrictions = new Bundle();
+ Bundle localRestrictions = new Bundle();
FileInputStream fis = null;
try {
@@ -1346,7 +1440,9 @@
name = parser.getText();
}
} else if (TAG_RESTRICTIONS.equals(tag)) {
- UserRestrictionsUtils.readRestrictions(parser, restrictions);
+ UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
+ } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
+ UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
}
}
}
@@ -1360,7 +1456,8 @@
userInfo.profileGroupId = profileGroupId;
userInfo.restrictedProfileParentId = restrictedProfileParentId;
synchronized (mRestrictionsLock) {
- mBaseUserRestrictions.append(id, restrictions);
+ mBaseUserRestrictions.put(id, baseRestrictions);
+ mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
}
return userInfo;
@@ -2118,6 +2215,15 @@
}
/**
+ * Called right before a user starts. This will not be called for the system user.
+ */
+ public void onBeforeStartUser(int userId) {
+ synchronized (mRestrictionsLock) {
+ applyUserRestrictionsLR(userId);
+ }
+ }
+
+ /**
* Make a note of the last started time of a user and do some cleanup.
* @param userId the user that was just foregrounded
*/
@@ -2313,16 +2419,25 @@
synchronized (mRestrictionsLock) {
UserRestrictionsUtils.dumpRestrictions(
pw, " ", mBaseUserRestrictions.get(user.id));
+ pw.println(" Device policy local restrictions:");
+ UserRestrictionsUtils.dumpRestrictions(
+ pw, " ", mDevicePolicyLocalUserRestrictions.get(user.id));
pw.println(" Effective restrictions:");
UserRestrictionsUtils.dumpRestrictions(
pw, " ", mCachedEffectiveUserRestrictions.get(user.id));
}
+ pw.println();
}
}
+ pw.println(" Device policy global restrictions:");
+ synchronized (mRestrictionsLock) {
+ UserRestrictionsUtils
+ .dumpRestrictions(pw, " ", mDevicePolicyGlobalUserRestrictions);
+ }
pw.println();
- pw.println("Guest restrictions:");
+ pw.println(" Guest restrictions:");
synchronized (mGuestRestrictions) {
- UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions);
+ UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions);
}
}
}
@@ -2354,22 +2469,11 @@
}
private class LocalService extends UserManagerInternal {
-
@Override
- public Object getUserRestrictionsLock() {
- return mRestrictionsLock;
- }
-
- @Override
- @GuardedBy("mRestrictionsLock")
- public void updateEffectiveUserRestrictionsLR(int userId) {
- UserManagerService.this.updateEffectiveUserRestrictionsLR(userId);
- }
-
- @Override
- @GuardedBy("mRestrictionsLock")
- public void updateEffectiveUserRestrictionsForAllUsersLR() {
- UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersLR();
+ public void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle localRestrictions,
+ @Nullable Bundle globalRestrictions) {
+ UserManagerService.this.setDevicePolicyUserRestrictions(userId, localRestrictions,
+ globalRestrictions);
}
@Override
@@ -2434,4 +2538,9 @@
pw.println(" Prints all users on the system.");
}
}
+
+ private static void debug(String message) {
+ Log.d(LOG_TAG, message +
+ (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 56e8b3e..129cbd3 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -18,6 +18,10 @@
import com.google.android.collect.Sets;
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
@@ -26,6 +30,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -79,22 +84,64 @@
UserManager.DISALLOW_SAFE_BOOT,
UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
UserManager.DISALLOW_RECORD_AUDIO,
+ UserManager.DISALLOW_CAMERA,
};
/**
* Set of user restrictions, which can only be enforced by the system.
*/
public static final Set<String> SYSTEM_CONTROLLED_USER_RESTRICTIONS = Sets.newArraySet(
- UserManager.DISALLOW_RECORD_AUDIO);
+ UserManager.DISALLOW_RECORD_AUDIO
+ );
/**
* Set of user restriction which we don't want to persist.
*/
- public static final Set<String> NON_PERSIST_USER_RESTRICTIONS = Sets.newArraySet(
- UserManager.DISALLOW_RECORD_AUDIO);
+ private static final Set<String> NON_PERSIST_USER_RESTRICTIONS = Sets.newArraySet(
+ UserManager.DISALLOW_RECORD_AUDIO
+ );
- public static void writeRestrictions(XmlSerializer serializer, Bundle restrictions,
- String tag) throws IOException {
+ /**
+ * User restrictions that can not be set by profile owners.
+ */
+ private static final Set<String> DEVICE_OWNER_ONLY_RESTRICTIONS = Sets.newArraySet(
+ UserManager.DISALLOW_USB_FILE_TRANSFER,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_NETWORK_RESET,
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_FUN,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_CREATE_WINDOWS
+ );
+
+ /**
+ * User restrictions that can't be changed by device owner or profile owner.
+ */
+ private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
+ UserManager.DISALLOW_RECORD_AUDIO,
+ UserManager.DISALLOW_WALLPAPER
+ );
+
+ /**
+ * Special user restrictions that can be applied to a user as well as to all users globally,
+ * depending on callers. When device owner sets them, they'll be applied to all users.
+ */
+ private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE
+ );
+
+ public static void writeRestrictions(@NonNull XmlSerializer serializer,
+ @Nullable Bundle restrictions, @NonNull String tag) throws IOException {
+ if (restrictions == null) {
+ return;
+ }
+
serializer.startTag(null, tag);
for (String key : USER_RESTRICTIONS) {
if (restrictions.getBoolean(key)
@@ -115,7 +162,31 @@
}
}
- public static void merge(Bundle dest, Bundle in) {
+ /**
+ * @return {@code in} itself when it's not null, or an empty bundle (which can writable).
+ */
+ public static Bundle nonNull(@Nullable Bundle in) {
+ return in != null ? in : new Bundle();
+ }
+
+ public static boolean isEmpty(@Nullable Bundle in) {
+ return (in == null) || (in.size() == 0);
+ }
+
+ /**
+ * Creates a copy of the {@code in} Bundle. If {@code in} is null, it'll return an empty
+ * bundle.
+ *
+ * <p>The resulting {@link Bundle} is always writable. (i.e. it won't return
+ * {@link Bundle#EMPTY})
+ */
+ public static @NonNull Bundle clone(@Nullable Bundle in) {
+ return (in != null) ? new Bundle(in) : new Bundle();
+ }
+
+ public static void merge(@NonNull Bundle dest, @Nullable Bundle in) {
+ Preconditions.checkNotNull(dest);
+ Preconditions.checkArgument(dest != in);
if (in == null) {
return;
}
@@ -127,6 +198,77 @@
}
/**
+ * @return true if a restriction is "system controlled"; i.e. can not be overwritten via
+ * {@link UserManager#setUserRestriction}.
+ */
+ public static boolean isSystemControlled(String restriction) {
+ return SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(restriction);
+ }
+
+ /**
+ * @return true if a restriction is settable by device owner.
+ */
+ public static boolean canDeviceOwnerChange(String restriction) {
+ return !IMMUTABLE_BY_OWNERS.contains(restriction);
+ }
+
+ /**
+ * @return true if a restriction is settable by profile owner.
+ */
+ public static boolean canProfileOwnerChange(String restriction) {
+ return !(IMMUTABLE_BY_OWNERS.contains(restriction)
+ || DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction));
+ }
+
+ /**
+ * Takes restrictions that can be set by device owner, and sort them into what should be applied
+ * globally and what should be applied only on the current user.
+ */
+ public static void sortToGlobalAndLocal(@Nullable Bundle in, @NonNull Bundle global,
+ @NonNull Bundle local) {
+ if (in == null || in.size() == 0) {
+ return;
+ }
+ for (String key : in.keySet()) {
+ if (!in.getBoolean(key)) {
+ continue;
+ }
+ if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)) {
+ global.putBoolean(key, true);
+ } else {
+ local.putBoolean(key, true);
+ }
+ }
+ }
+
+ /**
+ * @return true if two Bundles contain the same user restriction.
+ * A null bundle and an empty bundle are considered to be equal.
+ */
+ public static boolean areEqual(@Nullable Bundle a, @Nullable Bundle b) {
+ if (a == b) {
+ return true;
+ }
+ if (isEmpty(a)) {
+ return isEmpty(b);
+ }
+ if (isEmpty(b)) {
+ return false;
+ }
+ for (String key : a.keySet()) {
+ if (a.getBoolean(key) != b.getBoolean(key)) {
+ return false;
+ }
+ }
+ for (String key : b.keySet()) {
+ if (a.getBoolean(key) != b.getBoolean(key)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Takes a new use restriction set and the previous set, and apply the restrictions that have
* changed.
*
@@ -145,7 +287,7 @@
}
}
}
-
+
/**
* Apply each user restriction.
*
@@ -155,6 +297,10 @@
*/
private static void applyUserRestrictionLR(Context context, int userId, String key,
boolean newValue) {
+ if (UserManagerService.DBG) {
+ Log.d(TAG, "Applying user restriction: userId=" + userId
+ + " key=" + key + " value=" + newValue);
+ }
// When certain restrictions are cleared, we don't update the system settings,
// because these settings are changeable on the Settings UI and we don't know the original
// value -- for example LOCATION_MODE might have been off already when the restriction was
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 8fc979c..cc25c8c 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -17,6 +17,7 @@
package com.android.server.updates;
import com.android.server.EventLogTags;
+import com.android.internal.util.HexDump;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -155,7 +156,7 @@
try {
MessageDigest dgst = MessageDigest.getInstance("SHA512");
byte[] fingerprint = dgst.digest(content);
- return IntegralToString.bytesToHexString(fingerprint, false);
+ return HexDump.toHexString(fingerprint, false);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 8085f13..04ab544 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -515,10 +515,11 @@
for (int i = 0; i < count; i++) {
final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
final int otherStackId = otherStack.mStackId;
- if (StackId.isResizeableByDockedStack(otherStackId)) {
+ if (StackId.isResizeableByDockedStack(otherStackId)
+ && !otherStack.mBounds.equals(bounds)) {
mService.mH.sendMessage(
mService.mH.obtainMessage(RESIZE_STACK, otherStackId,
- 1 /*allowResizeInDockedMode*/, bounds));
+ 1 /*allowResizeInDockedMode*/, fullscreen ? null : bounds));
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 253fdad..b6600266 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2863,7 +2863,11 @@
// notifying the client to render to with an offset from the surface's top-left.
if (win.isDragResizeChanged()) {
win.setDragResizing();
- if (win.mHasSurface) {
+ // We can only change top level windows to the full-screen surface when
+ // resizing (as we only have one full-screen surface). So there is no need
+ // to preserve and destroy windows which are attached to another, they
+ // will keep their surface and its size may change over time.
+ if (win.mHasSurface && win.mAttachedWindow == null) {
winAnimator.preserveSurfaceLocked();
result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 6faf3a7..004307e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -735,7 +735,12 @@
if (mSurfaceController != null) {
int i = mWin.mChildWindows.size();
- while (i > 0) {
+ // When destroying a surface we want to make sure child windows
+ // are hidden. If we are preserving the surface until redraw though
+ // we intend to swap it out with another surface for resizing. In this case
+ // the window always remains visible to the user and the child windows
+ // should likewise remain visable.
+ while (!mDestroyPreservedSurfaceUponRedraw && i > 0) {
i--;
WindowState c = mWin.mChildWindows.get(i);
c.mAttachedHidden = true;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 844cca5..642def3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -201,31 +201,6 @@
private static final int STATUS_BAR_DISABLE2_MASK =
StatusBarManager.DISABLE2_QUICK_SETTINGS;
- private static final Set<String> DEVICE_OWNER_USER_RESTRICTIONS;
- static {
- DEVICE_OWNER_USER_RESTRICTIONS = new ArraySet<>();
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_USB_FILE_TRANSFER);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_TETHERING);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_NETWORK_RESET);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FACTORY_RESET);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_ADD_USER);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SMS);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FUN);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SAFE_BOOT);
- DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CREATE_WINDOWS);
- }
-
- // The following user restrictions cannot be changed by any active admin, including device
- // owner and profile owner.
- private static final Set<String> IMMUTABLE_USER_RESTRICTIONS;
- static {
- IMMUTABLE_USER_RESTRICTIONS = new ArraySet<>();
- IMMUTABLE_USER_RESTRICTIONS.add(UserManager.DISALLOW_WALLPAPER);
- }
-
private static final Set<String> SECURE_SETTINGS_WHITELIST;
private static final Set<String> SECURE_SETTINGS_DEVICEOWNER_WHITELIST;
private static final Set<String> GLOBAL_SETTINGS_WHITELIST;
@@ -306,6 +281,11 @@
public void onBootPhase(int phase) {
mService.systemReady(phase);
}
+
+ @Override
+ public void onStartUser(int userHandle) {
+ mService.onStartUser(userHandle);
+ }
}
public static class DevicePolicyData {
@@ -1014,7 +994,6 @@
}
}
- // DO NOT call it while taking the "this" lock, which could cause a dead lock.
private void handlePackagesChanged(String packageName, int userHandle) {
boolean removed = false;
if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
@@ -1060,11 +1039,8 @@
}
}
if (removed) {
- synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
- synchronized (DevicePolicyManagerService.this) {
- mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
- }
- }
+ // The removed admin might have disabled camera, so update user restrictions.
+ pushUserRestrictions(userHandle);
}
}
@@ -1350,8 +1326,6 @@
// TODO PO may not have a class name either due to b/17652534. Address that too.
updateDeviceOwnerLocked();
-
- // TODO Notify UM to update restrictions (?)
}
}
@@ -1403,6 +1377,9 @@
migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
/* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+ // Push DO user restrictions to user manager.
+ pushUserRestrictions(UserHandle.USER_SYSTEM);
+
mOwners.setDeviceOwnerUserRestrictionsMigrated();
}
@@ -1432,6 +1409,13 @@
migrateUserRestrictionsForUser(ui.getUserHandle(), profileOwnerAdmin,
exceptionList);
+
+ // Note if a secondary user has no PO but has a DA that disables camera, we
+ // don't get here and won't push the camera user restriction to UserManager
+ // here. That's okay because we'll push user restrictions anyway when a user
+ // starts. But we still do it because we want to let user manager persist
+ // upon migration.
+ pushUserRestrictions(userId);
}
mOwners.setProfileOwnerUserRestrictionsMigrated(userId);
@@ -1709,12 +1693,9 @@
updateMaximumTimeToLockLocked(policy);
policy.mRemovingAdmins.remove(adminReceiver);
}
- synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
- synchronized (DevicePolicyManagerService.this) {
- mUserManagerInternal.updateEffectiveUserRestrictionsLR(
- userHandle);
- }
- }
+ // The removed admin might have disabled camera, so update user
+ // restrictions.
+ pushUserRestrictions(userHandle);
}
});
}
@@ -2113,19 +2094,13 @@
getUserData(UserHandle.USER_SYSTEM);
loadOwners();
cleanUpOldUsers();
+
+ onStartUser(UserHandle.USER_SYSTEM);
+
// Register an observer for watching for user setup complete.
new SetupContentObserver(mHandler).register(mContext.getContentResolver());
// Initialize the user setup state, to handle the upgrade case.
updateUserSetupComplete();
-
- // Update the screen capture disabled cache in the window manager
- List<UserInfo> users = mUserManager.getUsers(true);
- final int N = users.size();
- for (int i = 0; i < N; i++) {
- int userHandle = users.get(i).id;
- updateScreenCaptureDisabledInWindowManager(userHandle,
- getScreenCaptureDisabled(null, userHandle));
- }
}
private void ensureDeviceOwnerUserStarted() {
@@ -2147,6 +2122,12 @@
}
}
+ private void onStartUser(int userId) {
+ updateScreenCaptureDisabledInWindowManager(userId,
+ getScreenCaptureDisabled(null, userId));
+ pushUserRestrictions(userId);
+ }
+
private void cleanUpOldUsers() {
// This is needed in case the broadcast {@link Intent.ACTION_USER_REMOVED} was not handled
// before reboot
@@ -4307,15 +4288,18 @@
}
}
- private void updateScreenCaptureDisabledInWindowManager(int userHandle, boolean disabled) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- mInjector.getIWindowManager().setScreenCaptureDisabled(userHandle, disabled);
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ private void updateScreenCaptureDisabledInWindowManager(final int userHandle,
+ final boolean disabled) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mInjector.getIWindowManager().setScreenCaptureDisabled(userHandle, disabled);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
+ }
+ }
+ });
}
/**
@@ -4381,15 +4365,7 @@
}
}
// Tell the user manager that the restrictions have changed.
- synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
- synchronized (this) {
- if (isDeviceOwner(who, userHandle)) {
- mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
- } else {
- mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
- }
- }
- }
+ pushUserRestrictions(userHandle);
}
/**
@@ -4398,6 +4374,11 @@
*/
@Override
public boolean getCameraDisabled(ComponentName who, int userHandle) {
+ return getCameraDisabled(who, userHandle, /* mergeDeviceOwnerRestriction= */ true);
+ }
+
+ private boolean getCameraDisabled(ComponentName who, int userHandle,
+ boolean mergeDeviceOwnerRestriction) {
if (!mHasFeature) {
return false;
}
@@ -4407,9 +4388,11 @@
return (admin != null) ? admin.disableCamera : false;
}
// First, see if DO has set it. If so, it's device-wide.
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- if (deviceOwner != null && deviceOwner.disableCamera) {
- return true;
+ if (mergeDeviceOwnerRestriction) {
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null && deviceOwner.disableCamera) {
+ return true;
+ }
}
// Then check each device admin on the user.
@@ -4602,6 +4585,7 @@
return admin;
}
}
+ Slog.wtf(LOG_TAG, "Active admin for device owner not found. component=" + component);
return null;
}
@@ -4622,6 +4606,11 @@
throw new SecurityException("clearDeviceOwner can only be called by the device owner");
}
synchronized (this) {
+ final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+ if (admin != null) {
+ admin.disableCamera = false;
+ admin.userRestrictions = null;
+ }
clearUserPoliciesLocked(new UserHandle(UserHandle.USER_SYSTEM));
mOwners.clearDeviceOwner();
@@ -4667,6 +4656,7 @@
final ActiveAdmin admin =
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
synchronized (this) {
+ admin.disableCamera = false;
admin.userRestrictions = null;
clearUserPoliciesLocked(callingUser);
final int userId = callingUser.getIdentifier();
@@ -4713,9 +4703,7 @@
mIPackageManager.updatePermissionFlagsForAllApps(
PackageManager.FLAG_PERMISSION_POLICY_FIXED,
0 /* flagValues */, userHandle.getIdentifier());
- synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
- mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle.getIdentifier());
- }
+ pushUserRestrictions(userHandle.getIdentifier());
} catch (RemoteException re) {
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -5004,31 +4992,30 @@
return;
}
- final Printer p = new PrintWriterPrinter(pw);
-
synchronized (this) {
- p.println("Current Device Policy Manager state:");
+ pw.println("Current Device Policy Manager state:");
mOwners.dump(" ", pw);
int userCount = mUserData.size();
for (int u = 0; u < userCount; u++) {
DevicePolicyData policy = getUserData(mUserData.keyAt(u));
- p.println(" Enabled Device Admins (User " + policy.mUserHandle + "):");
+ pw.println();
+ pw.println(" Enabled Device Admins (User " + policy.mUserHandle + "):");
final int N = policy.mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin ap = policy.mAdminList.get(i);
if (ap != null) {
- pw.print(" "); pw.print(ap.info.getComponent().flattenToShortString());
+ pw.print(" "); pw.print(ap.info.getComponent().flattenToShortString());
pw.println(":");
- ap.dump(" ", pw);
+ ap.dump(" ", pw);
}
}
if (!policy.mRemovingAdmins.isEmpty()) {
- p.println(" Removing Device Admins (User " + policy.mUserHandle + "): "
+ pw.println(" Removing Device Admins (User " + policy.mUserHandle + "): "
+ policy.mRemovingAdmins);
}
pw.println(" ");
- pw.print(" mPasswordOwner="); pw.println(policy.mPasswordOwner);
+ pw.print(" mPasswordOwner="); pw.println(policy.mPasswordOwner);
}
}
}
@@ -5666,56 +5653,83 @@
}
}
- // DO NOT call it while taking the "this" lock, which could cause a dead lock.
@Override
public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
Preconditions.checkNotNull(who, "ComponentName is null");
final int userHandle = mInjector.userHandleGetCallingUserId();
- synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
- synchronized (this) {
- ActiveAdmin activeAdmin =
- getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
- if (!isDeviceOwner && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
- throw new SecurityException(
- "Profile owners cannot set user restriction " + key);
+ synchronized (this) {
+ ActiveAdmin activeAdmin =
+ getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+ if (isDeviceOwner) {
+ if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+ throw new SecurityException("Device owner cannot set user restriction " + key);
}
- if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
- throw new SecurityException("User restriction " + key + " cannot be changed");
+ } else { // profile owner
+ if (!UserRestrictionsUtils.canProfileOwnerChange(key)) {
+ throw new SecurityException("Profile owner cannot set user restriction " + key);
}
-
- final long id = mInjector.binderClearCallingIdentity();
- try {
- // Save the restriction to ActiveAdmin.
- // TODO When DO sets a restriction, it'll always be treated as device-wide.
- // If there'll be a policy that can be set by both, we'll need scoping support,
- // and need to have another Bundle in DO active admin to hold restrictions as
- // PO.
- activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
- saveSettingsLocked(userHandle);
-
- // Tell UserManager the new value.
- if (isDeviceOwner) {
- mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
- } else {
- mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
-
- sendChangedNotification(userHandle);
}
+
+ // Save the restriction to ActiveAdmin.
+ activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
+ saveSettingsLocked(userHandle);
+
+ pushUserRestrictions(userHandle);
+
+ sendChangedNotification(userHandle);
+ }
+ }
+
+ private void pushUserRestrictions(int userId) {
+ synchronized (this) {
+ final Bundle global;
+ final Bundle local = new Bundle();
+ if (mOwners.isDeviceOwnerUserId(userId)) {
+ global = new Bundle();
+
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner == null) {
+ return; // Shouldn't happen.
+ }
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(deviceOwner.userRestrictions,
+ global, local);
+ // DO can disable camera globally.
+ if (deviceOwner.disableCamera) {
+ global.putBoolean(UserManager.DISALLOW_CAMERA, true);
+ }
+ } else {
+ global = null;
+
+ ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+ if (profileOwner != null) {
+ UserRestrictionsUtils.merge(local, profileOwner.userRestrictions);
+ }
+ }
+ // Also merge in *local* camera restriction.
+ if (getCameraDisabled(/* who= */ null,
+ userId, /* mergeDeviceOwnerRestriction= */ false)) {
+ local.putBoolean(UserManager.DISALLOW_CAMERA, true);
+ }
+ mUserManagerInternal.setDevicePolicyUserRestrictions(userId, local, global);
}
}
@Override
- public Bundle getUserRestrictions(ComponentName who) {
+ public Bundle getUserRestrictions(ComponentName who, int userHandle) {
Preconditions.checkNotNull(who, "ComponentName is null");
+ enforceCrossUserPermission(userHandle);
synchronized (this) {
- final ActiveAdmin activeAdmin =
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ ActiveAdmin activeAdmin = getActiveAdminUncheckedLocked(who, userHandle);
+ if (activeAdmin == null) {
+ throw new SecurityException("No active admin: " + activeAdmin);
+ }
+ if (activeAdmin.getUid() != mInjector.binderGetCallingUid()) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
+ }
return activeAdmin.userRestrictions;
}
}
@@ -6448,37 +6462,6 @@
}
}
- @Override
- public Bundle getComposedUserRestrictions(int userId, Bundle inBundle) {
- synchronized (DevicePolicyManagerService.this) {
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
-
- final Bundle deviceOwnerRestrictions =
- deviceOwner == null ? null : deviceOwner.userRestrictions;
- final Bundle profileOwnerRestrictions =
- profileOwner == null ? null : profileOwner.userRestrictions;
- final boolean cameraDisabled = getCameraDisabled(null, userId);
-
- if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null
- && !cameraDisabled) {
- // No restrictions to merge.
- return inBundle;
- }
-
- final Bundle composed = new Bundle(inBundle);
- UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
- UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
-
- // Also merge in the camera restriction.
- if (cameraDisabled) {
- composed.putBoolean(UserManager.DISALLOW_CAMERA, true);
- }
-
- return composed;
- }
- }
-
private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
synchronized (DevicePolicyManagerService.this) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index ded4422..435de7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -221,6 +221,10 @@
return mDeviceOwner != null;
}
+ boolean isDeviceOwnerUserId(int userId) {
+ return mDeviceOwner != null && mDeviceOwnerUserId == userId;
+ }
+
boolean hasProfileOwner(int userId) {
return getProfileOwnerComponent(userId) != null;
}
@@ -625,20 +629,30 @@
}
public void dump(String prefix, PrintWriter pw) {
+ boolean needBlank = false;
if (mDeviceOwner != null) {
pw.println(prefix + "Device Owner: ");
mDeviceOwner.dump(prefix + " ", pw);
pw.println(prefix + " User ID: " + mDeviceOwnerUserId);
- pw.println();
+ needBlank = true;
}
if (mSystemUpdatePolicy != null) {
+ if (needBlank) {
+ needBlank = false;
+ pw.println();
+ }
pw.println(prefix + "System Update Policy: " + mSystemUpdatePolicy);
- pw.println();
+ needBlank = true;
}
if (mProfileOwners != null) {
for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) {
+ if (needBlank) {
+ needBlank = false;
+ pw.println();
+ }
pw.println(prefix + "Profile Owner (User " + entry.getKey() + "): ");
entry.getValue().dump(prefix + " ", pw);
+ needBlank = true;
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 36980e3..0bd4896 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,12 +25,8 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Bundle;
-import android.content.pm.PackageInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Pair;
@@ -50,6 +46,7 @@
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -768,21 +765,42 @@
dpm.getUserRestrictions(admin1)
);
- dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS);
+ reset(mContext.userManagerInternal);
+
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+ );
+ reset(mContext.userManagerInternal);
+
dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+ );
+ reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
- UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+ UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_OUTGOING_CALLS),
dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
- UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+ UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_OUTGOING_CALLS),
dpm.getUserRestrictions(admin1)
);
- dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS);
+ dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+ MockUtils.checkUserRestrictions()
+ );
+ reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
@@ -794,6 +812,12 @@
);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(),
+ MockUtils.checkUserRestrictions()
+ );
+ reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(),
@@ -804,7 +828,68 @@
dpm.getUserRestrictions(admin1)
);
- // TODO Check inner calls.
+ // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE are PO restrictions, but when
+ // DO sets them, the scope is global.
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+ reset(mContext.userManagerInternal);
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE)
+ );
+ reset(mContext.userManagerInternal);
+
+ dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+ dpm.clearUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+
+
+ // More tests.
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+ );
+ reset(mContext.userManagerInternal);
+
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_FUN);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+ UserManager.DISALLOW_ADD_USER)
+ );
+ reset(mContext.userManagerInternal);
+
+ dpm.setCameraDisabled(admin1, true);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ // DISALLOW_CAMERA will be applied to both local and global.
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+ UserManager.DISALLOW_CAMERA, UserManager.DISALLOW_ADD_USER)
+ );
+ reset(mContext.userManagerInternal);
+
+ // Set up another DA and let it disable camera. Now DISALLOW_CAMERA will only be applied
+ // locally.
+ dpm.setCameraDisabled(admin1, false);
+ reset(mContext.userManagerInternal);
+
+ setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID);
+ dpm.setActiveAdmin(admin2, /* replace =*/ false, UserHandle.USER_SYSTEM);
+ dpm.setCameraDisabled(admin2, true);
+
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ // DISALLOW_CAMERA will be applied to both local and global.
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+ UserManager.DISALLOW_ADD_USER)
+ );
+ reset(mContext.userManagerInternal);
// TODO Make sure restrictions are written to the file.
}
@@ -818,7 +903,21 @@
);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
+ isNull(Bundle.class)
+ );
+ reset(mContext.userManagerInternal);
+
dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_OUTGOING_CALLS),
+ isNull(Bundle.class)
+ );
+ reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
@@ -837,7 +936,12 @@
);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
-
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+ isNull(Bundle.class)
+ );
+ reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
@@ -854,6 +958,12 @@
);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(),
+ isNull(Bundle.class)
+ );
+ reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(),
@@ -865,69 +975,29 @@
dpm.getUserRestrictions(admin1)
);
- // TODO Check inner calls.
+ // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE can be set by PO too, even
+ // though when DO sets them they'll be applied globally.
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+ reset(mContext.userManagerInternal);
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE),
+ isNull(Bundle.class)
+ );
+ reset(mContext.userManagerInternal);
+
+ dpm.setCameraDisabled(admin1, true);
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA,
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE),
+ isNull(Bundle.class)
+ );
+ reset(mContext.userManagerInternal);
+
// TODO Make sure restrictions are written to the file.
}
-
- public void testGetComposedUserRestrictions_noDoNoPo() throws Exception {
- final Bundle in = DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS);
-
- Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
- UserHandle.USER_SYSTEM, in);
- assertTrue(in == actual);
-
- actual = dpms.mLocalService.getComposedUserRestrictions(
- DpmMockContext.CALLER_USER_HANDLE, in);
- assertTrue(in == actual);
- }
-
- public void testGetComposedUserRestrictions() throws Exception {
- mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
- mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
-
- // First, set DO.
-
- // Call from a process on the system user.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-
- // Make sure admin1 is installed on system user.
- setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
-
- // Call.
- dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
- assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
- UserHandle.USER_SYSTEM));
-
- dpm.addUserRestriction(admin1, "rest1");
- dpm.addUserRestriction(admin1, "rest2");
-
- // Set PO on CALLER_USER_HANDLE.
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-
- setAsProfileOwner(admin2);
-
- dpm.addUserRestriction(admin2, "restA");
- dpm.addUserRestriction(admin2, "restB");
-
- final Bundle in = DpmTestUtils.newRestrictions("abc");
-
- Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
- UserHandle.USER_SYSTEM, in);
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
- actual);
-
- actual = dpms.mLocalService.getComposedUserRestrictions(
- DpmMockContext.CALLER_USER_HANDLE, in);
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions("abc", "rest1", "rest2", "restA", "restB"),
- actual);
-
- actual = dpms.mLocalService.getComposedUserRestrictions(
- DpmMockContext.CALLER_USER_HANDLE + 1, in);
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
- actual);
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index cc337b0..f4fdc95 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -260,9 +260,6 @@
// System user is always running.
setUserRunning(UserHandle.USER_SYSTEM, true);
-
- // This method must return an object.
- when(userManagerInternal.getUserRestrictionsLock()).thenReturn(new Object());
}
public File addUser(int userId, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 5008fbf..58db192 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -17,8 +17,12 @@
import com.google.common.base.Objects;
+import com.android.internal.util.Preconditions;
+import com.android.server.pm.UserRestrictionsUtils;
+
import android.content.ComponentName;
import android.content.Intent;
+import android.os.Bundle;
import android.os.UserHandle;
import org.hamcrest.BaseMatcher;
@@ -77,4 +81,37 @@
};
return Mockito.argThat(m);
}
+
+ public static Bundle checkUserRestrictions(String... keys) {
+ final Bundle expected = DpmTestUtils.newRestrictions(Preconditions.checkNotNull(keys));
+ final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
+ @Override
+ public boolean matches(Object item) {
+ if (item == null) return false;
+ return UserRestrictionsUtils.areEqual((Bundle) item, expected);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("User restrictions=" + getRestrictionsAsString(expected));
+ }
+ };
+ return Mockito.argThat(m);
+ }
+
+ private static String getRestrictionsAsString(Bundle b) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("[");
+
+ if (b != null) {
+ String sep = "";
+ for (String key : b.keySet()) {
+ sb.append(sep);
+ sep = ",";
+ sb.append(key);
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 4e11762..423c4d5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -19,21 +19,7 @@
import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
import android.content.ComponentName;
-import android.content.pm.UserInfo;
import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-
-import static org.mockito.Mockito.when;
/**
* Tests for the DeviceOwner object that saves & loads device and policy owner information.
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
new file mode 100644
index 0000000..56fd351
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.server.devicepolicy.DpmTestUtils;
+
+import android.os.Bundle;
+import android.os.UserManager;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+/**
+ * Tests for {@link com.android.server.pm.UserRestrictionsUtils}.
+ *
+ * <p>Run with:<pre>
+ m FrameworksServicesTests &&
+ adb install \
+ -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.pm.UserRestrictionsUtilsTest \
+ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public class UserRestrictionsUtilsTest extends AndroidTestCase {
+ public void testNonNull() {
+ Bundle out = UserRestrictionsUtils.nonNull(null);
+ assertNotNull(out);
+ out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+
+ Bundle in = new Bundle();
+ assertSame(in, UserRestrictionsUtils.nonNull(in));
+ }
+
+ public void testIsEmpty() {
+ assertTrue(UserRestrictionsUtils.isEmpty(null));
+ assertTrue(UserRestrictionsUtils.isEmpty(new Bundle()));
+ assertFalse(UserRestrictionsUtils.isEmpty(DpmTestUtils.newRestrictions("a")));
+ }
+
+ public void testClone() {
+ Bundle in = new Bundle();
+ Bundle out = UserRestrictionsUtils.clone(in);
+ assertNotSame(in, out);
+ DpmTestUtils.assertRestrictions(out, new Bundle());
+
+ out = UserRestrictionsUtils.clone(null);
+ assertNotNull(out);
+ out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+ }
+
+ public void testMerge() {
+ Bundle a = DpmTestUtils.newRestrictions("a", "d");
+ Bundle b = DpmTestUtils.newRestrictions("b", "d", "e");
+
+ UserRestrictionsUtils.merge(a, b);
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+
+ UserRestrictionsUtils.merge(a, null);
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+
+ try {
+ UserRestrictionsUtils.merge(a, a);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testIsSystemControlled() {
+ assertTrue(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_RECORD_AUDIO));
+ assertFalse(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_FUN));
+ }
+
+ public void testCanDeviceOwnerChange() {
+ assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
+ assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
+ assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
+ }
+
+ public void testCanProfileOwnerChange() {
+ assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
+ assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_WALLPAPER));
+ assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADD_USER));
+ assertTrue(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADJUST_VOLUME));
+ }
+
+ public void testSortToGlobalAndLocal() {
+ final Bundle local = new Bundle();
+ final Bundle global = new Bundle();
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(null, global, local);
+ assertEquals(0, global.size());
+ assertEquals(0, local.size());
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, global, local);
+ assertEquals(0, global.size());
+ assertEquals(0, local.size());
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE,
+ UserManager.DISALLOW_USB_FILE_TRANSFER,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_OUTGOING_BEAM,
+ UserManager.DISALLOW_APPS_CONTROL
+ ), global, local);
+
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+ // These can be set by PO too, but when DO sets them, they're global.
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE,
+
+ // These can only be set by DO.
+ UserManager.DISALLOW_USB_FILE_TRANSFER,
+ UserManager.DISALLOW_CONFIG_TETHERING
+ ), global);
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+ // They can be set by both DO/PO.
+ UserManager.DISALLOW_OUTGOING_BEAM,
+ UserManager.DISALLOW_APPS_CONTROL
+ ), local);
+ }
+
+ public void testAreEqual() {
+ assertTrue(UserRestrictionsUtils.areEqual(
+ null,
+ null));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ null,
+ Bundle.EMPTY));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ Bundle.EMPTY,
+ null));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ Bundle.EMPTY,
+ Bundle.EMPTY));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ new Bundle(),
+ Bundle.EMPTY));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ null,
+ DpmTestUtils.newRestrictions("a")));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("a"),
+ null));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("a"),
+ DpmTestUtils.newRestrictions("a")));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("a"),
+ DpmTestUtils.newRestrictions("a", "b")));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("a", "b"),
+ DpmTestUtils.newRestrictions("a")));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("b", "a"),
+ DpmTestUtils.newRestrictions("a", "a")));
+
+ // Make sure false restrictions are handled correctly.
+ final Bundle a = DpmTestUtils.newRestrictions("a");
+ a.putBoolean("b", true);
+
+ final Bundle b = DpmTestUtils.newRestrictions("a");
+ b.putBoolean("b", false);
+
+ assertFalse(UserRestrictionsUtils.areEqual(a, b));
+ assertFalse(UserRestrictionsUtils.areEqual(b, a));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java b/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
index b6742a1..d798518 100644
--- a/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
+++ b/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
@@ -16,6 +16,8 @@
package com.android.server.updates;
+import com.android.internal.util.HexDump;
+
import android.content.Context;
import android.content.Intent;
import android.test.AndroidTestCase;
@@ -128,7 +130,7 @@
MessageDigest dgst = MessageDigest.getInstance("SHA512");
byte[] encoded = content.getBytes();
byte[] fingerprint = dgst.digest(encoded);
- return IntegralToString.bytesToHexString(fingerprint, false);
+ return HexDump.toHexString(fingerprint, false);
}
private static String getHashOfCurrentContent() throws Exception {
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index b12795c..68bde35 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -20,7 +20,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := core-libart core-junit framework
+LOCAL_JAVA_LIBRARIES := core-oj core-libart core-junit framework
LOCAL_STATIC_JAVA_LIBRARIES := junit-runner
LOCAL_MODULE:= android.test.runner
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 67b9d77..8eb30d2 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -160,6 +160,36 @@
}
},
+ new Test("with topic Hello") {
+ public void run() {
+ Notification n = new Notification.Builder(NotificationTestList.this)
+ .setSmallIcon(R.drawable.icon1)
+ .setWhen(mActivityCreateTime)
+ .setContentTitle("hihi")
+ .setContentText("This is a notification!!!")
+ .setContentIntent(makeIntent2())
+ .setTopic(new Notification.Topic("hello", "Hello"))
+ .build();
+
+ mNM.notify(999, n);
+ }
+ },
+
+ new Test("with topic GoodBye") {
+ public void run() {
+ Notification n = new Notification.Builder(NotificationTestList.this)
+ .setSmallIcon(R.drawable.icon1)
+ .setWhen(mActivityCreateTime)
+ .setContentTitle("byebye")
+ .setContentText("This is a notification!!!")
+ .setContentIntent(makeIntent2())
+ .setTopic(new Notification.Topic("bye", "Goodbye"))
+ .build();
+
+ mNM.notify(9999, n);
+ }
+ },
+
new Test("Whens") {
public void run()
{
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index ec29c38..d8e0aac 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -58,8 +58,9 @@
ResourceValues.cpp \
SdkConstants.cpp \
StringPool.cpp \
- XmlDom.cpp \
- XmlPullParser.cpp
+ xml/XmlDom.cpp \
+ xml/XmlPullParser.cpp \
+ xml/XmlUtil.cpp
testSources := \
compile/IdAssigner_test.cpp \
@@ -90,8 +91,9 @@
ResourceUtils_test.cpp \
StringPool_test.cpp \
ValueVisitor_test.cpp \
- XmlDom_test.cpp \
- XmlPullParser_test.cpp
+ xml/XmlDom_test.cpp \
+ xml/XmlPullParser_test.cpp \
+ xml/XmlUtil_test.cpp
toolSources := \
compile/Compile.cpp \
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index d292f62..02fe59c 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -19,9 +19,8 @@
#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "XmlPullParser.h"
-
#include "util/Util.h"
+#include "xml/XmlPullParser.h"
#include <sstream>
@@ -29,26 +28,6 @@
constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
-static Maybe<StringPiece16> findAttribute(XmlPullParser* parser, const StringPiece16& name) {
- auto iter = parser->findAttribute(u"", name);
- if (iter != parser->endAttributes()) {
- return StringPiece16(util::trimWhitespace(iter->value));
- }
- return {};
-}
-
-static Maybe<StringPiece16> findNonEmptyAttribute(XmlPullParser* parser,
- const StringPiece16& name) {
- auto iter = parser->findAttribute(u"", name);
- if (iter != parser->endAttributes()) {
- StringPiece16 trimmed = util::trimWhitespace(iter->value);
- if (!trimmed.empty()) {
- return trimmed;
- }
- }
- return {};
-}
-
/**
* Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
*/
@@ -65,7 +44,7 @@
/**
* Build a string from XML that converts nested elements into Span objects.
*/
-bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
StyleString* outStyleString) {
std::vector<Span> spanStack;
@@ -74,9 +53,9 @@
outStyleString->spans.clear();
util::StringBuilder builder;
size_t depth = 1;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- const XmlPullParser::Event event = parser->getEvent();
- if (event == XmlPullParser::Event::kEndElement) {
+ while (xml::XmlPullParser::isGoodEvent(parser->next())) {
+ const xml::XmlPullParser::Event event = parser->getEvent();
+ if (event == xml::XmlPullParser::Event::kEndElement) {
if (!parser->getElementNamespace().empty()) {
// We already warned and skipped the start element, so just skip here too
continue;
@@ -91,11 +70,11 @@
outStyleString->spans.push_back(spanStack.back());
spanStack.pop_back();
- } else if (event == XmlPullParser::Event::kText) {
+ } else if (event == xml::XmlPullParser::Event::kText) {
outRawString->append(parser->getText());
builder.append(parser->getText());
- } else if (event == XmlPullParser::Event::kStartElement) {
+ } else if (event == xml::XmlPullParser::Event::kStartElement) {
if (!parser->getElementNamespace().empty()) {
if (parser->getElementNamespace() != sXliffNamespaceUri) {
// Only warn if this isn't an xliff namespace.
@@ -128,7 +107,7 @@
spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
}
- } else if (event == XmlPullParser::Event::kComment) {
+ } else if (event == xml::XmlPullParser::Event::kComment) {
// Skip
} else {
assert(false);
@@ -140,11 +119,11 @@
return !error;
}
-bool ResourceParser::parse(XmlPullParser* parser) {
+bool ResourceParser::parse(xml::XmlPullParser* parser) {
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip comments and text.
continue;
}
@@ -159,7 +138,7 @@
break;
};
- if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "xml parser error: " << parser->getLastError());
return false;
@@ -167,10 +146,11 @@
return !error;
}
-static bool shouldStripResource(XmlPullParser* parser, const Maybe<std::u16string> productToMatch) {
- assert(parser->getEvent() == XmlPullParser::Event::kStartElement);
+static bool shouldStripResource(const xml::XmlPullParser* parser,
+ const Maybe<std::u16string> productToMatch) {
+ assert(parser->getEvent() == xml::XmlPullParser::Event::kStartElement);
- if (Maybe<StringPiece16> maybeProduct = findNonEmptyAttribute(parser, u"product")) {
+ if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) {
if (!productToMatch) {
if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") {
// We didn't specify a product and this is not a default product, so skip.
@@ -229,20 +209,20 @@
return !error;
}
-bool ResourceParser::parseResources(XmlPullParser* parser) {
+bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
std::set<ResourceName> strippedResources;
bool error = false;
std::u16string comment;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- const XmlPullParser::Event event = parser->getEvent();
- if (event == XmlPullParser::Event::kComment) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ const xml::XmlPullParser::Event event = parser->getEvent();
+ if (event == xml::XmlPullParser::Event::kComment) {
comment = parser->getComment();
continue;
}
- if (event == XmlPullParser::Event::kText) {
+ if (event == xml::XmlPullParser::Event::kText) {
if (!util::trimWhitespace(parser->getText()).empty()) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "plain text not allowed here");
@@ -251,7 +231,7 @@
continue;
}
- assert(event == XmlPullParser::Event::kStartElement);
+ assert(event == xml::XmlPullParser::Event::kStartElement);
if (!parser->getElementNamespace().empty()) {
// Skip unknown namespace.
@@ -266,7 +246,7 @@
if (elementName == u"item") {
// Items simply have their type encoded in the type attribute.
- if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) {
+ if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) {
elementName = maybeType.value().toString();
} else {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
@@ -280,7 +260,7 @@
parsedResource.source = mSource.withLine(parser->getLineNumber());
parsedResource.comment = std::move(comment);
- if (Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name")) {
+ if (Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name")) {
parsedResource.name.entry = maybeName.value().toString();
} else if (elementName != u"public-group") {
@@ -403,7 +383,7 @@
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
-std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
+std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
const bool allowRawValue) {
const size_t beginXmlLine = parser->getLineNumber();
@@ -432,10 +412,7 @@
if (processedItem) {
// Fix up the reference.
if (Reference* ref = valueCast<Reference>(processedItem.get())) {
- if (Maybe<ResourceName> transformedName =
- parser->transformPackage(ref->name.value(), u"")) {
- ref->name = std::move(transformedName);
- }
+ transformReferenceFromNamespace(parser, u"", ref);
}
return processedItem;
}
@@ -456,11 +433,11 @@
return {};
}
-bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
bool formatted = true;
- if (Maybe<StringPiece16> formattedAttr = findAttribute(parser, u"formatted")) {
+ if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) {
if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) {
mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean");
return false;
@@ -468,7 +445,7 @@
}
bool translateable = mOptions.translatable;
- if (Maybe<StringPiece16> translateableAttr = findAttribute(parser, u"translatable")) {
+ if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
mDiag->error(DiagMessage(source)
<< "invalid value for 'translatable'. Must be a boolean");
@@ -495,7 +472,7 @@
return true;
}
-bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseColor(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
@@ -506,7 +483,7 @@
return true;
}
-bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
uint32_t typeMask = 0;
@@ -540,10 +517,10 @@
return true;
}
-bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
if (!maybeType) {
mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute");
return false;
@@ -558,7 +535,7 @@
outResource->name.type = *parsedType;
- if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) {
+ if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) {
android::Res_value val;
bool result = android::ResTable::stringToInt(maybeId.value().data(),
maybeId.value().size(), &val);
@@ -580,10 +557,10 @@
return true;
}
-bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
if (!maybeType) {
mDiag->error(DiagMessage(source) << "<public-group> must have a 'type' attribute");
return false;
@@ -596,7 +573,7 @@
return false;
}
- Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"first-id");
+ Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id");
if (!maybeId) {
mDiag->error(DiagMessage(source) << "<public-group> must have a 'first-id' attribute");
return false;
@@ -615,11 +592,11 @@
std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
comment = util::trimWhitespace(parser->getComment()).toString();
continue;
- } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text.
continue;
}
@@ -628,20 +605,20 @@
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"public") {
- Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
error = true;
continue;
}
- if (findNonEmptyAttribute(parser, u"id")) {
+ if (xml::findNonEmptyAttribute(parser, u"id")) {
mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
error = true;
continue;
}
- if (findNonEmptyAttribute(parser, u"type")) {
+ if (xml::findNonEmptyAttribute(parser, u"type")) {
mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
error = true;
continue;
@@ -666,10 +643,10 @@
return !error;
}
-bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
if (!maybeType) {
mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a "
"'type' attribute");
@@ -715,17 +692,16 @@
return mask;
}
-
-
-bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
outResource->source = mSource.withLine(parser->getLineNumber());
return parseAttrImpl(parser, outResource, false);
}
-bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) {
+bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+ bool weak) {
uint32_t typeMask = 0;
- Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format");
+ Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format");
if (maybeFormat) {
typeMask = parseFormatAttribute(maybeFormat.value());
if (typeMask == 0) {
@@ -735,18 +711,6 @@
}
}
- // If this is a declaration, the package name may be in the name. Separate these out.
- // Eg. <attr name="android:text" />
- // No format attribute is allowed.
- if (weak && !maybeFormat) {
- StringPiece16 package, type, name;
- ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name);
- if (type.empty() && !package.empty()) {
- outResource->name.package = package.toString();
- outResource->name.entry = name.toString();
- }
- }
-
struct SymbolComparator {
bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
return a.symbol.name.value() < b.symbol.name.value();
@@ -758,11 +722,11 @@
std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
comment = util::trimWhitespace(parser->getComment()).toString();
continue;
- } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text.
continue;
}
@@ -834,17 +798,17 @@
return true;
}
-Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser,
+Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser,
const StringPiece16& tag) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
return {};
}
- Maybe<StringPiece16> maybeValue = findNonEmptyAttribute(parser, u"value");
+ Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value");
if (!maybeValue) {
mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
return {};
@@ -863,12 +827,19 @@
val.data };
}
-static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
+static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) {
str = util::trimWhitespace(str);
- const char16_t* const start = str.data();
+ const char16_t* start = str.data();
const char16_t* const end = start + str.size();
const char16_t* p = start;
+ Reference ref;
+ if (p != end && *p == u'*') {
+ ref.privateReference = true;
+ start++;
+ p++;
+ }
+
StringPiece16 package;
StringPiece16 name;
while (p != end) {
@@ -880,28 +851,27 @@
p++;
}
- return ResourceName(package.toString(), ResourceType::kAttr,
+ ref.name = ResourceName(package.toString(), ResourceType::kAttr,
name.empty() ? str.toString() : name.toString());
+ return Maybe<Reference>(std::move(ref));
}
-bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
+bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
return false;
}
- Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value());
+ Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value());
if (!maybeKey) {
mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
return false;
}
- if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) {
- maybeKey = std::move(transformedName);
- }
+ transformReferenceFromNamespace(parser, u"", &maybeKey.value());
std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
if (!value) {
@@ -909,15 +879,15 @@
return false;
}
- style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) });
+ style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) });
return true;
}
-bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Style> style = util::make_unique<Style>();
- Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent");
+ Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent");
if (maybeParent) {
// If the parent is empty, we don't have a parent, but we also don't infer either.
if (!maybeParent.value().empty()) {
@@ -928,10 +898,9 @@
return false;
}
- if (Maybe<ResourceName> transformedName =
- parser->transformPackage(style->parent.value().name.value(), u"")) {
- style->parent.value().name = std::move(transformedName);
- }
+ // Transform the namespace prefix to the actual package name, and mark the reference as
+ // private if appropriate.
+ transformReferenceFromNamespace(parser, u"", &style->parent.value());
}
} else {
@@ -940,15 +909,15 @@
size_t pos = styleName.find_last_of(u'.');
if (pos != std::string::npos) {
style->parentInferred = true;
- style->parent = Reference(
- ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
+ style->parent = Reference(ResourceName({}, ResourceType::kStyle,
+ styleName.substr(0, pos)));
}
}
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
continue;
}
@@ -973,15 +942,15 @@
return true;
}
-bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResource,
+bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource,
uint32_t typeMask) {
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Array> array = util::make_unique<Array>();
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
continue;
}
@@ -1014,14 +983,14 @@
return true;
}
-bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
continue;
}
@@ -1030,16 +999,15 @@
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"item") {
- const auto endAttrIter = parser->endAttributes();
- auto attrIter = parser->findAttribute(u"", u"quantity");
- if (attrIter == endAttrIter || attrIter->value.empty()) {
+ Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity");
+ if (!maybeQuantity) {
mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
<< "'quantity'");
error = true;
continue;
}
- StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
+ StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
size_t index = 0;
if (trimmedQuantity == u"zero") {
index = Plural::Zero;
@@ -1089,7 +1057,7 @@
return true;
}
-bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
@@ -1099,11 +1067,11 @@
std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
comment = util::trimWhitespace(parser->getComment()).toString();
continue;
- } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Ignore text.
continue;
}
@@ -1112,17 +1080,29 @@
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"attr") {
- const auto endAttrIter = parser->endAttributes();
- auto attrIter = parser->findAttribute(u"", u"name");
- if (attrIter == endAttrIter || attrIter->value.empty()) {
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+ if (!maybeName) {
mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
error = true;
continue;
}
+ // If this is a declaration, the package name may be in the name. Separate these out.
+ // Eg. <attr name="android:text" />
+ Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value());
+ if (!maybeRef) {
+ mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
+ << maybeName.value() << "'");
+ error = true;
+ continue;
+ }
+
+ Reference& childRef = maybeRef.value();
+ xml::transformReferenceFromNamespace(parser, u"", &childRef);
+
// Create the ParsedResource that will add the attribute to the table.
ParsedResource childResource;
- childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
+ childResource.name = childRef.name.value();
childResource.source = itemSource;
childResource.comment = std::move(comment);
@@ -1132,7 +1112,6 @@
}
// Create the reference to this attribute.
- Reference childRef(childResource.name);
childRef.setComment(childResource.comment);
childRef.setSource(itemSource);
styleable->entries.push_back(std::move(childRef));
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 18101ee..1150758 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -22,10 +22,9 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "StringPool.h"
-#include "XmlPullParser.h"
-
#include "util/Maybe.h"
#include "util/StringPiece.h"
+#include "xml/XmlPullParser.h"
#include <memory>
@@ -57,7 +56,7 @@
ResourceParser(const ResourceParser&) = delete; // No copy.
- bool parse(XmlPullParser* parser);
+ bool parse(xml::XmlPullParser* parser);
private:
/*
@@ -66,7 +65,7 @@
* contains the escaped and whitespace trimmed text, while `outRawString`
* contains the unescaped text. Returns true on success.
*/
- bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+ bool flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
StyleString* outStyleString);
/*
@@ -75,24 +74,25 @@
* If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
* RawString is returned. Otherwise this returns false;
*/
- std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask,
+ std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
const bool allowRawValue);
- bool parseResources(XmlPullParser* parser);
- bool parseString(XmlPullParser* parser, ParsedResource* outResource);
- bool parseColor(XmlPullParser* parser, ParsedResource* outResource);
- bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource);
- bool parsePublic(XmlPullParser* parser, ParsedResource* outResource);
- bool parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource);
- bool parseSymbol(XmlPullParser* parser, ParsedResource* outResource);
- bool parseAttr(XmlPullParser* parser, ParsedResource* outResource);
- bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak);
- Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
- bool parseStyle(XmlPullParser* parser, ParsedResource* outResource);
- bool parseStyleItem(XmlPullParser* parser, Style* style);
- bool parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource);
- bool parseArray(XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
- bool parsePlural(XmlPullParser* parser, ParsedResource* outResource);
+ bool parseResources(xml::XmlPullParser* parser);
+ bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseColor(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak);
+ Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
+ const StringPiece16& tag);
+ bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
+ bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
+ bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
IDiagnostics* mDiag;
ResourceTable* mTable;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index b59eb95..ab16424 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -18,9 +18,8 @@
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "XmlPullParser.h"
-
#include "test/Context.h"
+#include "xml/XmlPullParser.h"
#include <gtest/gtest.h>
#include <sstream>
@@ -36,7 +35,7 @@
input << "<attr name=\"foo\"/>" << std::endl;
ResourceTable table;
ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
- XmlPullParser xmlParser(input);
+ xml::XmlPullParser xmlParser(input);
ASSERT_FALSE(parser.parse(&xmlParser));
}
@@ -56,7 +55,7 @@
parserOptions.product = product;
ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {},
parserOptions);
- XmlPullParser xmlParser(input);
+ xml::XmlPullParser xmlParser(input);
if (parser.parse(&xmlParser)) {
return ::testing::AssertionSuccess();
}
@@ -360,6 +359,25 @@
EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
}
+TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
+ std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
+ " <attr name=\"*android:bar\" />\n"
+ " <attr name=\"privAndroid:bat\" />\n"
+ "</declare-styleable>";
+ ASSERT_TRUE(testParse(input));
+ Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+ ASSERT_NE(nullptr, styleable);
+ ASSERT_EQ(2u, styleable->entries.size());
+
+ EXPECT_TRUE(styleable->entries[0].privateReference);
+ AAPT_ASSERT_TRUE(styleable->entries[0].name);
+ EXPECT_EQ(std::u16string(u"android"), styleable->entries[0].name.value().package);
+
+ EXPECT_TRUE(styleable->entries[1].privateReference);
+ AAPT_ASSERT_TRUE(styleable->entries[1].name);
+ EXPECT_EQ(std::u16string(u"android"), styleable->entries[1].name.value().package);
+}
+
TEST_F(ResourceParserTest, ParseArray) {
std::string input = "<array name=\"foo\">\n"
" <item>@string/ref</item>\n"
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index b1a4c7d..ffe6595 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -15,6 +15,7 @@
*/
#include "ResourceUtils.h"
+#include "flatten/ResourceTypeExtensions.h"
#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
@@ -47,6 +48,42 @@
return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
}
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
+ size_t offset = 0;
+ bool priv = false;
+ if (str.data()[0] == u'*') {
+ priv = true;
+ offset = 1;
+ }
+
+ StringPiece16 package;
+ StringPiece16 type;
+ StringPiece16 entry;
+ if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
+ return false;
+ }
+
+ const ResourceType* parsedType = parseResourceType(type);
+ if (!parsedType) {
+ return false;
+ }
+
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (outRef) {
+ outRef->package = package;
+ outRef->type = *parsedType;
+ outRef->entry = entry;
+ }
+
+ if (outPrivate) {
+ *outPrivate = priv;
+ }
+ return true;
+}
+
bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
bool* outPrivate) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
@@ -61,35 +98,24 @@
if (trimmedStr.data()[1] == u'+') {
create = true;
offset += 1;
- } else if (trimmedStr.data()[1] == u'*') {
- priv = true;
- offset += 1;
}
- StringPiece16 package;
- StringPiece16 type;
- StringPiece16 entry;
- if (!extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
- &package, &type, &entry)) {
+
+ ResourceNameRef name;
+ if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
+ &name, &priv)) {
return false;
}
- const ResourceType* parsedType = parseResourceType(type);
- if (!parsedType) {
+ if (create && priv) {
return false;
}
- if (entry.empty()) {
- return false;
- }
-
- if (create && *parsedType != ResourceType::kId) {
+ if (create && name.type != ResourceType::kId) {
return false;
}
if (outRef) {
- outRef->package = package;
- outRef->type = *parsedType;
- outRef->entry = entry;
+ *outRef = name;
}
if (outCreate) {
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 34daa66..f93a4c7 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -39,6 +39,13 @@
bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
StringPiece16* outType, StringPiece16* outEntry);
+/**
+ * Returns true if the string was parsed as a resource name ([*][package:]type/name), with
+ * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
+ * was present.
+ */
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, bool* outPrivate);
+
/*
* Returns true if the string was parsed as a reference (@[+][package:]type/name), with
* `outReference` set to the parsed reference.
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 3d2a6e1..4bbfc32 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -16,15 +16,30 @@
#include "Resource.h"
#include "ResourceUtils.h"
-
#include "test/Common.h"
#include <gtest/gtest.h>
namespace aapt {
+TEST(ResourceUtilsTest, ParseResourceName) {
+ ResourceNameRef actual;
+ bool actualPriv = false;
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"android:color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+ EXPECT_FALSE(actualPriv);
+
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, u"foo"), actual);
+ EXPECT_FALSE(actualPriv);
+
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+ EXPECT_TRUE(actualPriv);
+}
+
TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
- ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
+ ResourceNameRef expected({}, ResourceType::kColor, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -35,7 +50,7 @@
}
TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
- ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+ ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -47,7 +62,7 @@
}
TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
- ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+ ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -59,7 +74,7 @@
}
TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
- ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+ ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -71,7 +86,7 @@
}
TEST(ResourceUtilsTest, ParsePrivateReference) {
- ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+ ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -111,8 +126,8 @@
}
TEST(ResourceUtilsTest, ParseStyleParentReference) {
- const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
- const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
+ const ResourceName kAndroidStyleFooName(u"android", ResourceType::kStyle, u"foo");
+ const ResourceName kStyleFooName({}, ResourceType::kStyle, u"foo");
std::string errStr;
Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 8acff0d..5550f19 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -71,27 +71,23 @@
}
bool Reference::flatten(android::Res_value* outValue) const {
- outValue->dataType = (referenceType == Reference::Type::kResource)
- ? android::Res_value::TYPE_REFERENCE
- : android::Res_value::TYPE_ATTRIBUTE;
+ outValue->dataType = (referenceType == Reference::Type::kResource) ?
+ android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
outValue->data = util::hostToDevice32(id ? id.value().id : 0);
return true;
}
Reference* Reference::clone(StringPool* /*newPool*/) const {
- Reference* ref = new Reference();
- ref->mComment = mComment;
- ref->mSource = mSource;
- ref->referenceType = referenceType;
- ref->name = name;
- ref->id = id;
- return ref;
+ return new Reference(*this);
}
void Reference::print(std::ostream* out) const {
*out << "(reference) ";
if (referenceType == Reference::Type::kResource) {
*out << "@";
+ if (privateReference) {
+ *out << "*";
+ }
} else {
*out << "?";
}
@@ -116,10 +112,7 @@
}
Id* Id::clone(StringPool* /*newPool*/) const {
- Id* id = new Id();
- id->mComment = mComment;
- id->mSource = mSource;
- return id;
+ return new Id(*this);
}
void Id::print(std::ostream* out) const {
@@ -214,10 +207,7 @@
}
BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
- BinaryPrimitive* bp = new BinaryPrimitive(value);
- bp->mComment = mComment;
- bp->mSource = mSource;
- return bp;
+ return new BinaryPrimitive(*this);
}
void BinaryPrimitive::print(std::ostream* out) const {
@@ -255,12 +245,7 @@
}
Attribute* Attribute::clone(StringPool* /*newPool*/) const {
- Attribute* attr = new Attribute(weak);
- attr->mComment = mComment;
- attr->mSource = mSource;
- attr->typeMask = typeMask;
- std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
- return attr;
+ return new Attribute(*this);
}
void Attribute::printMask(std::ostream* out) const {
@@ -450,11 +435,7 @@
}
Styleable* Styleable::clone(StringPool* /*newPool*/) const {
- Styleable* styleable = new Styleable();
- styleable->mComment = mComment;
- styleable->mSource = mSource;
- std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
- return styleable;
+ return new Styleable(*this);
}
void Styleable::print(std::ostream* out) const {
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 39088bc..17a658e 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -19,9 +19,6 @@
#include "Flags.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
-#include "XmlDom.h"
-#include "XmlPullParser.h"
-
#include "compile/IdAssigner.h"
#include "compile/Png.h"
#include "compile/XmlIdCollector.h"
@@ -31,6 +28,8 @@
#include "util/Files.h"
#include "util/Maybe.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlPullParser.h"
#include <fstream>
#include <string>
@@ -131,7 +130,7 @@
// Parse the values file from XML.
- XmlPullParser xmlParser(fin);
+ xml::XmlPullParser xmlParser(fin);
ResourceParserOptions parserOptions;
parserOptions.product = options.product;
@@ -191,7 +190,7 @@
static bool compileXml(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& pathData, const std::string& outputPath) {
- std::unique_ptr<XmlResource> xmlRes;
+ std::unique_ptr<xml::XmlResource> xmlRes;
{
std::ifstream fin(pathData.source.path, std::ifstream::binary);
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
index dfdf710..f40689e 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -16,9 +16,8 @@
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "XmlDom.h"
-
#include "compile/XmlIdCollector.h"
+#include "xml/XmlDom.h"
#include <algorithm>
#include <vector>
@@ -61,7 +60,7 @@
} // namespace
-bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) {
+bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
xmlRes->file.exportedSymbols.clear();
IdCollector collector(&xmlRes->file.exportedSymbols);
xmlRes->root->accept(&collector);
diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h
index 96a58f2..1b14944 100644
--- a/tools/aapt2/compile/XmlIdCollector.h
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -18,11 +18,12 @@
#define AAPT_XMLIDCOLLECTOR_H
#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
namespace aapt {
struct XmlIdCollector : public IXmlResourceConsumer {
- bool consume(IAaptContext* context, XmlResource* xmlRes) override;
+ bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
};
} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index c703f451..45b7af2 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -26,7 +26,7 @@
TEST(XmlIdCollectorTest, CollectsIds) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/foo"
text="@+id/bar">
@@ -50,7 +50,7 @@
TEST(XmlIdCollectorTest, DontCollectNonIds) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
XmlIdCollector collector;
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index c1ff556..acf5bb5 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -62,7 +62,7 @@
* A raw string value that hasn't had its escape sequences
* processed nor whitespace removed.
*/
- TYPE_RAW_STRING = 0xfe
+ TYPE_RAW_STRING = 0xfe,
};
};
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 6b90fb2..636e977 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -78,10 +78,18 @@
explicit SymbolWriter(StringPool* pool) : mPool(pool) {
}
- void addSymbol(const ResourceNameRef& name, size_t offset) {
- symbols.push_back(Entry{ mPool->makeRef(name.package.toString() + u":" +
- toString(name.type).toString() + u"/" +
- name.entry.toString()), offset });
+ void addSymbol(const Reference& ref, size_t offset) {
+ const ResourceName& name = ref.name.value();
+ std::u16string fullName;
+ if (ref.privateReference) {
+ fullName += u"*";
+ }
+
+ if (!name.package.empty()) {
+ fullName += name.package + u":";
+ }
+ fullName += toString(name.type).toString() + u"/" + name.entry;
+ symbols.push_back(Entry{ mPool->makeRef(fullName), offset });
}
void shiftAllOffsets(size_t offset) {
@@ -100,20 +108,23 @@
SymbolWriter* mSymbols;
FlatEntry* mEntry;
BigBuffer* mBuffer;
+ bool mUseExtendedChunks;
size_t mEntryCount = 0;
Maybe<uint32_t> mParentIdent;
Maybe<ResourceNameRef> mParentName;
- MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) :
- mSymbols(symbols), mEntry(entry), mBuffer(buffer) {
+ MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
+ bool useExtendedChunks) :
+ mSymbols(symbols), mEntry(entry), mBuffer(buffer),
+ mUseExtendedChunks(useExtendedChunks) {
}
void flattenKey(Reference* key, ResTable_map* outEntry) {
- if (!key->id) {
+ if (!key->id || (key->privateReference && mUseExtendedChunks)) {
assert(key->name && "reference must have a name");
outEntry->name.ident = util::hostToDevice32(0);
- mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+ mSymbols->addSymbol(*key, (mBuffer->size() - sizeof(ResTable_map)) +
offsetof(ResTable_map, name));
} else {
outEntry->name.ident = util::hostToDevice32(key->id.value().id);
@@ -121,16 +132,21 @@
}
void flattenValue(Item* value, ResTable_map* outEntry) {
+ bool privateRef = false;
if (Reference* ref = valueCast<Reference>(value)) {
- if (!ref->id) {
+ privateRef = ref->privateReference && mUseExtendedChunks;
+ if (!ref->id || privateRef) {
assert(ref->name && "reference must have a name");
- mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+ mSymbols->addSymbol(*ref, (mBuffer->size() - sizeof(ResTable_map)) +
offsetof(ResTable_map, value) + offsetof(Res_value, data));
}
}
bool result = value->flatten(&outEntry->value);
+ if (privateRef) {
+ outEntry->value.data = 0;
+ }
assert(result && "flatten failed");
}
@@ -169,7 +185,8 @@
void visit(Style* style) override {
if (style->parent) {
- if (!style->parent.value().id) {
+ bool privateRef = style->parent.value().privateReference && mUseExtendedChunks;
+ if (!style->parent.value().id || privateRef) {
assert(style->parent.value().name && "reference must have a name");
mParentName = style->parent.value().name;
} else {
@@ -339,21 +356,28 @@
bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
if (Item* item = valueCast<Item>(entry->value)) {
writeEntry<ResTable_entry, true>(entry, buffer);
+ bool privateRef = false;
if (Reference* ref = valueCast<Reference>(entry->value)) {
- if (!ref->id) {
+ // If there is no ID or the reference is private and we allow extended chunks,
+ // write out a 0 and mark the symbol table with the name of the reference.
+ privateRef = (ref->privateReference && mOptions.useExtendedChunks);
+ if (!ref->id || privateRef) {
assert(ref->name && "reference must have at least a name");
- mSymbols->addSymbol(ref->name.value(),
- buffer->size() + offsetof(Res_value, data));
+ mSymbols->addSymbol(*ref, buffer->size() + offsetof(Res_value, data));
}
}
Res_value* outValue = buffer->nextBlock<Res_value>();
bool result = item->flatten(outValue);
assert(result && "flatten failed");
+ if (privateRef) {
+ // Force the value of 0 so we look up the symbol at unflatten time.
+ outValue->data = 0;
+ }
outValue->size = util::hostToDevice16(sizeof(*outValue));
} else {
const size_t beforeEntry = buffer->size();
ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
- MapFlattenVisitor visitor(mSymbols, entry, buffer);
+ MapFlattenVisitor visitor(mSymbols, entry, buffer, mOptions.useExtendedChunks);
entry->value->accept(&visitor);
outEntry->count = util::hostToDevice32(visitor.mEntryCount);
if (visitor.mParentName) {
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 68a1f47..4ffb980 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -15,11 +15,11 @@
*/
#include "flatten/TableFlattener.h"
+#include "test/Builders.h"
+#include "test/Context.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Util.h"
-#include "test/Builders.h"
-#include "test/Context.h"
#include <gtest/gtest.h>
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index 4efb08b..8219462 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -15,15 +15,14 @@
*/
#include "SdkConstants.h"
-#include "XmlDom.h"
-
#include "flatten/ChunkWriter.h"
#include "flatten/ResourceTypeExtensions.h"
#include "flatten/XmlFlattener.h"
+#include "xml/XmlDom.h"
#include <androidfw/ResourceTypes.h>
-#include <vector>
#include <utils/misc.h>
+#include <vector>
using namespace android;
@@ -306,7 +305,7 @@
return true;
}
-bool XmlFlattener::consume(IAaptContext* context, XmlResource* resource) {
+bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
if (!resource->root) {
return false;
}
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
index b1fb3a7..a688ac9 100644
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -17,16 +17,12 @@
#ifndef AAPT_FLATTEN_XMLFLATTENER_H
#define AAPT_FLATTEN_XMLFLATTENER_H
-#include "util/BigBuffer.h"
-
#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+#include "xml/XmlDom.h"
namespace aapt {
-namespace xml {
-struct Node;
-}
-
struct XmlFlattenerOptions {
/**
* Keep attribute raw string values along with typed values.
@@ -45,7 +41,7 @@
mBuffer(buffer), mOptions(options) {
}
- bool consume(IAaptContext* context, XmlResource* resource) override;
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
private:
BigBuffer* mBuffer;
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index 318bcdd..8648879 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -16,11 +16,10 @@
#include "flatten/XmlFlattener.h"
#include "link/Linkers.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
#include "test/Builders.h"
#include "test/Context.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
#include <gtest/gtest.h>
@@ -45,7 +44,7 @@
.build();
}
- ::testing::AssertionResult flatten(XmlResource* doc, android::ResXMLTree* outTree,
+ ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
XmlFlattenerOptions options = {}) {
BigBuffer buffer(1024);
XmlFlattener flattener(&buffer, options);
@@ -65,7 +64,7 @@
};
TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:test="http://com.test"
attr="hey">
<Layout test:hello="hi" />
@@ -144,7 +143,7 @@
}
TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingStart="1dp"
android:colorAccent="#ffffff"/>)EOF");
@@ -169,7 +168,7 @@
}
TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/id"
class="str"
@@ -192,7 +191,7 @@
* namespace.
*/
TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
android::ResXMLTree tree;
ASSERT_TRUE(flatten(doc.get(), &tree));
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index d5a2b38..da96b84 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -17,12 +17,10 @@
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "XmlPullParser.h"
-
#include "java/AnnotationProcessor.h"
-
#include "test/Builders.h"
#include "test/Context.h"
+#include "xml/XmlPullParser.h"
#include <gtest/gtest.h>
@@ -42,7 +40,7 @@
options);
std::stringstream in;
in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
- XmlPullParser xmlParser(in);
+ xml::XmlPullParser xmlParser(in);
if (parser.parse(&xmlParser)) {
return ::testing::AssertionSuccess();
}
diff --git a/tools/aapt2/java/ClassDefinitionWriter.h b/tools/aapt2/java/ClassDefinitionWriter.h
index b8886f9..04e1274 100644
--- a/tools/aapt2/java/ClassDefinitionWriter.h
+++ b/tools/aapt2/java/ClassDefinitionWriter.h
@@ -17,6 +17,7 @@
#ifndef AAPT_JAVA_CLASSDEFINITION_H
#define AAPT_JAVA_CLASSDEFINITION_H
+#include "Resource.h"
#include "java/AnnotationProcessor.h"
#include "util/StringPiece.h"
#include "util/Util.h"
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index d963d89..a9b4c14 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -15,12 +15,11 @@
*/
#include "Source.h"
-#include "XmlDom.h"
-
#include "java/AnnotationProcessor.h"
#include "java/ClassDefinitionWriter.h"
#include "java/ManifestClassGenerator.h"
#include "util/Maybe.h"
+#include "xml/XmlDom.h"
#include <algorithm>
@@ -80,7 +79,7 @@
}
bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package,
- XmlResource* res, std::ostream* out) {
+ xml::XmlResource* res, std::ostream* out) {
xml::Element* el = xml::findRootElement(res->root.get());
if (!el) {
return false;
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
index 0f0998f..226ed23 100644
--- a/tools/aapt2/java/ManifestClassGenerator.h
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -18,15 +18,15 @@
#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
#include "Diagnostics.h"
-#include "process/IResourceTableConsumer.h"
#include "util/StringPiece.h"
+#include "xml/XmlDom.h"
#include <iostream>
namespace aapt {
struct ManifestClassGenerator {
- bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res,
+ bool generate(IDiagnostics* diag, const StringPiece16& package, xml::XmlResource* res,
std::ostream* out);
};
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index 4081287..fc57ae6f 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -15,7 +15,6 @@
*/
#include "java/ManifestClassGenerator.h"
-
#include "test/Builders.h"
#include "test/Context.h"
@@ -25,7 +24,7 @@
TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<permission android:name="android.permission.ACCESS_INTERNET" />
<permission android:name="android.DO_DANGEROUS_THINGS" />
@@ -75,7 +74,7 @@
TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required to access the internet.
Added in API 1. -->
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 4431477..c096854 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-#include "XmlDom.h"
-
#include "java/ProguardRules.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
#include <memory>
#include <string>
@@ -40,11 +39,11 @@
virtual void visit(xml::Element* node) override {
if (!node->namespaceUri.empty()) {
- Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+ Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace(
node->namespaceUri);
if (maybePackage) {
// This is a custom view, let's figure out the class name from this.
- std::u16string package = maybePackage.value() + u"." + node->name;
+ std::u16string package = maybePackage.value().package + u"." + node->name;
if (util::isJavaClassName(package)) {
addClass(node->lineNumber, package);
}
@@ -185,7 +184,8 @@
std::u16string mPackage;
};
-bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) {
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res,
+ KeepSet* keepSet) {
ManifestVisitor visitor(source, keepSet);
if (res->root) {
res->root->accept(&visitor);
@@ -194,7 +194,7 @@
return false;
}
-bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) {
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) {
if (!res->root) {
return false;
}
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index be61eb9..aafffd3 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -19,8 +19,7 @@
#include "Resource.h"
#include "Source.h"
-
-#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
#include <map>
#include <ostream>
@@ -47,8 +46,8 @@
std::map<std::u16string, std::set<Source>> mKeepMethodSet;
};
-bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet);
-bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet);
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 97be774..9850ae5 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -18,8 +18,6 @@
#include "Debug.h"
#include "Flags.h"
#include "NameMangler.h"
-#include "XmlDom.h"
-
#include "compile/IdAssigner.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
@@ -28,6 +26,7 @@
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
#include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
#include "link/ManifestFixer.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
@@ -36,6 +35,7 @@
#include "unflatten/FileExportHeaderReader.h"
#include "util/Files.h"
#include "util/StringPiece.h"
+#include "xml/XmlDom.h"
#include <fstream>
#include <sys/stat.h>
@@ -159,7 +159,7 @@
/**
* Inflates an XML file from the source path.
*/
- std::unique_ptr<XmlResource> loadXml(const std::string& path) {
+ std::unique_ptr<xml::XmlResource> loadXml(const std::string& path) {
std::ifstream fin(path, std::ifstream::binary);
if (!fin) {
mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
@@ -172,7 +172,7 @@
/**
* Inflates a binary XML file from the source path.
*/
- std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
+ std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
// Read header for symbol info and export info.
std::string errorStr;
Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
@@ -188,7 +188,7 @@
return {};
}
- std::unique_ptr<XmlResource> xmlRes = xml::inflate(
+ std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
(const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
maybeF.value().getDataLength() - offset,
mContext.getDiagnostics(), Source(path));
@@ -245,7 +245,7 @@
return true;
}
- Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
+ Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
// Make sure the first element is <manifest> with package attribute.
if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
@@ -309,7 +309,7 @@
return true;
}
- bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+ bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
IArchiveWriter* writer) {
BigBuffer buffer(1024);
XmlFlattenerOptions options = {};
@@ -354,7 +354,7 @@
return true;
}
- bool writeManifestJavaFile(XmlResource* manifestXml) {
+ bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
if (!mOptions.generateJavaClassPath) {
return true;
}
@@ -502,7 +502,7 @@
int run(const std::vector<std::string>& inputFiles) {
// Load the AndroidManifest.xml
- std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+ std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath);
if (!manifestXml) {
return 1;
}
@@ -545,25 +545,21 @@
<< "with package ID " << std::hex << (int) mContext.mPackageId);
}
- bool error = false;
for (const std::string& input : inputFiles) {
if (!processFile(input, false)) {
- error = true;
+ mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+ return 1;
}
}
for (const std::string& input : mOptions.overlayFiles) {
if (!processFile(input, true)) {
- error = true;
+ mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
+ return 1;
}
}
- if (error) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
- return 1;
- }
-
if (!verifyNoExternalPackages()) {
return 1;
}
@@ -608,6 +604,7 @@
return 1;
}
+ bool error = false;
{
ManifestFixerOptions manifestFixerOptions;
manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
@@ -617,6 +614,11 @@
error = true;
}
+ // AndroidManifest.xml has no resource name, but the CallSite is built from the name
+ // (aka, which package the AndroidManifest.xml is coming from).
+ // So we give it a package name so it can see local resources.
+ manifestXml->file.name.package = mContext.getCompilationPackage().toString();
+
XmlReferenceLinker manifestLinker;
if (manifestLinker.consume(&mContext, manifestXml.get())) {
if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
@@ -640,14 +642,21 @@
}
}
+ if (error) {
+ mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
+ return 1;
+ }
+
for (const FileToProcess& file : mFilesToProcess) {
if (file.file.name.type != ResourceType::kRaw &&
util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "linking " << file.source.path);
+ mContext.getDiagnostics()->note(DiagMessage()
+ << "linking " << file.source.path);
}
- std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(file.source.path);
+ std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
+ file.source.path);
if (!xmlRes) {
return 1;
}
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 7b3fc35..4d3a483 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -17,7 +17,9 @@
#ifndef AAPT_LINKER_LINKERS_H
#define AAPT_LINKER_LINKERS_H
+#include "Resource.h"
#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
#include <set>
@@ -28,6 +30,14 @@
struct ConfigDescription;
/**
+ * Defines the location in which a value exists. This determines visibility of other
+ * package's private symbols.
+ */
+struct CallSite {
+ ResourceNameRef resource;
+};
+
+/**
* Determines whether a versioned resource should be created. If a versioned resource already
* exists, it takes precedence.
*/
@@ -39,7 +49,7 @@
};
struct XmlAutoVersioner : public IXmlResourceConsumer {
- bool consume(IAaptContext* context, XmlResource* resource) override;
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
};
/**
@@ -69,15 +79,6 @@
};
/**
- * Resolves all references to resources in the ResourceTable and assigns them IDs.
- * The ResourceTable must already have IDs assigned to each resource.
- * Once the ResourceTable is processed by this linker, it is ready to be flattened.
- */
-struct ReferenceLinker : public IResourceTableConsumer {
- bool consume(IAaptContext* context, ResourceTable* table) override;
-};
-
-/**
* Resolves attributes in the XmlResource and compiles string values to resource values.
* Once an XmlResource is processed by this linker, it is ready to be flattened.
*/
@@ -86,7 +87,7 @@
std::set<int> mSdkLevelsFound;
public:
- bool consume(IAaptContext* context, XmlResource* resource) override;
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
/**
* Once the XmlResource has been consumed, this returns the various SDK levels in which
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 52d9426..2034c57 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -15,10 +15,9 @@
*/
#include "ResourceUtils.h"
-#include "XmlDom.h"
-
#include "link/ManifestFixer.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
namespace aapt {
@@ -63,7 +62,7 @@
return true;
}
-bool ManifestFixer::consume(IAaptContext* context, XmlResource* doc) {
+bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
xml::Element* root = xml::findRootElement(doc->root.get());
if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
context->getDiagnostics()->error(DiagMessage(doc->file.source)
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 16e161d..a77e6d5 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -18,6 +18,10 @@
#define AAPT_LINK_MANIFESTFIXER_H
#include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "xml/XmlDom.h"
+
+#include <string>
namespace aapt {
@@ -36,7 +40,7 @@
ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
}
- bool consume(IAaptContext* context, XmlResource* doc) override;
+ bool consume(IAaptContext* context, xml::XmlResource* doc) override;
};
} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 5c5d8af..f6bf895 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -15,7 +15,6 @@
*/
#include "link/ManifestFixer.h"
-
#include "test/Builders.h"
#include "test/Context.h"
@@ -51,13 +50,13 @@
.build();
}
- std::unique_ptr<XmlResource> verify(const StringPiece& str) {
+ std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
return verifyWithOptions(str, {});
}
- std::unique_ptr<XmlResource> verifyWithOptions(const StringPiece& str,
- const ManifestFixerOptions& options) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(str);
+ std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str,
+ const ManifestFixerOptions& options) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
ManifestFixer fixer(options);
if (fixer.consume(mContext.get(), doc.get())) {
return doc;
@@ -88,7 +87,7 @@
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
- std::unique_ptr<XmlResource> doc = verifyWithOptions(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index 5a2f5f0..3c8af4f 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -15,7 +15,6 @@
*/
#include "ResourceTable.h"
-
#include "link/Linkers.h"
#include <algorithm>
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 8c924b5..4b82537 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -14,17 +14,18 @@
* limitations under the License.
*/
+#include "ReferenceLinker.h"
+
#include "Diagnostics.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "util/Util.h"
#include "ValueVisitor.h"
-
#include "link/Linkers.h"
-#include "link/ReferenceLinkerVisitor.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
#include <androidfw/ResourceTypes.h>
#include <cassert>
@@ -41,52 +42,15 @@
*
* NOTE: All of the entries in the ResourceTable must be assigned IDs.
*/
-class StyleAndReferenceLinkerVisitor : public ValueVisitor {
+class ReferenceLinkerVisitor : public ValueVisitor {
private:
- ReferenceLinkerVisitor mReferenceVisitor;
IAaptContext* mContext;
ISymbolTable* mSymbols;
- IPackageDeclStack* mPackageDecls;
+ xml::IPackageDeclStack* mPackageDecls;
StringPool* mStringPool;
+ CallSite* mCallSite;
bool mError = false;
- const ISymbolTable::Symbol* findAttributeSymbol(Reference* reference) {
- assert(reference);
- assert(reference->name || reference->id);
-
- if (reference->name) {
- // Transform the package name if it is an alias.
- Maybe<ResourceName> realName = mPackageDecls->transformPackage(
- reference->name.value(), mContext->getCompilationPackage());
-
- // Mangle the reference name if it should be mangled.
- Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
- realName ? realName.value() : reference->name.value());
-
- const ISymbolTable::Symbol* s = nullptr;
- if (mangledName) {
- s = mSymbols->findByName(mangledName.value());
- } else if (realName) {
- s = mSymbols->findByName(realName.value());
- } else {
- s = mSymbols->findByName(reference->name.value());
- }
-
- if (s && s->attribute) {
- return s;
- }
- }
-
- if (reference->id) {
- if (const ISymbolTable::Symbol* s = mSymbols->findById(reference->id.value())) {
- if (s->attribute) {
- return s;
- }
- }
- }
- return nullptr;
- }
-
/**
* Transform a RawString value into a more specific, appropriate value, based on the
* Attribute. If a non RawString value is passed in, this is an identity transform.
@@ -94,8 +58,8 @@
std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
const Attribute* attr) {
if (RawString* rawString = valueCast<RawString>(value.get())) {
- std::unique_ptr<Item> transformed = ResourceUtils::parseItemForAttribute(
- *rawString->value, attr);
+ std::unique_ptr<Item> transformed =
+ ResourceUtils::parseItemForAttribute(*rawString->value, attr);
// If we could not parse as any specific type, try a basic STRING.
if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
@@ -163,14 +127,16 @@
public:
using ValueVisitor::visit;
- StyleAndReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
- StringPool* stringPool, IPackageDeclStack* decl) :
- mReferenceVisitor(context, symbols, decl), mContext(context), mSymbols(symbols),
- mPackageDecls(decl), mStringPool(stringPool) {
+ ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, StringPool* stringPool,
+ xml::IPackageDeclStack* decl,CallSite* callSite) :
+ mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool),
+ mCallSite(callSite) {
}
- void visit(Reference* reference) override {
- mReferenceVisitor.visit(reference);
+ void visit(Reference* ref) override {
+ if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) {
+ mError = true;
+ }
}
/**
@@ -184,13 +150,26 @@
}
for (Style::Entry& entry : style->entries) {
- if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) {
+ std::string errStr;
+
+ // Transform the attribute reference so that it is using the fully qualified package
+ // name. This will also mark the reference as being able to see private resources if
+ // there was a '*' in the reference or if the package came from the private namespace.
+ Reference transformedReference = entry.key;
+ transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(),
+ &transformedReference);
+
+ // Find the attribute in the symbol table and check if it is visible from this callsite.
+ const ISymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility(
+ transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
+ if (symbol) {
// Assign our style key the correct ID.
- entry.key.id = s->id;
+ entry.key.id = symbol->id;
// Try to convert the value to a more specific, typed value based on the
// attribute it is set to.
- entry.value = parseValueWithAttribute(std::move(entry.value), s->attribute.get());
+ entry.value = parseValueWithAttribute(std::move(entry.value),
+ symbol->attribute.get());
// Link/resolve the final value (mostly if it's a reference).
entry.value->accept(this);
@@ -201,13 +180,13 @@
entry.value->flatten(&val);
// Always allow references.
- const uint32_t typeMask = s->attribute->typeMask |
+ const uint32_t typeMask = symbol->attribute->typeMask |
android::ResTable_map::TYPE_REFERENCE;
if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
// The actual type of this item is incompatible with the attribute.
DiagMessage msg(style->getSource());
- buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
+ buildAttributeMismatchMessage(&msg, symbol->attribute.get(), entry.value.get());
mContext->getDiagnostics()->error(msg);
mError = true;
}
@@ -219,23 +198,151 @@
} else {
msg << entry.key.id.value();
}
- msg << "' not found";
+ msg << "' " << errStr;
mContext->getDiagnostics()->error(msg);
+ mContext->getDiagnostics()->note(DiagMessage(style->getSource()) << entry.key);
mError = true;
}
}
}
- inline bool hasError() {
- return mError || mReferenceVisitor.hasError();
+ bool hasError() {
+ return mError;
}
};
-struct EmptyDeclStack : public IPackageDeclStack {
- Maybe<ResourceName> transformPackage(const ResourceName& name,
- const StringPiece16& localPackage) const override {
- if (name.package.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
+} // namespace
+
+/**
+ * The symbol is visible if it is public, or if the reference to it is requesting private access
+ * or if the callsite comes from the same package.
+ */
+bool ReferenceLinker::isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref,
+ const CallSite& callSite) {
+ if (!symbol.isPublic && !ref.privateReference) {
+ if (ref.name) {
+ return callSite.resource.package == ref.name.value().package;
+ } else if (ref.id) {
+ return ref.id.value().packageId() == symbol.id.packageId();
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference,
+ NameMangler* mangler,
+ ISymbolTable* symbols) {
+ if (reference.name) {
+ Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value());
+ return symbols->findByName(mangled ? mangled.value() : reference.name.value());
+ } else if (reference.id) {
+ return symbols->findById(reference.id.value());
+ } else {
+ return nullptr;
+ }
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility(
+ const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+ if (!symbol) {
+ std::stringstream errStr;
+ errStr << "not found";
+ if (outError) *outError = errStr.str();
+ return nullptr;
+ }
+
+ if (!isSymbolVisible(*symbol, reference, *callSite)) {
+ std::stringstream errStr;
+ errStr << "is private";
+ if (outError) *outError = errStr.str();
+ return nullptr;
+ }
+ return symbol;
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility(
+ const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const ISymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler,
+ symbols, callSite,
+ outError);
+ if (!symbol) {
+ return nullptr;
+ }
+
+ if (!symbol->attribute) {
+ std::stringstream errStr;
+ errStr << "is not an attribute";
+ if (outError) *outError = errStr.str();
+ return nullptr;
+ }
+ return symbol;
+}
+
+Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference,
+ NameMangler* nameMangler,
+ ISymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError) {
+ const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+ if (!symbol) {
+ return {};
+ }
+
+ if (!symbol->attribute) {
+ std::stringstream errStr;
+ errStr << "is not an attribute";
+ if (outError) *outError = errStr.str();
+ return {};
+ }
+ return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+}
+
+bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
+ ISymbolTable* symbols, xml::IPackageDeclStack* decls,
+ CallSite* callSite) {
+ assert(reference);
+ assert(reference->name || reference->id);
+
+ Reference transformedReference = *reference;
+ transformReferenceFromNamespace(decls, context->getCompilationPackage(),
+ &transformedReference);
+
+ std::string errStr;
+ const ISymbolTable::Symbol* s = resolveSymbolCheckVisibility(
+ transformedReference, context->getNameMangler(), symbols, callSite, &errStr);
+ if (s) {
+ reference->id = s->id;
+ return true;
+ }
+
+ DiagMessage errorMsg(reference->getSource());
+ errorMsg << "resource ";
+ if (reference->name) {
+ errorMsg << reference->name.value();
+ if (transformedReference.name.value() != reference->name.value()) {
+ errorMsg << " (aka " << transformedReference.name.value() << ")";
+ }
+ } else {
+ errorMsg << reference->id.value();
+ }
+
+ errorMsg << " " << errStr;
+ context->getDiagnostics()->error(errorMsg);
+ return false;
+}
+
+namespace {
+
+struct EmptyDeclStack : public xml::IPackageDeclStack {
+ Maybe<xml::ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override {
+ if (alias.empty()) {
+ return xml::ExtractedPackage{ localPackage.toString(), true /* private */ };
}
return {};
}
@@ -259,14 +366,16 @@
error = true;
}
+ CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) };
+ ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(),
+ &table->stringPool, &declStack, &callSite);
+
for (auto& configValue : entry->values) {
- StyleAndReferenceLinkerVisitor visitor(context,
- context->getExternalSymbols(),
- &table->stringPool, &declStack);
configValue.value->accept(&visitor);
- if (visitor.hasError()) {
- error = true;
- }
+ }
+
+ if (visitor.hasError()) {
+ error = true;
}
}
}
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
new file mode 100644
index 0000000..6f11d58
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef AAPT_LINKER_REFERENCELINKER_H
+#define AAPT_LINKER_REFERENCELINKER_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "link/Linkers.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "xml/XmlDom.h"
+
+#include <cassert>
+
+namespace aapt {
+
+/**
+ * Resolves all references to resources in the ResourceTable and assigns them IDs.
+ * The ResourceTable must already have IDs assigned to each resource.
+ * Once the ResourceTable is processed by this linker, it is ready to be flattened.
+ */
+struct ReferenceLinker : public IResourceTableConsumer {
+ /**
+ * Returns true if the symbol is visible by the reference and from the callsite.
+ */
+ static bool isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref,
+ const CallSite& callSite);
+
+ /**
+ * Performs name mangling and looks up the resource in the symbol table. Returns nullptr
+ * if the symbol was not found.
+ */
+ static const ISymbolTable::Symbol* resolveSymbol(const Reference& reference,
+ NameMangler* mangler, ISymbolTable* symbols);
+
+ /**
+ * Performs name mangling and looks up the resource in the symbol table. If the symbol is
+ * not visible by the reference at the callsite, nullptr is returned. outError holds
+ * the error message.
+ */
+ static const ISymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference,
+ NameMangler* nameMangler,
+ ISymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute.
+ * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute.
+ */
+ static const ISymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference,
+ NameMangler* nameMangler,
+ ISymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Resolves the attribute reference and returns an xml::AaptAttribute if successful.
+ * If resolution fails, outError holds the error message.
+ */
+ static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference,
+ NameMangler* nameMangler,
+ ISymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Transforms the package name of the reference to the fully qualified package name using
+ * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
+ * to the reference at the callsite, the reference is updated with an ID.
+ * Returns false on failure, and an error message is logged to the IDiagnostics in the context.
+ */
+ static bool linkReference(Reference* reference, IAaptContext* context, ISymbolTable* symbols,
+ xml::IPackageDeclStack* decls, CallSite* callSite);
+
+ /**
+ * Links all references in the ResourceTable.
+ */
+ bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_REFERENCELINKER_H */
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
deleted file mode 100644
index a4cb596..0000000
--- a/tools/aapt2/link/ReferenceLinkerVisitor.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2015 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.
- */
-
-#ifndef AAPT_LINKER_REFERENCELINKERVISITOR_H
-#define AAPT_LINKER_REFERENCELINKERVISITOR_H
-
-#include "Resource.h"
-#include "ResourceValues.h"
-#include "ValueVisitor.h"
-
-#include "process/IResourceTableConsumer.h"
-#include "process/SymbolTable.h"
-
-#include <cassert>
-
-namespace aapt {
-
-/**
- * The ReferenceLinkerVisitor will follow all references and make sure they point
- * to resources that actually exist in the given ISymbolTable.
- * Once the target resource has been found, the ID of the resource will be assigned
- * to the reference object.
- */
-class ReferenceLinkerVisitor : public ValueVisitor {
- using ValueVisitor::visit;
-private:
- IAaptContext* mContext;
- ISymbolTable* mSymbols;
- IPackageDeclStack* mPackageDecls;
- bool mError = false;
-
-public:
- ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, IPackageDeclStack* decls) :
- mContext(context), mSymbols(symbols), mPackageDecls(decls) {
- }
-
- /**
- * Lookup a reference and ensure it exists, either in our local table, or as an external
- * symbol. Once found, assign the ID of the target resource to this reference object.
- */
- void visit(Reference* reference) override {
- assert(reference);
- assert(reference->name || reference->id);
-
- // We prefer to lookup by name if the name is set. Otherwise it could be
- // an out-of-date ID.
- if (reference->name) {
- // Transform the package name if it is an alias.
- Maybe<ResourceName> realName = mPackageDecls->transformPackage(
- reference->name.value(), mContext->getCompilationPackage());
-
- // Mangle the reference name if it should be mangled.
- Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
- realName ? realName.value() : reference->name.value());
-
- const ISymbolTable::Symbol* s = nullptr;
- if (mangledName) {
- s = mSymbols->findByName(mangledName.value());
- } else if (realName) {
- s = mSymbols->findByName(realName.value());
- } else {
- s = mSymbols->findByName(reference->name.value());
- }
-
- if (s) {
- reference->id = s->id;
- return;
- }
-
- DiagMessage errorMsg(reference->getSource());
- errorMsg << "reference to " << reference->name.value();
- if (realName) {
- errorMsg << " (aka " << realName.value() << ")";
- }
- errorMsg << " was not found";
- mContext->getDiagnostics()->error(errorMsg);
- mError = true;
- return;
- }
-
- if (!mSymbols->findById(reference->id.value())) {
- mContext->getDiagnostics()->error(DiagMessage(reference->getSource())
- << "reference to " << reference->id.value()
- << " was not found");
- mError = true;
- }
- }
-
- inline bool hasError() {
- return mError;
- }
-};
-
-} // namespace aapt
-
-#endif /* AAPT_LINKER_REFERENCELINKERVISITOR_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index 5e7641a..8d324fe 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
#include "process/SymbolTable.h"
#include "test/Builders.h"
@@ -44,7 +44,7 @@
.setSymbolTable(JoinedSymbolTableBuilder()
.addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
.addSymbolTable(test::StaticSymbolTableBuilder()
- .addSymbol(u"@android:string/ok", ResourceId(0x01040034))
+ .addPublicSymbol(u"@android:string/ok", ResourceId(0x01040034))
.build())
.build())
.build();
@@ -92,12 +92,12 @@
.setPackageId(0x7f)
.setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
.setSymbolTable(test::StaticSymbolTableBuilder()
- .addSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
- .addSymbol(u"@android:attr/foo", ResourceId(0x01010001),
+ .addPublicSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
+ .addPublicSymbol(u"@android:attr/foo", ResourceId(0x01010001),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR)
.build())
- .addSymbol(u"@android:attr/bar", ResourceId(0x01010002),
+ .addPublicSymbol(u"@android:attr/bar", ResourceId(0x01010002),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_FLAGS)
.addItem(u"one", 0x01)
@@ -132,7 +132,7 @@
.setPackageId(0x7f)
.setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
.setSymbolTable(test::StaticSymbolTableBuilder()
- .addSymbol(u"@com.app.test:attr/com.android.support$foo",
+ .addPublicSymbol(u"@com.app.test:attr/com.android.support$foo",
ResourceId(0x7f010000), test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR).build())
.build())
@@ -156,4 +156,78 @@
EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
}
+TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+ u"@android:string/hidden")
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .setSymbolTable(JoinedSymbolTableBuilder()
+ .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+ .addSymbolTable(test::StaticSymbolTableBuilder()
+ .addSymbol(u"@android:string/hidden", ResourceId(0x01040034))
+ .build())
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+ u"@com.app.lib:string/hidden")
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.app.lib" } })
+ .setSymbolTable(JoinedSymbolTableBuilder()
+ .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+ .addSymbolTable(test::StaticSymbolTableBuilder()
+ .addSymbol(u"@com.app.test:string/com.app.lib$hidden",
+ ResourceId(0x7f040034))
+ .build())
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+ .addItem(u"@android:attr/hidden", ResourceUtils::tryParseColor(u"#ff00ff"))
+ .build())
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .setSymbolTable(JoinedSymbolTableBuilder()
+ .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+ .addSymbolTable(test::StaticSymbolTableBuilder()
+ .addSymbol(u"@android:attr/hidden", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR)
+ .build())
+ .build())
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index caab9b8..a26d763 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -17,51 +17,91 @@
#include "Diagnostics.h"
#include "ResourceUtils.h"
#include "SdkConstants.h"
-#include "XmlDom.h"
-
#include "link/Linkers.h"
-#include "link/ReferenceLinkerVisitor.h"
+#include "link/ReferenceLinker.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
namespace aapt {
namespace {
-class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor {
+/**
+ * Visits all references (including parents of styles, references in styles, arrays, etc) and
+ * links their symbolic name to their Resource ID, performing mangling and package aliasing
+ * as needed.
+ */
+class ReferenceVisitor : public ValueVisitor {
+private:
+ IAaptContext* mContext;
+ ISymbolTable* mSymbols;
+ xml::IPackageDeclStack* mDecls;
+ CallSite* mCallSite;
+ bool mError;
+
+public:
+ using ValueVisitor::visit;
+
+ ReferenceVisitor(IAaptContext* context, ISymbolTable* symbols, xml::IPackageDeclStack* decls,
+ CallSite* callSite) :
+ mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite),
+ mError(false) {
+ }
+
+ void visit(Reference* ref) override {
+ if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) {
+ mError = true;
+ }
+ }
+
+ bool hasError() const {
+ return mError;
+ }
+};
+
+/**
+ * Visits each xml Element and compiles the attributes within.
+ */
+class XmlVisitor : public xml::PackageAwareVisitor {
private:
IAaptContext* mContext;
ISymbolTable* mSymbols;
Source mSource;
std::set<int>* mSdkLevelsFound;
- ReferenceLinkerVisitor mReferenceLinkerVisitor;
+ CallSite* mCallSite;
+ ReferenceVisitor mReferenceVisitor;
bool mError = false;
public:
using xml::PackageAwareVisitor::visit;
- XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source,
- std::set<int>* sdkLevelsFound) :
+ XmlVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source,
+ std::set<int>* sdkLevelsFound, CallSite* callSite) :
mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound),
- mReferenceLinkerVisitor(context, symbols, this) {
+ mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) {
}
void visit(xml::Element* el) override {
const Source source = mSource.withLine(el->lineNumber);
for (xml::Attribute& attr : el->attributes) {
- Maybe<std::u16string> maybePackage =
- util::extractPackageFromNamespace(attr.namespaceUri);
+ Maybe<xml::ExtractedPackage> maybePackage =
+ xml::extractPackageFromNamespace(attr.namespaceUri);
if (maybePackage) {
// There is a valid package name for this attribute. We will look this up.
- StringPiece16 package = maybePackage.value();
+ StringPiece16 package = maybePackage.value().package;
if (package.empty()) {
// Empty package means the 'current' or 'local' package.
package = mContext->getCompilationPackage();
}
- attr.compiledAttribute = compileAttribute(
- ResourceName{ package.toString(), ResourceType::kAttr, attr.name });
+ Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name));
+ attrRef.privateReference = maybePackage.value().privateNamespace;
+
+ std::string errStr;
+ attr.compiledAttribute = ReferenceLinker::compileXmlAttribute(
+ attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
// Convert the string value into a compiled Value if this is a valid attribute.
if (attr.compiledAttribute) {
@@ -87,7 +127,7 @@
} else {
mContext->getDiagnostics()->error(DiagMessage(source)
<< "attribute '" << package << ":"
- << attr.name << "' was not found");
+ << attr.name << "' " << errStr);
mError = true;
}
@@ -99,7 +139,7 @@
if (attr.compiledValue) {
// With a compiledValue, we must resolve the reference and assign it an ID.
attr.compiledValue->setSource(source);
- attr.compiledValue->accept(&mReferenceLinkerVisitor);
+ attr.compiledValue->accept(&mReferenceVisitor);
}
}
@@ -107,28 +147,18 @@
xml::PackageAwareVisitor::visit(el);
}
- Maybe<xml::AaptAttribute> compileAttribute(const ResourceName& name) {
- Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(name);
- if (const ISymbolTable::Symbol* symbol = mSymbols->findByName(
- mangledName ? mangledName.value() : name)) {
- if (symbol->attribute) {
- return xml::AaptAttribute{ symbol->id, *symbol->attribute };
- }
- }
- return {};
- }
-
- inline bool hasError() {
- return mError || mReferenceLinkerVisitor.hasError();
+ bool hasError() {
+ return mError || mReferenceVisitor.hasError();
}
};
} // namespace
-bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
+bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) {
mSdkLevelsFound.clear();
- XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
- &mSdkLevelsFound);
+ CallSite callSite = { resource->file.name };
+ XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
+ &mSdkLevelsFound, &callSite);
if (resource->root) {
resource->root->accept(&visitor);
return !visitor.hasError();
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index 7f91ec3..3bfaf91 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -31,37 +31,40 @@
.setNameManglerPolicy(
NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
.setSymbolTable(test::StaticSymbolTableBuilder()
- .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
+ .addPublicSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_ENUM |
android::ResTable_map::TYPE_DIMENSION)
.addItem(u"match_parent", 0xffffffff)
.build())
- .addSymbol(u"@android:attr/background", ResourceId(0x01010001),
+ .addPublicSymbol(u"@android:attr/background", ResourceId(0x01010001),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addSymbol(u"@android:attr/attr", ResourceId(0x01010002),
+ .addPublicSymbol(u"@android:attr/attr", ResourceId(0x01010002),
test::AttributeBuilder().build())
- .addSymbol(u"@android:attr/text", ResourceId(0x01010003),
+ .addPublicSymbol(u"@android:attr/text", ResourceId(0x01010003),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_STRING)
.build())
// Add one real symbol that was introduces in v21
- .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+ .addPublicSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
test::AttributeBuilder().build())
- .addSymbol(u"@android:id/id", ResourceId(0x01030000))
+ // Private symbol.
+ .addSymbol(u"@android:color/hidden", ResourceId(0x01020001))
+
+ .addPublicSymbol(u"@android:id/id", ResourceId(0x01030000))
.addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000))
.addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000))
.addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001))
.addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
+ .addPublicSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
ResourceId(0x7f010001), test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
+ .addPublicSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
test::AttributeBuilder().build())
.build())
.build();
@@ -71,23 +74,8 @@
std::unique_ptr<IAaptContext> mContext;
};
-static xml::Element* getRootElement(XmlResource* doc) {
- xml::Node* node = doc->root.get();
- while (xml::nodeCast<xml::Namespace>(node)) {
- if (node->children.empty()) {
- return nullptr;
- }
- node = node->children.front().get();
- }
-
- if (xml::Element* el = xml::nodeCast<xml::Element>(node)) {
- return el;
- }
- return nullptr;
-}
-
TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@color/green"
@@ -97,7 +85,7 @@
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
@@ -132,8 +120,26 @@
ASSERT_EQ(xmlAttr->compiledValue, nullptr);
}
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:colorAccent="@android:color/hidden" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(mContext.get(), doc.get()));
+}
+
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:colorAccent="@*android:color/hidden" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+}
+
TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:colorAccent="#ffffff" />)EOF");
@@ -143,14 +149,14 @@
}
TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
support:colorAccent="#ff0000" />)EOF");
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
xml::Attribute* xmlAttr = viewEl->findAttribute(
@@ -162,14 +168,14 @@
}
TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:app="http://schemas.android.com/apk/res-auto"
app:colorAccent="@app:color/red" />)EOF");
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto",
@@ -185,7 +191,7 @@
}
TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:app="http://schemas.android.com/apk/res/android"
app:attr="@app:id/id">
<View xmlns:app="http://schemas.android.com/apk/res/com.app.test"
@@ -195,7 +201,7 @@
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
// All attributes and references in this element should be referring to "android" (0x01).
@@ -225,14 +231,14 @@
}
TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
android:attr="@id/id"/>)EOF");
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
// All attributes and references in this element should be referring to "com.app.test" (0x7f).
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index 24ad05d..a2528d2 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -49,25 +49,13 @@
};
namespace xml {
-struct Node;
+struct XmlResource;
}
-struct XmlResource {
- ResourceFile file;
- std::unique_ptr<xml::Node> root;
-};
-
struct IXmlResourceConsumer {
virtual ~IXmlResourceConsumer() = default;
- virtual bool consume(IAaptContext* context, XmlResource* resource) = 0;
-};
-
-struct IPackageDeclStack {
- virtual ~IPackageDeclStack() = default;
-
- virtual Maybe<ResourceName> transformPackage(const ResourceName& name,
- const StringPiece16& localPackage) const = 0;
+ virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0;
};
} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index bb33ea7..d04181d 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -51,6 +51,7 @@
std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
+ symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic);
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
const ConfigDescription kDefaultConfig;
@@ -158,6 +159,7 @@
}
if (s) {
+ s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
mCache.put(name, s);
return s.get();
}
@@ -165,6 +167,44 @@
return nullptr;
}
+static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) {
+ android::ResTable::resource_name resName;
+ if (!table.getResourceName(id.id, true, &resName)) {
+ return {};
+ }
+
+ ResourceName name;
+ if (resName.package) {
+ name.package = StringPiece16(resName.package, resName.packageLen).toString();
+ }
+
+ const ResourceType* type;
+ if (resName.type) {
+ type = parseResourceType(StringPiece16(resName.type, resName.typeLen));
+
+ } else if (resName.type8) {
+ type = parseResourceType(util::utf8ToUtf16(StringPiece(resName.type8, resName.typeLen)));
+ } else {
+ return {};
+ }
+
+ if (!type) {
+ return {};
+ }
+
+ name.type = *type;
+
+ if (resName.name) {
+ name.entry = StringPiece16(resName.name, resName.nameLen).toString();
+ } else if (resName.name8) {
+ name.entry = util::utf8ToUtf16(StringPiece(resName.name8, resName.nameLen));
+ } else {
+ return {};
+ }
+
+ return name;
+}
+
const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findById(
ResourceId id) {
if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
@@ -174,22 +214,16 @@
for (const auto& asset : mAssets) {
const android::ResTable& table = asset->getResources(false);
- android::ResTable::resource_name name;
- if (!table.getResourceName(id.id, true, &name)) {
+ Maybe<ResourceName> maybeName = getResourceName(table, id);
+ if (!maybeName) {
continue;
}
- bool isAttr = false;
- if (name.type) {
- if (const ResourceType* t = parseResourceType(StringPiece16(name.type, name.typeLen))) {
- isAttr = (*t == ResourceType::kAttr);
- }
- } else if (name.type8) {
- isAttr = (StringPiece(name.type8, name.typeLen) == "attr");
- }
+ uint32_t typeSpecFlags = 0;
+ table.getResourceFlags(id.id, &typeSpecFlags);
std::shared_ptr<Symbol> s;
- if (isAttr) {
+ if (maybeName.value().type == ResourceType::kAttr) {
s = lookupAttributeInTable(table, id);
} else {
s = std::make_shared<Symbol>();
@@ -197,6 +231,7 @@
}
if (s) {
+ s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
mIdCache.put(id, s);
return s.get();
}
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 89cd972..9ca694a 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -19,10 +19,9 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "XmlDom.h"
-#include "util/Util.h"
-
#include "test/Common.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
#include <memory>
@@ -212,15 +211,22 @@
}
};
-inline std::unique_ptr<XmlResource> buildXmlDom(const StringPiece& str) {
+inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) {
std::stringstream in;
in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
StdErrDiagnostics diag;
- std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, {});
+ std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, {});
assert(doc);
return doc;
}
+inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context,
+ const StringPiece& str) {
+ std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str);
+ doc->file.name.package = context->getCompilationPackage().toString();
+ return doc;
+}
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 4fa4918..555a539 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -135,8 +135,19 @@
std::unique_ptr<SymbolTable> mSymbolTable = util::make_unique<SymbolTable>();
public:
+ StaticSymbolTableBuilder& addPublicSymbol(const StringPiece16& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>(
+ id, std::move(attr));
+ symbol->isPublic = true;
+ mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
+ mSymbolTable->mIdMap[id] = symbol.get();
+ mSymbolTable->mSymbols.push_back(std::move(symbol));
+ return *this;
+ }
+
StaticSymbolTableBuilder& addSymbol(const StringPiece16& name, ResourceId id,
- std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<Attribute> attr = {}) {
std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>(
id, std::move(attr));
mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 3048334..49625b5 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -97,19 +97,19 @@
return !error;
}
-bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
+Maybe<Reference> BinaryResourceParser::getSymbol(const void* data) {
if (!mSymbolEntries || mSymbolEntryCount == 0) {
- return false;
+ return {};
}
if ((uintptr_t) data < (uintptr_t) mData) {
- return false;
+ return {};
}
// We only support 32 bit offsets right now.
const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData;
if (offset > std::numeric_limits<uint32_t>::max()) {
- return false;
+ return {};
}
for (size_t i = 0; i < mSymbolEntryCount; i++) {
@@ -118,24 +118,23 @@
const StringPiece16 str = util::getString(
mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
- StringPiece16 typeStr;
- ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
- &outSymbol->entry);
- const ResourceType* type = parseResourceType(typeStr);
- if (!type) {
- return false;
+ ResourceNameRef nameRef;
+ bool privateRef = false;
+ if (!ResourceUtils::parseResourceName(str, &nameRef, &privateRef)) {
+ return {};
}
- outSymbol->type = *type;
-
// Since we scan the symbol table in order, we can start looking for the
// next symbol from this point.
mSymbolEntryCount -= i + 1;
mSymbolEntries += i + 1;
- return true;
+
+ Reference ref(nameRef);
+ ref.privateReference = privateRef;
+ return Maybe<Reference>(std::move(ref));
}
}
- return false;
+ return {};
}
/**
@@ -566,7 +565,13 @@
resourceValue = parseValue(name, config, value, entry->flags);
}
- assert(resourceValue && "failed to interpret valid resource");
+ if (!resourceValue) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "failed to parse value for resource " << name
+ << " (" << resId << ") with configuration '"
+ << config << "'");
+ return false;
+ }
Source source = mSource;
if (sourceBlock) {
@@ -657,7 +662,7 @@
if (value->dataType == Res_value::TYPE_REFERENCE ||
value->dataType == Res_value::TYPE_ATTRIBUTE) {
const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
- Reference::Type::kResource : Reference::Type::kAttribute;
+ Reference::Type::kResource : Reference::Type::kAttribute;
if (data != 0) {
// This is a normal reference.
@@ -665,9 +670,9 @@
}
// This reference has an invalid ID. Check if it is an unresolved symbol.
- ResourceNameRef symbol;
- if (getSymbol(&value->data, &symbol)) {
- return util::make_unique<Reference>(symbol, type);
+ if (Maybe<Reference> ref = getSymbol(&value->data)) {
+ ref.value().referenceType = type;
+ return util::make_unique<Reference>(std::move(ref.value()));
}
// This is not an unresolved symbol, so it must be the magic @null reference.
@@ -715,10 +720,8 @@
if (util::deviceToHost32(map->parent.ident) == 0) {
// The parent is either not set or it is an unresolved symbol.
// Check to see if it is a symbol.
- ResourceNameRef symbol;
- if (getSymbol(&map->parent.ident, &symbol)) {
- style->parent = Reference(symbol.toResourceName());
- }
+ style->parent = getSymbol(&map->parent.ident);
+
} else {
// The parent is a regular reference to a resource.
style->parent = Reference(util::deviceToHost32(map->parent.ident));
@@ -731,10 +734,14 @@
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
// The map entry's key (attribute) is not set. This must be
// a symbol reference, so resolve it.
- ResourceNameRef symbol;
- bool result = getSymbol(&mapEntry.name.ident, &symbol);
- assert(result);
- styleEntry.key.name = symbol.toResourceName();
+ Maybe<Reference> symbol = getSymbol(&mapEntry.name.ident);
+ if (!symbol) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "unresolved style attribute");
+ return {};
+ }
+ styleEntry.key = std::move(symbol.value());
+
} else {
// The map entry's key (attribute) is a regular reference.
styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
@@ -742,7 +749,9 @@
// Parse the attribute's value.
styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
- assert(styleEntry.value);
+ if (!styleEntry.value) {
+ return {};
+ }
}
return style;
}
@@ -773,10 +782,14 @@
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
// The map entry's key (id) is not set. This must be
// a symbol reference, so resolve it.
- ResourceNameRef symbolName;
- bool result = getSymbol(&mapEntry.name.ident, &symbolName);
- assert(result);
- symbol.symbol.name = symbolName.toResourceName();
+ Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
+ if (!ref) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "unresolved attribute symbol");
+ return {};
+ }
+ symbol.symbol = std::move(ref.value());
+
} else {
// The map entry's key (id) is a regular reference.
symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
@@ -808,10 +821,14 @@
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
// The map entry's key (attribute) is not set. This must be
// a symbol reference, so resolve it.
- ResourceNameRef symbol;
- bool result = getSymbol(&mapEntry.name.ident, &symbol);
- assert(result);
- styleable->entries.emplace_back(symbol);
+ Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
+ if (!ref) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "unresolved styleable symbol");
+ return {};
+ }
+ styleable->entries.emplace_back(std::move(ref.value()));
+
} else {
// The map entry's key (attribute) is a regular reference.
styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident));
@@ -826,6 +843,9 @@
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
for (const ResTable_map& mapEntry : map) {
std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
+ if (!item) {
+ return {};
+ }
switch (util::deviceToHost32(mapEntry.name.ident)) {
case android::ResTable_map::ATTR_ZERO:
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 02c4081..73fcf52 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -57,7 +57,7 @@
private:
// Helper method to retrieve the symbol name for a given table offset specified
// as a pointer.
- bool getSymbol(const void* data, ResourceNameRef* outSymbol);
+ Maybe<Reference> getSymbol(const void* data);
bool parseTable(const android::ResChunk_header* chunk);
bool parseSymbolTable(const android::ResChunk_header* chunk);
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 59b8385..9ecc974 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -28,9 +28,6 @@
namespace aapt {
namespace util {
-constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
-constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
-
static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
const std::function<char(char)>& f) {
std::vector<std::string> parts;
@@ -467,18 +464,6 @@
return data;
}
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) {
- if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) {
- StringPiece16 schemaPrefix = kSchemaPrefix;
- StringPiece16 package = namespaceUri;
- return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size())
- .toString();
- } else if (namespaceUri == kSchemaAuto) {
- return std::u16string();
- }
- return {};
-}
-
bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
StringPiece16* outEntry, StringPiece16* outSuffix) {
if (!stringStartsWith<char16_t>(path, u"res/")) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 324afb3..a898619 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -331,15 +331,6 @@
}
/**
- * Returns a package name if the namespace URI is of the form:
- * http://schemas.android.com/apk/res/<package>
- *
- * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
- * returns an empty package name.
- */
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri);
-
-/**
* Given a path like: res/xml-sw600dp/foo.xml
*
* Extracts "res/xml-sw600dp/" into outPrefix.
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
similarity index 91%
rename from tools/aapt2/XmlDom.cpp
rename to tools/aapt2/xml/XmlDom.cpp
index b769c76..d27b62fd 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-#include "util/Util.h"
#include "XmlDom.h"
#include "XmlPullParser.h"
+#include "util/Util.h"
#include <cassert>
+#include <expat.h>
#include <memory>
#include <stack>
#include <string>
@@ -317,6 +318,10 @@
return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
}
+Element* findRootElement(XmlResource* doc) {
+ return findRootElement(doc->root.get());
+}
+
Element* findRootElement(Node* node) {
if (!node) {
return nullptr;
@@ -397,5 +402,39 @@
return elements;
}
+void PackageAwareVisitor::visit(Namespace* ns) {
+ bool added = false;
+ if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) {
+ ExtractedPackage& package = maybePackage.value();
+ mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) });
+ added = true;
+ }
+
+ Visitor::visit(ns);
+
+ if (added) {
+ mPackageDecls.pop_back();
+ }
+}
+
+Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const {
+ if (alias.empty()) {
+ return ExtractedPackage{ localPackage.toString(), false /* private */ };
+ }
+
+ const auto rend = mPackageDecls.rend();
+ for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{ localPackage.toString(),
+ iter->package.privateNamespace };
+ }
+ return iter->package;
+ }
+ }
+ return {};
+}
+
} // namespace xml
} // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/xml/XmlDom.h
similarity index 74%
rename from tools/aapt2/XmlDom.h
rename to tools/aapt2/xml/XmlDom.h
index 721bf5b..033b0a4 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -22,11 +22,9 @@
#include "ResourceValues.h"
#include "util/StringPiece.h"
#include "util/Util.h"
-
-#include "process/IResourceTableConsumer.h"
+#include "xml/XmlUtil.h"
#include <istream>
-#include <expat.h>
#include <memory>
#include <string>
#include <vector>
@@ -34,21 +32,9 @@
namespace aapt {
namespace xml {
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
struct RawVisitor;
/**
- * The type of node. Can be used to downcast to the concrete XML node
- * class.
- */
-enum class NodeType {
- kNamespace,
- kElement,
- kText,
-};
-
-/**
* Base class for all XML nodes.
*/
struct Node {
@@ -58,9 +44,10 @@
std::u16string comment;
std::vector<std::unique_ptr<Node>> children;
+ virtual ~Node() = default;
+
void addChild(std::unique_ptr<Node> child);
virtual void accept(RawVisitor* visitor) = 0;
- virtual ~Node() {}
};
/**
@@ -122,6 +109,14 @@
};
/**
+ * An XML resource with a source, name, and XML tree.
+ */
+struct XmlResource {
+ ResourceFile file;
+ std::unique_ptr<xml::Node> root;
+};
+
+/**
* Inflates an XML DOM from a text stream, logging errors to the logger.
* Returns the root node on success, or nullptr on failure.
*/
@@ -134,6 +129,7 @@
std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
const Source& source);
+Element* findRootElement(XmlResource* doc);
Element* findRootElement(Node* node);
/**
@@ -180,7 +176,7 @@
private:
struct PackageDecl {
std::u16string prefix;
- std::u16string package;
+ ExtractedPackage package;
};
std::vector<PackageDecl> mPackageDecls;
@@ -188,44 +184,9 @@
public:
using Visitor::visit;
- void visit(Namespace* ns) override {
- bool added = false;
- {
- Maybe<std::u16string> package = util::extractPackageFromNamespace(ns->namespaceUri);
- if (package) {
- mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, package.value() });
- added = true;
- }
- }
-
- Visitor::visit(ns);
-
- if (added) {
- mPackageDecls.pop_back();
- }
- }
-
- Maybe<ResourceName> transformPackage(const ResourceName& name,
- const StringPiece16& localPackage) const override {
- if (name.package.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
- }
-
- const auto rend = mPackageDecls.rend();
- for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
- if (name.package == iter->prefix) {
- if (iter->package.empty()) {
- if (localPackage != name.package) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
- }
- } else if (iter->package != name.package) {
- return ResourceName{ iter->package, name.type, name.entry };
- }
- break;
- }
- }
- return {};
- }
+ void visit(Namespace* ns) override;
+ Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override;
};
// Implementations
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
similarity index 93%
rename from tools/aapt2/XmlDom_test.cpp
rename to tools/aapt2/xml/XmlDom_test.cpp
index a1b9ed0..431ee2c 100644
--- a/tools/aapt2/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "XmlDom.h"
+#include "xml/XmlDom.h"
#include <gtest/gtest.h>
#include <sstream>
@@ -38,7 +38,7 @@
const Source source = { "test.xml" };
StdErrDiagnostics diag;
- std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source);
+ std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source);
ASSERT_NE(doc, nullptr);
xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
diff --git a/tools/aapt2/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
similarity index 85%
rename from tools/aapt2/XmlPullParser.cpp
rename to tools/aapt2/xml/XmlPullParser.cpp
index cff935c..323ec05 100644
--- a/tools/aapt2/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -16,12 +16,14 @@
#include "util/Maybe.h"
#include "util/Util.h"
-#include "XmlPullParser.h"
+#include "xml/XmlPullParser.h"
+#include "xml/XmlUtil.h"
#include <iostream>
#include <string>
namespace aapt {
+namespace xml {
constexpr char kXmlNamespaceSep = 1;
@@ -72,14 +74,14 @@
// Record namespace prefixes and package names so that we can do our own
// handling of references that use namespace aliases.
if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
- Maybe<std::u16string> result = util::extractPackageFromNamespace(getNamespaceUri());
+ Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri());
if (event == Event::kStartNamespace) {
if (result) {
- mPackageAliases.emplace_back(getNamespacePrefix(), result.value());
+ mPackageAliases.emplace_back(
+ PackageDecl{ getNamespacePrefix(), std::move(result.value()) });
}
} else {
if (result) {
- assert(mPackageAliases.back().second == result.value());
mPackageAliases.pop_back();
}
}
@@ -131,20 +133,20 @@
return mEventQueue.front().data2;
}
-Maybe<ResourceName> XmlPullParser::transformPackage(
- const ResourceName& name, const StringPiece16& localPackage) const {
- if (name.package.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
+Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const {
+ if (alias.empty()) {
+ return ExtractedPackage{ localPackage.toString(), false /* private */ };
}
const auto endIter = mPackageAliases.rend();
for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
- if (name.package == iter->first) {
- if (iter->second.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
- } else {
- return ResourceName{ iter->second, name.type, name.entry };
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{ localPackage.toString(),
+ iter->package.privateNamespace };
}
+ return iter->package;
}
}
return {};
@@ -283,4 +285,24 @@
});
}
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+ auto iter = parser->findAttribute(u"", name);
+ if (iter != parser->endAttributes()) {
+ return StringPiece16(util::trimWhitespace(iter->value));
+ }
+ return {};
+}
+
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+ auto iter = parser->findAttribute(u"", name);
+ if (iter != parser->endAttributes()) {
+ StringPiece16 trimmed = util::trimWhitespace(iter->value);
+ if (!trimmed.empty()) {
+ return trimmed;
+ }
+ }
+ return {};
+}
+
+} // namespace xml
} // namespace aapt
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
similarity index 91%
rename from tools/aapt2/XmlPullParser.h
rename to tools/aapt2/xml/XmlPullParser.h
index a0ce21d..7e7070e 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -17,11 +17,11 @@
#ifndef AAPT_XML_PULL_PARSER_H
#define AAPT_XML_PULL_PARSER_H
-#include "util/Maybe.h"
#include "Resource.h"
-#include "util/StringPiece.h"
-
#include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "xml/XmlUtil.h"
#include <algorithm>
#include <expat.h>
@@ -33,6 +33,7 @@
#include <vector>
namespace aapt {
+namespace xml {
class XmlPullParser : public IPackageDeclStack {
public:
@@ -60,7 +61,7 @@
static bool isGoodEvent(Event event);
XmlPullParser(std::istream& in);
- virtual ~XmlPullParser();
+ ~XmlPullParser();
/**
* Returns the current event that is being processed.
@@ -95,6 +96,13 @@
const std::u16string& getNamespacePrefix() const;
const std::u16string& getNamespaceUri() const;
+ //
+ // These are available for StartElement and EndElement.
+ //
+
+ const std::u16string& getElementNamespace() const;
+ const std::u16string& getElementName() const;
+
/*
* Uses the current stack of namespaces to resolve the package. Eg:
* xmlns:app = "http://schemas.android.com/apk/res/com.android.app"
@@ -106,17 +114,8 @@
* If xmlns:app="http://schemas.android.com/apk/res-auto", then
* 'package' will be set to 'defaultPackage'.
*/
- //
-
- //
- // These are available for StartElement and EndElement.
- //
-
- const std::u16string& getElementNamespace() const;
- const std::u16string& getElementName() const;
-
- Maybe<ResourceName> transformPackage(const ResourceName& name,
- const StringPiece16& localPackage) const override;
+ Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override;
//
// Remaining methods are for retrieving information about attributes
@@ -169,9 +168,25 @@
const std::u16string mEmpty;
size_t mDepth;
std::stack<std::u16string> mNamespaceUris;
- std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
+
+ struct PackageDecl {
+ std::u16string prefix;
+ ExtractedPackage package;
+ };
+ std::vector<PackageDecl> mPackageAliases;
};
+/**
+ * Finds the attribute in the current element within the global namespace.
+ */
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
+/**
+ * Finds the attribute in the current element within the global namespace. The attribute's value
+ * must not be the empty string.
+ */
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
//
// Implementation
//
@@ -277,6 +292,7 @@
return endIter;
}
+} // namespace xml
} // namespace aapt
#endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
similarity index 63%
rename from tools/aapt2/XmlPullParser_test.cpp
rename to tools/aapt2/xml/XmlPullParser_test.cpp
index 1c99a43..8fa2c6d 100644
--- a/tools/aapt2/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -15,7 +15,7 @@
*/
#include "util/StringPiece.h"
-#include "XmlPullParser.h"
+#include "xml/XmlPullParser.h"
#include <gtest/gtest.h>
#include <sstream>
@@ -26,30 +26,30 @@
std::stringstream str;
str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
- XmlPullParser parser(str);
+ xml::XmlPullParser parser(str);
const size_t depthOuter = parser.getDepth();
- ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter));
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName()));
const size_t depthA = parser.getDepth();
- ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthA));
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName()));
const size_t depthB = parser.getDepth();
- ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName()));
- ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName()));
- ASSERT_FALSE(XmlPullParser::nextChildNode(&parser, depthOuter));
- EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.getEvent());
+ ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
+ EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent());
}
} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
new file mode 100644
index 0000000..ab9f544
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "util/Maybe.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri) {
+ if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPublicPrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPublicPrefix;
+ StringPiece16 package = namespaceUri;
+ package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+ if (package.empty()) {
+ return {};
+ }
+ return ExtractedPackage{ package.toString(), false /* isPrivate */ };
+
+ } else if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPrivatePrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPrivatePrefix;
+ StringPiece16 package = namespaceUri;
+ package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+ if (package.empty()) {
+ return {};
+ }
+ return ExtractedPackage{ package.toString(), true /* isPrivate */ };
+
+ } else if (namespaceUri == kSchemaAuto) {
+ return ExtractedPackage{ std::u16string(), true /* isPrivate */ };
+ }
+ return {};
+}
+
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+ const StringPiece16& localPackage, Reference* inRef) {
+ if (inRef->name) {
+ if (Maybe<ExtractedPackage> transformedPackage =
+ declStack->transformPackageAlias(inRef->name.value().package, localPackage)) {
+ ExtractedPackage& extractedPackage = transformedPackage.value();
+ inRef->name.value().package = std::move(extractedPackage.package);
+
+ // If the reference was already private (with a * prefix) and the namespace is public,
+ // we keep the reference private.
+ inRef->privateReference |= extractedPackage.privateNamespace;
+ }
+ }
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
new file mode 100644
index 0000000..98e5520
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef AAPT_XML_XMLUTIL_H
+#define AAPT_XML_XMLUTIL_H
+
+#include "ResourceValues.h"
+#include "util/Maybe.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
+constexpr const char16_t* kSchemaPublicPrefix = u"http://schemas.android.com/apk/res/";
+constexpr const char16_t* kSchemaPrivatePrefix = u"http://schemas.android.com/apk/prv/res/";
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+/**
+ * Result of extracting a package name from a namespace URI declaration.
+ */
+struct ExtractedPackage {
+ /**
+ * The name of the package. This can be the empty string, which means that the package
+ * should be assumed to be the package being compiled.
+ */
+ std::u16string package;
+
+ /**
+ * True if the package's private namespace was declared. This means that private resources
+ * are made visible.
+ */
+ bool privateNamespace;
+};
+
+/**
+ * Returns an ExtractedPackage struct if the namespace URI is of the form:
+ * http://schemas.android.com/apk/res/<package> or
+ * http://schemas.android.com/apk/prv/res/<package>
+ *
+ * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
+ * returns an empty package name.
+ */
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri);
+
+/**
+ * Interface representing a stack of XML namespace declarations. When looking up the package
+ * for a namespace prefix, the stack is checked from top to bottom.
+ */
+struct IPackageDeclStack {
+ virtual ~IPackageDeclStack() = default;
+
+ /**
+ * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
+ */
+ virtual Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const = 0;
+};
+
+/**
+ * Helper function for transforming the original Reference inRef to a fully qualified reference
+ * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of
+ * the package declaration was private.
+ */
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+ const StringPiece16& localPackage, Reference* inRef);
+
+} // namespace xml
+} // namespace aapt
+
+#endif /* AAPT_XML_XMLUTIL_H */
diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp
new file mode 100644
index 0000000..7796b3e
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "test/Common.h"
+#include "xml/XmlUtil.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(XmlUtilTest, ExtractPackageFromNamespace) {
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"com.android"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(
+ u"http://schemas.android.com/apk/prv/res/"));
+
+ Maybe<xml::ExtractedPackage> p =
+ xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"a"), p.value().package);
+ EXPECT_EQ(false, p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"android"), p.value().package);
+ EXPECT_EQ(true, p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"com.test"), p.value().package);
+ EXPECT_EQ(true, p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(), p.value().package);
+ EXPECT_EQ(true, p.value().privateNamespace);
+}
+
+} // namespace aapt
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 61ddb04..53bfc15 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -30,6 +30,9 @@
built_framework_dep := $(call java-lib-deps,framework)
built_framework_classes := $(call java-lib-files,framework)
+built_oj_dep := $(call java-lib-deps,core-oj)
+built_oj_classes := $(call java-lib-files,core-oj)
+
built_core_dep := $(call java-lib-deps,core-libart)
built_core_classes := $(call java-lib-files,core-libart)
@@ -56,7 +59,8 @@
include $(BUILD_SYSTEM)/base_rules.mk
#######################################
-$(LOCAL_BUILT_MODULE): $(built_core_dep) \
+$(LOCAL_BUILT_MODULE): $(built_oj_dep) \
+ $(built_core_dep) \
$(built_framework_dep) \
$(built_ext_dep) \
$(built_ext_data) \
@@ -69,6 +73,7 @@
$(hide) ls -l $(built_framework_classes)
$(hide) java -ea -jar $(built_layoutlib_create_jar) \
$@ \
+ $(built_oj_classes) \
$(built_core_classes) \
$(built_framework_classes) \
$(built_ext_classes) \