Merge "Add more methods to RecoverableKeyStoreLoader."
diff --git a/Android.bp b/Android.bp
index 3754c81..e23d9c2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -693,7 +693,6 @@
srcs: [
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
- "tools/streaming_proto/stream.proto",
],
target: {
diff --git a/Android.mk b/Android.mk
index 00f877a..995630b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1003,7 +1003,6 @@
-Iexternal/protobuf/src
LOCAL_SOURCE_FILES_ALL_GENERATED := true
LOCAL_SRC_FILES := \
- tools/streaming_proto/stream.proto \
cmds/am/proto/instrumentation_data.proto \
$(call all-proto-files-under, core/proto) \
$(call all-proto-files-under, libs/incident/proto) \
diff --git a/api/current.txt b/api/current.txt
index c4f9e4a..60d314f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3961,7 +3961,8 @@
field public static final int IMPORTANCE_PERCEPTIBLE = 230; // 0xe6
field public static final int IMPORTANCE_PERCEPTIBLE_PRE_26 = 130; // 0x82
field public static final int IMPORTANCE_SERVICE = 300; // 0x12c
- field public static final int IMPORTANCE_TOP_SLEEPING = 150; // 0x96
+ field public static final int IMPORTANCE_TOP_SLEEPING = 325; // 0x145
+ field public static final deprecated int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150; // 0x96
field public static final int IMPORTANCE_VISIBLE = 200; // 0xc8
field public static final int REASON_PROVIDER_IN_USE = 1; // 0x1
field public static final int REASON_SERVICE_IN_USE = 2; // 0x2
@@ -5199,6 +5200,7 @@
field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
+ field public static final java.lang.String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
field public static final deprecated java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
@@ -5482,7 +5484,9 @@
method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
method public java.lang.CharSequence getUserDisplayName();
+ method public boolean isGroupConversation();
method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
+ method public android.app.Notification.MessagingStyle setGroupConversation(boolean);
field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
}
@@ -6450,6 +6454,7 @@
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public void setKeepUninstalledPackages(android.content.ComponentName, java.util.List<java.lang.String>);
+ method public boolean setKeyPairCertificate(android.content.ComponentName, java.lang.String, java.util.List<java.security.cert.Certificate>, boolean);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
method public void setLockTaskFeatures(android.content.ComponentName, int);
@@ -7030,10 +7035,12 @@
field public static final java.lang.String HINT_NO_TINT = "no_tint";
field public static final java.lang.String HINT_PARTIAL = "partial";
field public static final java.lang.String HINT_SELECTED = "selected";
+ field public static final java.lang.String HINT_SHORTCUT = "shortcut";
field public static final java.lang.String HINT_SUMMARY = "summary";
field public static final java.lang.String HINT_TITLE = "title";
field public static final java.lang.String SUBTYPE_COLOR = "color";
field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+ field public static final java.lang.String SUBTYPE_PRIORITY = "priority";
field public static final java.lang.String SUBTYPE_SLIDER = "slider";
field public static final java.lang.String SUBTYPE_SOURCE = "source";
field public static final java.lang.String SUBTYPE_TOGGLE = "toggle";
@@ -8221,7 +8228,7 @@
method public void onAppStatusChanged(android.bluetooth.BluetoothDevice, boolean);
method public void onConnectionStateChanged(android.bluetooth.BluetoothDevice, int);
method public void onGetReport(android.bluetooth.BluetoothDevice, byte, byte, int);
- method public void onIntrData(android.bluetooth.BluetoothDevice, byte, byte[]);
+ method public void onInterruptData(android.bluetooth.BluetoothDevice, byte, byte[]);
method public void onSetProtocol(android.bluetooth.BluetoothDevice, byte);
method public void onSetReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
method public void onVirtualCableUnplug(android.bluetooth.BluetoothDevice);
@@ -32257,6 +32264,7 @@
method public deprecated void setUserRestrictions(android.os.Bundle);
method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
method public static boolean supportsMultipleUsers();
+ method public boolean trySetQuietModeEnabled(boolean, android.os.UserHandle);
field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
@@ -41064,6 +41072,7 @@
method public java.lang.String getMeid(int);
method public java.lang.String getMmsUAProfUrl();
method public java.lang.String getMmsUserAgent();
+ method public java.lang.String getNai();
method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
method public java.lang.String getNetworkCountryIso();
method public java.lang.String getNetworkOperator();
@@ -41907,9 +41916,9 @@
}
public class DynamicLayout extends android.text.Layout {
- ctor public DynamicLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
- ctor public DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
- ctor public DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
+ ctor public deprecated DynamicLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+ ctor public deprecated DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+ ctor public deprecated DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
method public int getBottomPadding();
method public int getEllipsisCount(int);
method public int getEllipsisStart(int);
@@ -42296,9 +42305,9 @@
}
public class StaticLayout extends android.text.Layout {
- ctor public StaticLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
- ctor public StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
- ctor public StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
+ ctor public deprecated StaticLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+ ctor public deprecated StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+ ctor public deprecated StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
method public int getBottomPadding();
method public int getEllipsisCount(int);
method public int getEllipsisStart(int);
@@ -44715,6 +44724,14 @@
field public static final android.os.Parcelable.Creator<android.view.Display.Mode> CREATOR;
}
+ public final class DisplayCutout {
+ method public android.graphics.Region getBounds();
+ method public int getSafeInsetBottom();
+ method public int getSafeInsetLeft();
+ method public int getSafeInsetRight();
+ method public int getSafeInsetTop();
+ }
+
public final class DragAndDropPermissions implements android.os.Parcelable {
method public int describeContents();
method public void release();
@@ -47668,8 +47685,10 @@
public final class WindowInsets {
ctor public WindowInsets(android.view.WindowInsets);
+ method public android.view.WindowInsets consumeDisplayCutout();
method public android.view.WindowInsets consumeStableInsets();
method public android.view.WindowInsets consumeSystemWindowInsets();
+ method public android.view.DisplayCutout getDisplayCutout();
method public int getStableInsetBottom();
method public int getStableInsetLeft();
method public int getStableInsetRight();
@@ -47729,6 +47748,7 @@
field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1
field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8
field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0
+ field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L
field public static final int FLAGS_CHANGED = 4; // 0x4
field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
@@ -47823,6 +47843,7 @@
field public float buttonBrightness;
field public float dimAmount;
field public int flags;
+ field public long flags2;
field public int format;
field public int gravity;
field public float horizontalMargin;
diff --git a/api/removed.txt b/api/removed.txt
index be4d5be..1a4e796 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -506,14 +506,6 @@
}
-package android.view.accessibility {
-
- public final class AccessibilityWindowInfo implements android.os.Parcelable {
- method public boolean inPictureInPicture();
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 5859504..ec509ad 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,6 +103,7 @@
field public static final deprecated java.lang.String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
field public static final java.lang.String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
+ field public static final java.lang.String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
@@ -1339,7 +1340,7 @@
package android.hardware.location {
- public class ContextHubInfo {
+ public class ContextHubInfo implements android.os.Parcelable {
ctor public ContextHubInfo();
method public int describeContents();
method public int getId();
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 6e7a613..3018be1 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -31,10 +31,9 @@
namespace os {
namespace statsd {
-UidMap::UidMap() : mBytesUsed(0) {
-}
-UidMap::~UidMap() {
-}
+UidMap::UidMap() : mBytesUsed(0) {}
+
+UidMap::~UidMap() {}
bool UidMap::hasApp(int uid, const string& packageName) const {
lock_guard<mutex> lock(mMutex);
@@ -48,6 +47,27 @@
return false;
}
+string UidMap::normalizeAppName(const string& appName) const {
+ string normalizedName = appName;
+ std::transform(normalizedName.begin(), normalizedName.end(), normalizedName.begin(), ::tolower);
+ return normalizedName;
+}
+
+std::set<string> UidMap::getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const {
+ lock_guard<mutex> lock(mMutex);
+ return getAppNamesFromUidLocked(uid,returnNormalized);
+}
+
+std::set<string> UidMap::getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const {
+ std::set<string> names;
+ auto range = mMap.equal_range(uid);
+ for (auto it = range.first; it != range.second; ++it) {
+ names.insert(returnNormalized ?
+ normalizeAppName(it->second.packageName) : it->second.packageName);
+ }
+ return names;
+}
+
int64_t UidMap::getAppVersion(int uid, const string& packageName) const {
lock_guard<mutex> lock(mMutex);
@@ -97,17 +117,17 @@
const int64_t& versionCode) {
lock_guard<mutex> lock(mMutex);
- string app = string(String8(app_16).string());
+ string appName = string(String8(app_16).string());
// Notify any interested producers that this app has updated
for (auto it : mSubscribers) {
- it->notifyAppUpgrade(app, uid, versionCode);
+ it->notifyAppUpgrade(appName, uid, versionCode);
}
auto log = mOutput.add_changes();
log->set_deletion(false);
log->set_timestamp_nanos(timestamp);
- log->set_app(app);
+ log->set_app(appName);
log->set_uid(uid);
log->set_version(versionCode);
mBytesUsed += log->ByteSize();
@@ -117,16 +137,15 @@
auto range = mMap.equal_range(int(uid));
for (auto it = range.first; it != range.second; ++it) {
- if (it->second.packageName == app) {
+ // If we find the exact same app name and uid, update the app version directly.
+ if (it->second.packageName == appName) {
it->second.versionCode = versionCode;
return;
}
- VLOG("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
- return;
}
// Otherwise, we need to add an app at this uid.
- mMap.insert(make_pair(uid, AppData(app, versionCode)));
+ mMap.insert(make_pair(uid, AppData(appName, versionCode)));
}
void UidMap::ensureBytesUsedBelowLimit() {
@@ -154,6 +173,7 @@
void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
removeApp(time(nullptr) * NS_PER_SEC, app_16, uid);
}
+
void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
lock_guard<mutex> lock(mMutex);
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 9e1ad69..487fdf9 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef STATSD_UIDMAP_H
-#define STATSD_UIDMAP_H
+#pragma once
#include "config/ConfigKey.h"
#include "config/ConfigListener.h"
@@ -66,6 +65,9 @@
// Returns true if the given uid contains the specified app (eg. com.google.android.gms).
bool hasApp(int uid, const string& packageName) const;
+ // Returns the app names from uid.
+ std::set<string> getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const;
+
int64_t getAppVersion(int uid, const string& packageName) const;
// Helper for debugging contents of this uid map. Can be triggered with:
@@ -103,6 +105,9 @@
size_t getBytesUsed();
private:
+ std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
+ string normalizeAppName(const string& appName) const;
+
void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
const vector<int64_t>& versionCode, const vector<String16>& packageName);
@@ -160,4 +165,3 @@
} // namespace os
} // namespace android
-#endif // STATSD_UIDMAP_H
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 5b2cedd..3fa96d3 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -74,6 +74,14 @@
EXPECT_TRUE(m.hasApp(1000, kApp1));
EXPECT_TRUE(m.hasApp(1000, kApp2));
EXPECT_FALSE(m.hasApp(1000, "not.app"));
+
+ std::set<string> name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ name_set = m.getAppNamesFromUid(12345, true /* returnNormalized */);
+ EXPECT_TRUE(name_set.empty());
}
TEST(UidMapTest, TestAddAndRemove) {
@@ -90,12 +98,59 @@
versions.push_back(5);
m.updateMap(uids, versions, apps);
+ std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Update the app1 version.
m.updateApp(String16(kApp1.c_str()), 1000, 40);
EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
m.removeApp(String16(kApp1.c_str()), 1000);
EXPECT_FALSE(m.hasApp(1000, kApp1));
EXPECT_TRUE(m.hasApp(1000, kApp2));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 1u);
+ EXPECT_TRUE(name_set.find(kApp1) == name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Remove app2.
+ m.removeApp(String16(kApp2.c_str()), 1000);
+ EXPECT_FALSE(m.hasApp(1000, kApp1));
+ EXPECT_FALSE(m.hasApp(1000, kApp2));
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_TRUE(name_set.empty());
+}
+
+TEST(UidMapTest, TestUpdateApp) {
+ UidMap m;
+ m.updateMap({1000, 1000}, {4, 5}, {String16(kApp1.c_str()), String16(kApp2.c_str())});
+ std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 2u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+ // Adds a new name for uid 1000.
+ m.updateApp(String16("NeW_aPP1_NAmE"), 1000, 40);
+ name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 3u);
+ EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+ EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+ EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+ EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
+
+ // This name is also reused by another uid 2000.
+ m.updateApp(String16("NeW_aPP1_NAmE"), 2000, 1);
+ name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */);
+ EXPECT_EQ(name_set.size(), 1u);
+ EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+ EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
}
TEST(UidMapTest, TestClearingOutput) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 19e3a23..1adae7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -458,16 +458,17 @@
public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4;
/**
+ * @hide
* Process states, describing the kind of state a particular process is in.
* When updating these, make sure to also check all related references to the
* constant in code, and update these arrays:
*
- * com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE
- * com.android.server.am.ProcessList#sProcStateToProcMem
- * com.android.server.am.ProcessList#sFirstAwakePssTimes
- * com.android.server.am.ProcessList#sSameAwakePssTimes
- * com.android.server.am.ProcessList#sTestFirstPssTimes
- * com.android.server.am.ProcessList#sTestSamePssTimes
+ * @see com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE
+ * @see com.android.server.am.ProcessList#sProcStateToProcMem
+ * @see com.android.server.am.ProcessList#sFirstAwakePssTimes
+ * @see com.android.server.am.ProcessList#sSameAwakePssTimes
+ * @see com.android.server.am.ProcessList#sTestFirstPssTimes
+ * @see com.android.server.am.ProcessList#sTestSamePssTimes
*/
/** @hide Not a real process state. */
@@ -489,31 +490,31 @@
/** @hide Process is hosting a foreground service. */
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
- /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
- public static final int PROCESS_STATE_TOP_SLEEPING = 5;
-
/** @hide Process is important to the user, and something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
+ public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
/** @hide Process is important to the user, but not something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
+ public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6;
/** @hide Process is in the background transient so we will try to keep running. */
- public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 8;
+ public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 7;
/** @hide Process is in the background running a backup/restore operation. */
- public static final int PROCESS_STATE_BACKUP = 9;
+ public static final int PROCESS_STATE_BACKUP = 8;
/** @hide Process is in the background running a service. Unlike oom_adj, this level
* is used for both the normal running in background state and the executing
* operations state. */
- public static final int PROCESS_STATE_SERVICE = 10;
+ public static final int PROCESS_STATE_SERVICE = 9;
/** @hide Process is in the background running a receiver. Note that from the
* perspective of oom_adj, receivers run at a higher foreground level, but for our
* prioritization here that is not necessary and putting them below services means
* many fewer changes in some process states as they receive broadcasts. */
- public static final int PROCESS_STATE_RECEIVER = 11;
+ public static final int PROCESS_STATE_RECEIVER = 10;
+
+ /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+ public static final int PROCESS_STATE_TOP_SLEEPING = 11;
/** @hide Process is in the background, but it can't restore its state so we want
* to try to avoid killing it. */
@@ -2897,13 +2898,13 @@
public static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
/**
- * Constant for {@link #importance}: This process is running the foreground
- * UI, but the device is asleep so it is not visible to the user. This means
- * the user is not really aware of the process, because they can not see or
- * interact with it, but it is quite important because it what they expect to
- * return to once unlocking the device.
+ * @deprecated Pre-{@link android.os.Build.VERSION_CODES#P} version of
+ * {@link #IMPORTANCE_TOP_SLEEPING}. As of Android
+ * {@link android.os.Build.VERSION_CODES#P}, this is considered much less
+ * important since we want to reduce what apps can do when the screen is off.
*/
- public static final int IMPORTANCE_TOP_SLEEPING = 150;
+ @Deprecated
+ public static final int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150;
/**
* Constant for {@link #importance}: This process is running something
@@ -2964,6 +2965,15 @@
public static final int IMPORTANCE_SERVICE = 300;
/**
+ * Constant for {@link #importance}: This process is running the foreground
+ * UI, but the device is asleep so it is not visible to the user. Though the
+ * system will try hard to keep its process from being killed, in all other
+ * ways we consider it a kind of cached process, with the limitations that go
+ * along with that state: network access, running background services, etc.
+ */
+ public static final int IMPORTANCE_TOP_SLEEPING = 325;
+
+ /**
* Constant for {@link #importance}: This process is running an
* application that can not save its state, and thus can't be killed
* while in the background. This will be used with apps that have
@@ -3008,14 +3018,14 @@
return IMPORTANCE_CACHED;
} else if (procState == PROCESS_STATE_HEAVY_WEIGHT) {
return IMPORTANCE_CANT_SAVE_STATE;
+ } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
+ return IMPORTANCE_TOP_SLEEPING;
} else if (procState >= PROCESS_STATE_SERVICE) {
return IMPORTANCE_SERVICE;
} else if (procState >= PROCESS_STATE_TRANSIENT_BACKGROUND) {
return IMPORTANCE_PERCEPTIBLE;
} else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) {
return IMPORTANCE_VISIBLE;
- } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
- return IMPORTANCE_TOP_SLEEPING;
} else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) {
return IMPORTANCE_FOREGROUND_SERVICE;
} else {
@@ -3049,6 +3059,8 @@
switch (importance) {
case IMPORTANCE_PERCEPTIBLE:
return IMPORTANCE_PERCEPTIBLE_PRE_26;
+ case IMPORTANCE_TOP_SLEEPING:
+ return IMPORTANCE_TOP_SLEEPING_PRE_28;
case IMPORTANCE_CANT_SAVE_STATE:
return IMPORTANCE_CANT_SAVE_STATE_PRE_26;
}
@@ -3062,16 +3074,18 @@
return PROCESS_STATE_NONEXISTENT;
} else if (importance >= IMPORTANCE_CACHED) {
return PROCESS_STATE_HOME;
- } else if (importance == IMPORTANCE_CANT_SAVE_STATE) {
+ } else if (importance >= IMPORTANCE_CANT_SAVE_STATE) {
return PROCESS_STATE_HEAVY_WEIGHT;
+ } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
+ return PROCESS_STATE_TOP_SLEEPING;
} else if (importance >= IMPORTANCE_SERVICE) {
return PROCESS_STATE_SERVICE;
} else if (importance >= IMPORTANCE_PERCEPTIBLE) {
return PROCESS_STATE_TRANSIENT_BACKGROUND;
} else if (importance >= IMPORTANCE_VISIBLE) {
return PROCESS_STATE_IMPORTANT_FOREGROUND;
- } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
- return PROCESS_STATE_TOP_SLEEPING;
+ } else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) {
+ return PROCESS_STATE_FOREGROUND_SERVICE;
} else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) {
return PROCESS_STATE_FOREGROUND_SERVICE;
} else {
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index a0fb6ee..3a355d9 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -137,7 +137,9 @@
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
* embed}
*
- * @deprecated Use {@link android.support.v4.app.DialogFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class DialogFragment extends Fragment
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index a92684b..4ff07f2 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -257,7 +257,9 @@
* pressing back will pop it to return the user to whatever previous state
* the activity UI was in.
*
- * @deprecated Use {@link android.support.v4.app.Fragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.Fragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index a1dd32f..536c866 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -25,7 +25,8 @@
/**
* Callbacks to a {@link Fragment}'s container.
*
- * @deprecated Use {@link android.support.v4.app.FragmentContainer}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentContainer}.
*/
@Deprecated
public abstract class FragmentContainer {
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index cbb58d4..40bc248 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -38,7 +38,8 @@
* It is the responsibility of the host to take care of the Fragment's lifecycle.
* The methods provided by {@link FragmentController} are for that purpose.
*
- * @deprecated Use {@link android.support.v4.app.FragmentController}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentController}
*/
@Deprecated
public class FragmentController {
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 1edc68e..b48817b 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -38,7 +38,8 @@
* host fragments, implement {@link FragmentHostCallback}, overriding the methods
* applicable to the host.
*
- * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentHostCallback}
*/
@Deprecated
public abstract class FragmentHostCallback<E> extends FragmentContainer {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 12e60b8..708450f 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -75,7 +75,9 @@
* <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
* Fragments For All</a> for more details.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public abstract class FragmentManager {
@@ -90,7 +92,8 @@
* the identifier as returned by {@link #getId} is the only thing that
* will be persisted across activity instances.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry}
*/
@Deprecated
public interface BackStackEntry {
@@ -136,7 +139,9 @@
/**
* Interface to watch for changes to the back stack.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a>
+ * {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
*/
@Deprecated
public interface OnBackStackChangedListener {
@@ -438,7 +443,9 @@
* Callback interface for listening to fragment state changes that happen
* within a given FragmentManager.
*
- * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a>
+ * {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
*/
@Deprecated
public abstract static class FragmentLifecycleCallbacks {
diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java
index beb1a15..326438a 100644
--- a/core/java/android/app/FragmentManagerNonConfig.java
+++ b/core/java/android/app/FragmentManagerNonConfig.java
@@ -28,7 +28,8 @@
* {@link FragmentController#retainNonConfig()} and
* {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
*
- * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentManagerNonConfig}
*/
@Deprecated
public class FragmentManagerNonConfig {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 1103649..713a559 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -22,7 +22,8 @@
* guide.</p>
* </div>
*
- * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.FragmentTransaction}
*/
@Deprecated
public abstract class FragmentTransaction {
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 90b77b3..7790f70 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -145,7 +145,9 @@
* @see #setListAdapter
* @see android.widget.ListView
*
- * @deprecated Use {@link android.support.v4.app.ListFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.ListFragment} for consistent behavior across all devices
+ * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
*/
@Deprecated
public class ListFragment extends Fragment {
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 7969684..86d0fd62 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -55,14 +55,16 @@
* <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
* </div>
*
- * @deprecated Use {@link android.support.v4.app.LoaderManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.app.LoaderManager}
*/
@Deprecated
public abstract class LoaderManager {
/**
* Callback interface for a client to interact with the manager.
*
- * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+ * Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
*/
@Deprecated
public interface LoaderCallbacks<D> {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 705f9a0..85c3be8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1090,6 +1090,12 @@
public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
/**
+ * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
+ * represents a group conversation.
+ */
+ public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
+
+ /**
* {@link #extras} key: whether the notification should be colorized as
* supplied to {@link Builder#setColorized(boolean)}}.
*/
@@ -5960,9 +5966,10 @@
public static final int MAXIMUM_RETAINED_MESSAGES = 25;
CharSequence mUserDisplayName;
- CharSequence mConversationTitle;
+ @Nullable CharSequence mConversationTitle;
List<Message> mMessages = new ArrayList<>();
List<Message> mHistoricMessages = new ArrayList<>();
+ boolean mIsGroupConversation;
MessagingStyle() {
}
@@ -5985,20 +5992,20 @@
}
/**
- * Sets the title to be displayed on this conversation. This should only be used for
- * group messaging and left unset for one-on-one conversations.
- * @param conversationTitle
+ * Sets the title to be displayed on this conversation. May be set to {@code null}.
+ *
+ * @param conversationTitle A name for the conversation, or {@code null}
* @return this object for method chaining.
*/
- public MessagingStyle setConversationTitle(CharSequence conversationTitle) {
+ public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
mConversationTitle = conversationTitle;
return this;
}
/**
- * Return the title to be displayed on this conversation. Can be <code>null</code> and
- * should be for one-on-one conversations
+ * Return the title to be displayed on this conversation. May return {@code null}.
*/
+ @Nullable
public CharSequence getConversationTitle() {
return mConversationTitle;
}
@@ -6075,6 +6082,24 @@
}
/**
+ * Sets whether this conversation notification represents a group.
+ * @param isGroupConversation {@code true} if the conversation represents a group,
+ * {@code false} otherwise.
+ * @return this object for method chaining
+ */
+ public MessagingStyle setGroupConversation(boolean isGroupConversation) {
+ mIsGroupConversation = isGroupConversation;
+ return this;
+ }
+
+ /**
+ * Returns {@code true} if this notification represents a group conversation.
+ */
+ public boolean isGroupConversation() {
+ return mIsGroupConversation;
+ }
+
+ /**
* @hide
*/
@Override
@@ -6094,6 +6119,7 @@
}
fixTitleAndTextExtras(extras);
+ extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
}
private void fixTitleAndTextExtras(Bundle extras) {
@@ -6136,6 +6162,7 @@
mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
+ mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4bb4c50..9816297 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2635,10 +2635,121 @@
}
/**
+ * The maximum number of characters allowed in the password blacklist.
+ */
+ private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000;
+
+ /**
+ * Throws an exception if the password blacklist is too large.
+ *
+ * @hide
+ */
+ public static void enforcePasswordBlacklistSize(List<String> blacklist) {
+ if (blacklist == null) {
+ return;
+ }
+ long characterCount = 0;
+ for (final String item : blacklist) {
+ characterCount += item.length();
+ }
+ if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) {
+ throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by "
+ + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters");
+ }
+ }
+
+ /**
+ * Called by an application that is administering the device to blacklist passwords.
+ * <p>
+ * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin.
+ * Note that the match against the blacklist is case insensitive. The blacklist applies for all
+ * password qualities requested by {@link #setPasswordQuality} however it is not taken into
+ * consideration by {@link #isActivePasswordSufficient}.
+ * <p>
+ * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is
+ * given a name that is used to track which blacklist is currently set by calling {@link
+ * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link
+ * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when
+ * the blacklist is being cleared.
+ * <p>
+ * The blacklist is limited to a total of 128 thousand characters rather than limiting to a
+ * number of entries.
+ * <p>
+ * This method can be called on the {@link DevicePolicyManager} instance returned by
+ * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
+ * profile.
+ *
+ * @param admin the {@link DeviceAdminReceiver} this request is associated with
+ * @param name name to associate with the blacklist
+ * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist
+ * @return whether the new blacklist was successfully installed
+ * @throws SecurityException if {@code admin} is not a device or profile owner
+ * @throws IllegalArgumentException if the blacklist surpasses the character limit
+ * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list
+ *
+ * @see #getPasswordBlacklistName
+ * @see #isActivePasswordSufficient
+ * @see #resetPasswordWithToken
+ *
+ * TODO(63578054): unhide for P
+ * @hide
+ */
+ public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name,
+ @Nullable List<String> blacklist) {
+ enforcePasswordBlacklistSize(blacklist);
+
+ try {
+ return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the name of the password blacklist set by the given admin.
+ *
+ * @param admin the {@link DeviceAdminReceiver} this request is associated with
+ * @return the name of the blacklist or {@code null} if no blacklist is set
+ *
+ * @see #setPasswordBlacklist
+ *
+ * TODO(63578054): unhide for P
+ * @hide
+ */
+ public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) {
+ try {
+ return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Test if a given password is blacklisted.
+ *
+ * @param userId the user to valiate for
+ * @param password the password to check against the blacklist
+ * @return whether the password is blacklisted
+ *
+ * @see #setPasswordBlacklist
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD)
+ public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) {
+ try {
+ return mService.isPasswordBlacklisted(userId, password);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Determine whether the current password the user has set is sufficient to meet the policy
* requirements (e.g. quality, minimum length) that have been requested by the admins of this
* user and its participating profiles. Restrictions on profiles that have a separate challenge
- * are not taken into account. The user must be unlocked in order to perform the check.
+ * are not taken into account. The user must be unlocked in order to perform the check. The
+ * password blacklist is not considered when checking sufficiency.
* <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
@@ -4063,6 +4174,52 @@
return null;
}
+
+ /**
+ * Called by a device or profile owner, or delegated certificate installer, to associate
+ * certificates with a key pair that was generated using {@link #generateKeyPair}, and
+ * set whether the key is available for the user to choose in the certificate selection
+ * prompt.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if calling from a delegated certificate installer.
+ * @param alias The private key alias under which to install the certificate. The {@code alias}
+ * should denote an existing private key. If a certificate with that alias already
+ * exists, it will be overwritten.
+ * @param certs The certificate chain to install. The chain should start with the leaf
+ * certificate and include the chain of trust in order. This will be returned by
+ * {@link android.security.KeyChain#getCertificateChain}.
+ * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+ * certificate selection prompt, {@code false} to indicate that this key can only be
+ * granted access by implementing
+ * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * @return {@code true} if the provided {@code alias} exists and the certificates has been
+ * successfully associated with it, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+ * owner, or {@code admin} is null but the calling application is not a delegated
+ * certificate installer.
+ */
+ public boolean setKeyPairCertificate(@Nullable ComponentName admin,
+ @NonNull String alias, @NonNull List<Certificate> certs, boolean isUserSelectable) {
+ throwIfParentInstance("setKeyPairCertificate");
+ try {
+ final byte[] pemCert = Credentials.convertToPem(certs.get(0));
+ byte[] pemChain = null;
+ if (certs.size() > 1) {
+ pemChain = Credentials.convertToPem(
+ certs.subList(1, certs.size()).toArray(new Certificate[0]));
+ }
+ return mService.setKeyPairCertificate(admin, mContext.getPackageName(), alias, pemCert,
+ pemChain, isUserSelectable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (CertificateException | IOException e) {
+ Log.w(TAG, "Could not pem-encode certificate", e);
+ }
+ return false;
+ }
+
+
/**
* @return the alias of a given CA certificate in the certificate store, or {@code null} if it
* doesn't exist.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9128208..5b02c22 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -78,6 +78,10 @@
long getPasswordExpiration(in ComponentName who, int userHandle, boolean parent);
+ boolean setPasswordBlacklist(in ComponentName who, String name, in List<String> blacklist, boolean parent);
+ String getPasswordBlacklistName(in ComponentName who, int userId, boolean parent);
+ boolean isPasswordBlacklisted(int userId, String password);
+
boolean isActivePasswordSufficient(int userHandle, boolean parent);
boolean isProfileActivePasswordSufficientForParent(int userHandle);
boolean isUsingUnifiedPassword(in ComponentName admin);
@@ -171,6 +175,8 @@
boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
in ParcelableKeyGenParameterSpec keySpec,
out KeymasterCertificateChain attestationChain);
+ boolean setKeyPairCertificate(in ComponentName who, in String callerPackage, in String alias,
+ in byte[] certBuffer, in byte[] certChainBuffer, boolean isUserSelectable);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 1aff7e9..5c7f674 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -63,7 +63,7 @@
HINT_ACTIONS,
HINT_SELECTED,
HINT_NO_TINT,
- HINT_HIDDEN,
+ HINT_SHORTCUT,
HINT_TOGGLE,
HINT_HORIZONTAL,
HINT_PARTIAL,
@@ -118,12 +118,10 @@
*/
public static final String HINT_NO_TINT = "no_tint";
/**
- * Hint to indicate that this content should not be shown in larger renderings
- * of Slices. This content may be used to populate the shortcut/icon
- * format of the slice.
- * @hide
+ * Hint to indicate that this content should only be displayed if the slice is presented
+ * as a shortcut.
*/
- public static final String HINT_HIDDEN = "hidden";
+ public static final String HINT_SHORTCUT = "shortcut";
/**
* Hint indicating this content should be shown instead of the normal content when the slice
* is in small format.
@@ -182,6 +180,10 @@
* which can be retrieved to see the new state of the toggle.
*/
public static final String SUBTYPE_TOGGLE = "toggle";
+ /**
+ * Subtype to tag an item representing priority.
+ */
+ public static final String SUBTYPE_PRIORITY = "priority";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index f38e462..2fab305 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -85,7 +85,7 @@
*
* @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
* @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
- * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
+ * @see BluetoothHidDeviceCallback#onInterruptData(BluetoothDevice, byte, byte[])
*/
public static final byte REPORT_TYPE_INPUT = (byte) 1;
public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
@@ -155,8 +155,8 @@
}
@Override
- public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
- mCallback.onIntrData(device, reportId, data);
+ public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+ mCallback.onInterruptData(device, reportId, data);
}
@Override
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index bd19955..e71b00f 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -106,8 +106,8 @@
* @param reportId Report Id.
* @param data Report data.
*/
- public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
- Log.d(TAG, "onIntrData: device=" + device + " reportId=" + reportId);
+ public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+ Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId);
}
/**
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 6e9f09c..c44e356 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -50,7 +50,8 @@
*
* @param <D> the data type to be loaded.
*
- * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.AsyncTaskLoader}
*/
@Deprecated
public abstract class AsyncTaskLoader<D> extends Loader<D> {
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 7f24c51..5a08636 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -39,7 +39,8 @@
* {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
* and {@link #setProjection(String[])}.
*
- * @deprecated Use {@link android.support.v4.content.CursorLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.CursorLoader}
*/
@Deprecated
public class CursorLoader extends AsyncTaskLoader<Cursor> {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 80f9a14..b0555d4 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -49,7 +49,8 @@
*
* @param <D> The result returned when the load is complete
*
- * @deprecated Use {@link android.support.v4.content.Loader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.Loader}
*/
@Deprecated
public class Loader<D> {
@@ -561,4 +562,4 @@
writer.print(" mReset="); writer.println(mReset);
}
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 7409671..a85b5f7 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -486,6 +486,7 @@
this.mConfiguredSize = other.mConfiguredSize;
this.mConfiguredGenerationId = other.mConfiguredGenerationId;
this.mIsDeferredConfig = other.mIsDeferredConfig;
+ this.mIsShared = other.mIsShared;
}
/**
@@ -498,6 +499,7 @@
int width = source.readInt();
int height = source.readInt();
boolean isDeferred = source.readInt() == 1;
+ boolean isShared = source.readInt() == 1;
ArrayList<Surface> surfaces = new ArrayList<Surface>();
source.readTypedList(surfaces, Surface.CREATOR);
@@ -508,6 +510,7 @@
mSurfaces = surfaces;
mConfiguredSize = new Size(width, height);
mIsDeferredConfig = isDeferred;
+ mIsShared = isShared;
mSurfaces = surfaces;
if (mSurfaces.size() > 0) {
mSurfaceType = SURFACE_TYPE_UNKNOWN;
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index e1137aa..c2b2800 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -26,7 +26,7 @@
* @hide
*/
@SystemApi
-public class ContextHubInfo {
+public class ContextHubInfo implements Parcelable {
private int mId;
private String mName;
private String mVendor;
@@ -262,7 +262,7 @@
@Override
public String toString() {
String retVal = "";
- retVal += "Id : " + mId;
+ retVal += "ID/handle : " + mId;
retVal += ", Name : " + mName;
retVal += "\n\tVendor : " + mVendor;
retVal += ", Toolchain : " + mToolchain;
@@ -275,8 +275,6 @@
retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
- retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
- retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
return retVal;
}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 5b89f54..6da6fb7 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -258,9 +258,9 @@
}
/**
- * Returns the list of context hubs in the system.
+ * Returns the list of ContextHubInfo objects describing the available Context Hubs.
*
- * @return the list of context hub informations
+ * @return the list of ContextHubInfo objects
*
* @see ContextHubInfo
*
@@ -268,7 +268,11 @@
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public List<ContextHubInfo> getContextHubs() {
- throw new UnsupportedOperationException("TODO: Implement this");
+ try {
+ return mService.getContextHubs();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index db5bd36..233e857 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -43,23 +43,26 @@
ContextHubInfo getContextHubInfo(int contextHubHandle);
// Loads a nanoapp at the specified hub (old API)
- int loadNanoApp(int hubHandle, in NanoApp app);
+ int loadNanoApp(int contextHubHandle, in NanoApp nanoApp);
// Unloads a nanoapp given its instance ID (old API)
- int unloadNanoApp(int nanoAppInstanceHandle);
+ int unloadNanoApp(int nanoAppHandle);
// Gets the NanoAppInstanceInfo of a nanoapp give its instance ID
- NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle);
+ NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle);
// Finds all nanoApp instances matching some filter
- int[] findNanoAppOnHub(int hubHandle, in NanoAppFilter filter);
+ int[] findNanoAppOnHub(int contextHubHandle, in NanoAppFilter filter);
// Sends a message to a nanoApp
- int sendMessage(int hubHandle, int nanoAppHandle, in ContextHubMessage msg);
+ int sendMessage(int contextHubHandle, int nanoAppHandle, in ContextHubMessage msg);
// Creates a client to send and receive messages
IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
+ // Returns a list of ContextHub objects of available hubs
+ List<ContextHubInfo> getContextHubs();
+
// Loads a nanoapp at the specified hub (new API)
void loadNanoAppOnHub(
int contextHubId, in IContextHubTransactionCallback transactionCallback,
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index b7e6b66..f73fd87 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -27,15 +27,13 @@
* Describes an instance of a nanoapp, used by the internal state manged by ContextHubService.
*
* TODO(b/69270990) Remove this class once the old API is deprecated.
- * TODO(b/70624255) Clean up toString() by removing unnecessary fields
*
* @hide
*/
@SystemApi
public class NanoAppInstanceInfo {
- private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
- private String mPublisher = PRE_LOADED_GENERIC_UNKNOWN;
- private String mName = PRE_LOADED_GENERIC_UNKNOWN;
+ private String mPublisher = "Unknown";
+ private String mName = "Unknown";
private int mHandle;
private long mAppId;
@@ -227,9 +225,7 @@
public String toString() {
String retVal = "handle : " + mHandle;
retVal += ", Id : 0x" + Long.toHexString(mAppId);
- retVal += ", Version : " + mAppVersion;
- retVal += ", Name : " + mName;
- retVal += ", Publisher : " + mPublisher;
+ retVal += ", Version : 0x" + Integer.toHexString(mAppVersion);
return retVal;
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2e9eeb1..9513b9b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -658,32 +658,40 @@
*/
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 1;
/**
- * Time this uid has any process that is top while the device is sleeping, but none
- * in the "foreground service" or better state.
- */
- public static final int PROCESS_STATE_TOP_SLEEPING = 2;
- /**
* Time this uid has any process in an active foreground state, but none in the
* "top sleeping" or better state.
*/
- public static final int PROCESS_STATE_FOREGROUND = 3;
+ public static final int PROCESS_STATE_FOREGROUND = 2;
/**
* Time this uid has any process in an active background state, but none in the
* "foreground" or better state.
*/
- public static final int PROCESS_STATE_BACKGROUND = 4;
+ public static final int PROCESS_STATE_BACKGROUND = 3;
+ /**
+ * Time this uid has any process that is top while the device is sleeping, but not
+ * active for any other reason. We kind-of consider it a kind of cached process
+ * for execution restrictions.
+ */
+ public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+ /**
+ * Time this uid has any process that is in the background but it has an activity
+ * marked as "can't save state". This is essentially a cached process, though the
+ * system will try much harder than normal to avoid killing it.
+ */
+ public static final int PROCESS_STATE_HEAVY_WEIGHT = 5;
/**
* Time this uid has any processes that are sitting around cached, not in one of the
* other active states.
*/
- public static final int PROCESS_STATE_CACHED = 5;
+ public static final int PROCESS_STATE_CACHED = 6;
/**
* Total number of process states we track.
*/
- public static final int NUM_PROCESS_STATE = 6;
+ public static final int NUM_PROCESS_STATE = 7;
static final String[] PROCESS_STATE_NAMES = {
- "Top", "Fg Service", "Top Sleeping", "Foreground", "Background", "Cached"
+ "Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
+ "Cached"
};
public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 9c90c38..f643c57 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -79,9 +79,7 @@
void setDefaultGuestRestrictions(in Bundle restrictions);
Bundle getDefaultGuestRestrictions();
boolean markGuestForDeletion(int userHandle);
- void setQuietModeEnabled(int userHandle, boolean enableQuietMode, in IntentSender target);
boolean isQuietModeEnabled(int userHandle);
- boolean trySetQuietModeDisabled(int userHandle, in IntentSender target);
void setSeedAccountData(int userHandle, in String accountName,
in String accountType, in PersistableBundle accountOptions, boolean persist);
String getSeedAccountName();
@@ -99,4 +97,5 @@
boolean isUserRunning(int userId);
boolean isUserNameSet(int userHandle);
boolean hasRestrictedProfiles();
+ boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
}
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index b9b9a18..bbb8a7b 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -334,6 +334,23 @@
}
/**
+ * Performs {@code action} for each cookie associated with a callback, calling
+ * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
+ *
+ * @hide
+ */
+ public <C> void broadcastForEachCookie(Consumer<C> action) {
+ int itemCount = beginBroadcast();
+ try {
+ for (int i = 0; i < itemCount; i++) {
+ action.accept((C) getBroadcastCookie(i));
+ }
+ } finally {
+ finishBroadcast();
+ }
+ }
+
+ /**
* Returns the number of registered callbacks. Note that the number of registered
* callbacks may differ from the value returned by {@link #beginBroadcast()} since
* the former returns the number of callbacks registered at the time of the call
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3504142..75cbd57 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2130,15 +2130,46 @@
}
/**
- * Set quiet mode of a managed profile.
+ * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a
+ * managed profile don't run, generate notifications, or consume data or battery.
+ * <p>
+ * If a user's credential is needed to turn off quiet mode, a confirm credential screen will be
+ * shown to the user.
+ * <p>
+ * The change may not happen instantly, however apps can listen for
+ * {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and
+ * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE} broadcasts in order to be notified of
+ * the change of the quiet mode. Apps can also check the current state of quiet mode by
+ * calling {@link #isQuietModeEnabled(UserHandle)}.
+ * <p>
+ * The caller must either be the foreground default launcher or have one of these permissions:
+ * {@code MANAGE_USERS} or {@code MODIFY_QUIET_MODE}.
*
- * @param userHandle The user handle of the profile.
- * @param enableQuietMode Whether quiet mode should be enabled or disabled.
+ * @param enableQuietMode whether quiet mode should be enabled or disabled
+ * @param userHandle user handle of the profile
+ * @return {@code false} if user's credential is needed in order to turn off quiet mode,
+ * {@code true} otherwise
+ * @throws SecurityException if the caller is invalid
+ * @throws IllegalArgumentException if {@code userHandle} is not a managed profile
+ *
+ * @see #isQuietModeEnabled(UserHandle)
+ */
+ public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+ return trySetQuietModeEnabled(enableQuietMode, userHandle, null);
+ }
+
+ /**
+ * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify
+ * a target to start when user is unlocked.
+ *
+ * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)}
* @hide
*/
- public void setQuietModeEnabled(@UserIdInt int userHandle, boolean enableQuietMode) {
+ public boolean trySetQuietModeEnabled(
+ boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
try {
- mService.setQuietModeEnabled(userHandle, enableQuietMode, null);
+ return mService.trySetQuietModeEnabled(
+ mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -2160,27 +2191,6 @@
}
/**
- * Tries disabling quiet mode for a given user. If the user is still locked, we unlock the user
- * first by showing the confirm credentials screen and disable quiet mode upon successful
- * unlocking. If the user is already unlocked, we call through to {@link #setQuietModeEnabled}
- * directly.
- *
- * @param userHandle The user that is going to disable quiet mode.
- * @param target The target to launch when the user is unlocked.
- * @return {@code true} if quiet mode is disabled without showing confirm credentials screen,
- * {@code false} otherwise.
- * @hide
- */
- public boolean trySetQuietModeDisabled(
- @UserIdInt int userHandle, @Nullable IntentSender target) {
- try {
- return mService.trySetQuietModeDisabled(userHandle, target);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* If the target user is a managed profile of the calling user or the caller
* is itself a managed profile, then this returns a badged copy of the given
* icon to be able to distinguish it from the original icon. For badging an
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index ecec448..8632aad 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,10 +1,13 @@
package android.os;
+import android.annotation.Nullable;
import android.os.WorkSourceProto;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Objects;
/**
* Describes the source of some work that may be done by someone else.
@@ -19,6 +22,8 @@
int[] mUids;
String[] mNames;
+ private ArrayList<WorkChain> mChains;
+
/**
* Internal statics to avoid object allocations in some operations.
* The WorkSource object itself is not thread safe, but we need to
@@ -39,6 +44,7 @@
*/
public WorkSource() {
mNum = 0;
+ mChains = null;
}
/**
@@ -48,6 +54,7 @@
public WorkSource(WorkSource orig) {
if (orig == null) {
mNum = 0;
+ mChains = null;
return;
}
mNum = orig.mNum;
@@ -58,6 +65,16 @@
mUids = null;
mNames = null;
}
+
+ if (orig.mChains != null) {
+ // Make a copy of all WorkChains that exist on |orig| since they are mutable.
+ mChains = new ArrayList<>(orig.mChains.size());
+ for (WorkChain chain : orig.mChains) {
+ mChains.add(new WorkChain(chain));
+ }
+ } else {
+ mChains = null;
+ }
}
/** @hide */
@@ -65,6 +82,7 @@
mNum = 1;
mUids = new int[] { uid, 0 };
mNames = null;
+ mChains = null;
}
/** @hide */
@@ -75,12 +93,21 @@
mNum = 1;
mUids = new int[] { uid, 0 };
mNames = new String[] { name, null };
+ mChains = null;
}
WorkSource(Parcel in) {
mNum = in.readInt();
mUids = in.createIntArray();
mNames = in.createStringArray();
+
+ int numChains = in.readInt();
+ if (numChains > 0) {
+ mChains = new ArrayList<>(numChains);
+ in.readParcelableList(mChains, WorkChain.class.getClassLoader());
+ } else {
+ mChains = null;
+ }
}
/** @hide */
@@ -99,7 +126,8 @@
}
/**
- * Clear names from this WorkSource. Uids are left intact.
+ * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
+ * intact.
*
* <p>Useful when combining with another WorkSource that doesn't have names.
* @hide
@@ -127,11 +155,16 @@
*/
public void clear() {
mNum = 0;
+ if (mChains != null) {
+ mChains.clear();
+ }
}
@Override
public boolean equals(Object o) {
- return o instanceof WorkSource && !diff((WorkSource)o);
+ return o instanceof WorkSource
+ && !diff((WorkSource) o)
+ && Objects.equals(mChains, ((WorkSource) o).mChains);
}
@Override
@@ -145,6 +178,11 @@
result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
}
}
+
+ if (mChains != null) {
+ result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
+ }
+
return result;
}
@@ -153,6 +191,8 @@
* @param other The WorkSource to compare against.
* @return If there is a difference, true is returned.
*/
+ // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
+ // we keep its semantics the same and ignore any differences in WorkChains (if any).
public boolean diff(WorkSource other) {
int N = mNum;
if (N != other.mNum) {
@@ -175,12 +215,15 @@
/**
* Replace the current contents of this work source with the given
- * work source. If <var>other</var> is null, the current work source
+ * work source. If {@code other} is null, the current work source
* will be made empty.
*/
public void set(WorkSource other) {
if (other == null) {
mNum = 0;
+ if (mChains != null) {
+ mChains.clear();
+ }
return;
}
mNum = other.mNum;
@@ -203,6 +246,18 @@
mUids = null;
mNames = null;
}
+
+ if (other.mChains != null) {
+ if (mChains != null) {
+ mChains.clear();
+ } else {
+ mChains = new ArrayList<>(other.mChains.size());
+ }
+
+ for (WorkChain chain : other.mChains) {
+ mChains.add(new WorkChain(chain));
+ }
+ }
}
/** @hide */
@@ -211,6 +266,7 @@
if (mUids == null) mUids = new int[2];
mUids[0] = uid;
mNames = null;
+ mChains.clear();
}
/** @hide */
@@ -225,9 +281,21 @@
}
mUids[0] = uid;
mNames[0] = name;
+ mChains.clear();
}
- /** @hide */
+ /**
+ * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
+ * differences in chains are returned. This will be removed once its callers have been
+ * rewritten.
+ *
+ * NOTE: This is currently only used in GnssLocationProvider.
+ *
+ * @hide
+ * @deprecated for internal use only. WorkSources are opaque and no external callers should need
+ * to be aware of internal differences.
+ */
+ @Deprecated
public WorkSource[] setReturningDiffs(WorkSource other) {
synchronized (sTmpWorkSource) {
sNewbWork = null;
@@ -251,11 +319,34 @@
*/
public boolean add(WorkSource other) {
synchronized (sTmpWorkSource) {
- return updateLocked(other, false, false);
+ boolean uidAdded = updateLocked(other, false, false);
+
+ boolean chainAdded = false;
+ if (other.mChains != null) {
+ // NOTE: This is quite an expensive operation, especially if the number of chains
+ // is large. We could look into optimizing it if it proves problematic.
+ if (mChains == null) {
+ mChains = new ArrayList<>(other.mChains.size());
+ }
+
+ for (WorkChain wc : other.mChains) {
+ if (!mChains.contains(wc)) {
+ mChains.add(new WorkChain(wc));
+ }
+ }
+ }
+
+ return uidAdded || chainAdded;
}
}
- /** @hide */
+ /**
+ * Legacy API: DO NOT USE. Only in use from unit tests.
+ *
+ * @hide
+ * @deprecated meant for unit testing use only. Will be removed in a future API revision.
+ */
+ @Deprecated
public WorkSource addReturningNewbs(WorkSource other) {
synchronized (sTmpWorkSource) {
sNewbWork = null;
@@ -311,22 +402,14 @@
return true;
}
- /** @hide */
- public WorkSource addReturningNewbs(int uid) {
- synchronized (sTmpWorkSource) {
- sNewbWork = null;
- sTmpWorkSource.mUids[0] = uid;
- updateLocked(sTmpWorkSource, false, true);
- return sNewbWork;
- }
- }
-
public boolean remove(WorkSource other) {
if (mNum <= 0 || other.mNum <= 0) {
return false;
}
+
+ boolean uidRemoved = false;
if (mNames == null && other.mNames == null) {
- return removeUids(other);
+ uidRemoved = removeUids(other);
} else {
if (mNames == null) {
throw new IllegalArgumentException("Other " + other + " has names, but target "
@@ -336,24 +419,44 @@
throw new IllegalArgumentException("Target " + this + " has names, but other "
+ other + " does not");
}
- return removeUidsAndNames(other);
+ uidRemoved = removeUidsAndNames(other);
}
+
+ boolean chainRemoved = false;
+ if (other.mChains != null) {
+ if (mChains != null) {
+ chainRemoved = mChains.removeAll(other.mChains);
+ }
+ } else if (mChains != null) {
+ mChains.clear();
+ chainRemoved = true;
+ }
+
+ return uidRemoved || chainRemoved;
}
- /** @hide */
- public WorkSource stripNames() {
- if (mNum <= 0) {
- return new WorkSource();
+ /**
+ * Create a new {@code WorkChain} associated with this WorkSource and return it.
+ *
+ * @hide
+ */
+ public WorkChain createWorkChain() {
+ if (mChains == null) {
+ mChains = new ArrayList<>(4);
}
- WorkSource result = new WorkSource();
- int lastUid = -1;
- for (int i=0; i<mNum; i++) {
- int uid = mUids[i];
- if (i == 0 || lastUid != uid) {
- result.add(uid);
- }
- }
- return result;
+
+ final WorkChain wc = new WorkChain();
+ mChains.add(wc);
+
+ return wc;
+ }
+
+ /**
+ * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
+ * @hide
+ */
+ public ArrayList<WorkChain> getWorkChains() {
+ return mChains;
}
private boolean removeUids(WorkSource other) {
@@ -664,6 +767,167 @@
}
}
+ /**
+ * Represents an attribution chain for an item of work being performed. An attribution chain is
+ * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
+ * of the work, and the node at the highest index performs the work. Nodes at other indices
+ * are intermediaries that facilitate the work. Examples :
+ *
+ * (1) Work being performed by uid=2456 (no chaining):
+ * <pre>
+ * WorkChain {
+ * mUids = { 2456 }
+ * mTags = { null }
+ * mSize = 1;
+ * }
+ * </pre>
+ *
+ * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
+ *
+ * <pre>
+ * WorkChain {
+ * mUids = { 5678, 2456 }
+ * mTags = { null, "c1" }
+ * mSize = 1
+ * }
+ * </pre>
+ *
+ * Attribution chains are mutable, though the only operation that can be performed on them
+ * is the addition of a new node at the end of the attribution chain to represent
+ *
+ * @hide
+ */
+ public static class WorkChain implements Parcelable {
+ private int mSize;
+ private int[] mUids;
+ private String[] mTags;
+
+ // @VisibleForTesting
+ public WorkChain() {
+ mSize = 0;
+ mUids = new int[4];
+ mTags = new String[4];
+ }
+
+ // @VisibleForTesting
+ public WorkChain(WorkChain other) {
+ mSize = other.mSize;
+ mUids = other.mUids.clone();
+ mTags = other.mTags.clone();
+ }
+
+ private WorkChain(Parcel in) {
+ mSize = in.readInt();
+ mUids = in.createIntArray();
+ mTags = in.createStringArray();
+ }
+
+ /**
+ * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
+ * {@code WorkChain}.
+ */
+ public WorkChain addNode(int uid, @Nullable String tag) {
+ if (mSize == mUids.length) {
+ resizeArrays();
+ }
+
+ mUids[mSize] = uid;
+ mTags[mSize] = tag;
+ mSize++;
+
+ return this;
+ }
+
+ // TODO: The following three trivial getters are purely for testing and will be removed
+ // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
+ // diffing it etc.
+ //
+ // @VisibleForTesting
+ public int[] getUids() {
+ return mUids;
+ }
+ // @VisibleForTesting
+ public String[] getTags() {
+ return mTags;
+ }
+ // @VisibleForTesting
+ public int getSize() {
+ return mSize;
+ }
+
+ private void resizeArrays() {
+ final int newSize = mSize * 2;
+ int[] uids = new int[newSize];
+ String[] tags = new String[newSize];
+
+ System.arraycopy(mUids, 0, uids, 0, mSize);
+ System.arraycopy(mTags, 0, tags, 0, mSize);
+
+ mUids = uids;
+ mTags = tags;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("WorkChain{");
+ for (int i = 0; i < mSize; ++i) {
+ if (i != 0) {
+ result.append(", ");
+ }
+ result.append("(");
+ result.append(mUids[i]);
+ if (mTags[i] != null) {
+ result.append(", ");
+ result.append(mTags[i]);
+ }
+ result.append(")");
+ }
+
+ result.append("}");
+ return result.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof WorkChain) {
+ WorkChain other = (WorkChain) o;
+
+ return mSize == other.mSize
+ && Arrays.equals(mUids, other.mUids)
+ && Arrays.equals(mTags, other.mTags);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSize);
+ dest.writeIntArray(mUids);
+ dest.writeStringArray(mTags);
+ }
+
+ public static final Parcelable.Creator<WorkChain> CREATOR =
+ new Parcelable.Creator<WorkChain>() {
+ public WorkChain createFromParcel(Parcel in) {
+ return new WorkChain(in);
+ }
+ public WorkChain[] newArray(int size) {
+ return new WorkChain[size];
+ }
+ };
+ }
+
@Override
public int describeContents() {
return 0;
@@ -674,6 +938,13 @@
dest.writeInt(mNum);
dest.writeIntArray(mUids);
dest.writeStringArray(mNames);
+
+ if (mChains == null) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(mChains.size());
+ dest.writeParcelableList(mChains, flags);
+ }
}
@Override
@@ -690,6 +961,17 @@
result.append(mNames[i]);
}
}
+
+ if (mChains != null) {
+ result.append(" chains=");
+ for (int i = 0; i < mChains.size(); ++i) {
+ if (i != 0) {
+ result.append(", ");
+ }
+ result.append(mChains.get(i));
+ }
+ }
+
result.append("}");
return result.toString();
}
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
index 2205c41..978e60e 100644
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
+++ b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
@@ -60,7 +60,7 @@
/**
* Creates instance of the class to to derive key using salted SHA256 hash.
*/
- public KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
+ public static KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
return new KeyDerivationParameters(ALGORITHM_SHA256, salt);
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 2600f8a..917efa8 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -438,7 +438,7 @@
* AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
*
* save(username, password);
- * </pre>
+ * </pre>
*
* <a name="Privacy"></a>
* <h3>Privacy</h3>
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 71e039a..6bca37a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -42,8 +42,7 @@
* {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
* Canvas.drawText()} directly.</p>
*/
-public class DynamicLayout extends Layout
-{
+public class DynamicLayout extends Layout {
private static final int PRIORITY = 128;
private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400;
@@ -303,8 +302,9 @@
}
/**
- * Make a layout for the specified text that will be updated as the text is changed.
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public DynamicLayout(@NonNull CharSequence base,
@NonNull TextPaint paint,
@IntRange(from = 0) int width, @NonNull Alignment align,
@@ -315,9 +315,9 @@
}
/**
- * Make a layout for the transformed text (password transformation being the primary example of
- * a transformation) that will be updated as the base text is changed.
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
@NonNull TextPaint paint,
@IntRange(from = 0) int width, @NonNull Alignment align,
@@ -328,10 +328,9 @@
}
/**
- * Make a layout for the transformed text (password transformation being the primary example of
- * a transformation) that will be updated as the base text is changed. If ellipsize is non-null,
- * the Layout will ellipsize the text down to ellipsizedWidth.
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
@NonNull TextPaint paint,
@IntRange(from = 0) int width, @NonNull Alignment align,
@@ -351,7 +350,9 @@
* the Layout will ellipsize the text down to ellipsizedWidth.
*
* @hide
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
@NonNull TextPaint paint,
@IntRange(from = 0) int width,
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/core/java/android/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 2e10fe8d..d69b119 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -454,6 +454,10 @@
private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
}
+ /**
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
public StaticLayout(CharSequence source, TextPaint paint,
int width,
Alignment align, float spacingmult, float spacingadd,
@@ -463,16 +467,9 @@
}
/**
- * @hide
+ * @deprecated Use {@link Builder} instead.
*/
- public StaticLayout(CharSequence source, TextPaint paint,
- int width, Alignment align, TextDirectionHeuristic textDir,
- float spacingmult, float spacingadd,
- boolean includepad) {
- this(source, 0, source.length(), paint, width, align, textDir,
- spacingmult, spacingadd, includepad);
- }
-
+ @Deprecated
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
@@ -483,17 +480,9 @@
}
/**
- * @hide
+ * @deprecated Use {@link Builder} instead.
*/
- public StaticLayout(CharSequence source, int bufstart, int bufend,
- TextPaint paint, int outerwidth,
- Alignment align, TextDirectionHeuristic textDir,
- float spacingmult, float spacingadd,
- boolean includepad) {
- this(source, bufstart, bufend, paint, outerwidth, align, textDir,
- spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
-}
-
+ @Deprecated
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
@@ -507,7 +496,9 @@
/**
* @hide
+ * @deprecated Use {@link Builder} instead.
*/
+ @Deprecated
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align, TextDirectionHeuristic textDir,
@@ -565,6 +556,9 @@
Builder.recycle(b);
}
+ /**
+ * Used by DynamicLayout.
+ */
/* package */ StaticLayout(@Nullable CharSequence text) {
super(text, null, 0, null, 0, 0);
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 19cd42e..e448f14 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -21,40 +21,37 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import android.annotation.NonNull;
+import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.ArrayList;
import java.util.List;
/**
* Represents a part of the display that is not functional for displaying content.
*
* <p>{@code DisplayCutout} is immutable.
- *
- * @hide will become API
*/
public final class DisplayCutout {
- private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0);
- private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>();
+ private static final Rect ZERO_RECT = new Rect();
+ private static final Region EMPTY_REGION = new Region();
/**
- * An instance where {@link #hasCutout()} returns {@code false}.
+ * An instance where {@link #isEmpty()} returns {@code true}.
*
* @hide
*/
- public static final DisplayCutout NO_CUTOUT =
- new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST);
+ public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION);
private final Rect mSafeInsets;
- private final Rect mBoundingRect;
- private final List<Point> mBoundingPolygon;
+ private final Region mBounds;
/**
* Creates a DisplayCutout instance.
@@ -64,22 +61,18 @@
* @hide
*/
@VisibleForTesting
- public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) {
+ public DisplayCutout(Rect safeInsets, Region bounds) {
mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
- mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT;
- mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST;
+ mBounds = bounds != null ? bounds : Region.obtain();
}
/**
- * Returns whether there is a cutout.
+ * Returns true if there is no cutout or it is outside of the content view.
*
- * If false, the safe insets will all return zero, and the bounding box or polygon will be
- * empty or outside the content view.
- *
- * @return {@code true} if there is a cutout, {@code false} otherwise
+ * @hide
*/
- public boolean hasCutout() {
- return !mSafeInsets.equals(ZERO_RECT);
+ public boolean isEmpty() {
+ return mSafeInsets.equals(ZERO_RECT);
}
/** Returns the inset from the top which avoids the display cutout. */
@@ -103,44 +96,41 @@
}
/**
- * Obtains the safe insets in a rect.
+ * Returns the safe insets in a rect.
*
- * @param out a rect which is set to the safe insets.
+ * @return a rect which is set to the safe insets.
* @hide
*/
- public void getSafeInsets(@NonNull Rect out) {
- out.set(mSafeInsets);
+ public Rect getSafeInsets() {
+ return new Rect(mSafeInsets);
}
/**
- * Obtains the bounding rect of the cutout.
+ * Returns the bounding region of the cutout.
*
- * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative
+ * @return the bounding region of the cutout. Coordinates are relative
* to the top-left corner of the content view.
*/
- public void getBoundingRect(@NonNull Rect outRect) {
- outRect.set(mBoundingRect);
+ public Region getBounds() {
+ return Region.obtain(mBounds);
}
/**
- * Obtains the bounding polygon of the cutout.
+ * Returns the bounding rect of the cutout.
*
- * @param outPolygon is filled with a list of points representing the corners of a convex
- * polygon which covers the cutout. Coordinates are relative to the
- * top-left corner of the content view.
+ * @return the bounding rect of the cutout. Coordinates are relative
+ * to the top-left corner of the content view.
+ * @hide
*/
- public void getBoundingPolygon(List<Point> outPolygon) {
- outPolygon.clear();
- for (int i = 0; i < mBoundingPolygon.size(); i++) {
- outPolygon.add(new Point(mBoundingPolygon.get(i)));
- }
+ public Rect getBoundingRect() {
+ // TODO(roosa): Inline.
+ return mBounds.getBounds();
}
@Override
public int hashCode() {
int result = mSafeInsets.hashCode();
- result = result * 31 + mBoundingRect.hashCode();
- result = result * 31 + mBoundingPolygon.hashCode();
+ result = result * 31 + mBounds.getBounds().hashCode();
return result;
}
@@ -152,8 +142,7 @@
if (o instanceof DisplayCutout) {
DisplayCutout c = (DisplayCutout) o;
return mSafeInsets.equals(c.mSafeInsets)
- && mBoundingRect.equals(c.mBoundingRect)
- && mBoundingPolygon.equals(c.mBoundingPolygon);
+ && mBounds.equals(c.mBounds);
}
return false;
}
@@ -161,7 +150,7 @@
@Override
public String toString() {
return "DisplayCutout{insets=" + mSafeInsets
- + " bounding=" + mBoundingRect
+ + " bounds=" + mBounds
+ "}";
}
@@ -172,15 +161,13 @@
* @hide
*/
public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
- if (mBoundingRect.isEmpty()
+ if (mBounds.isEmpty()
|| insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
return this;
}
Rect safeInsets = new Rect(mSafeInsets);
- Rect boundingRect = new Rect(mBoundingRect);
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- getBoundingPolygon(boundingPolygon);
+ Region bounds = Region.obtain(mBounds);
// Note: it's not really well defined what happens when the inset is negative, because we
// don't know if the safe inset needs to expand in general.
@@ -197,10 +184,9 @@
safeInsets.right = atLeastZero(safeInsets.right - insetRight);
}
- boundingRect.offset(-insetLeft, -insetTop);
- offset(boundingPolygon, -insetLeft, -insetTop);
+ bounds.translate(-insetLeft, -insetTop);
- return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+ return new DisplayCutout(safeInsets, bounds);
}
/**
@@ -210,20 +196,17 @@
* @hide
*/
public DisplayCutout calculateRelativeTo(Rect frame) {
- if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) {
+ if (mBounds.isEmpty() || !Rect.intersects(frame, mBounds.getBounds())) {
return NO_CUTOUT;
}
- Rect boundingRect = new Rect(mBoundingRect);
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- getBoundingPolygon(boundingPolygon);
-
- return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon);
+ return DisplayCutout.calculateRelativeTo(frame, Region.obtain(mBounds));
}
- private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect,
- ArrayList<Point> boundingPolygon) {
+ private static DisplayCutout calculateRelativeTo(Rect frame, Region bounds) {
+ Rect boundingRect = bounds.getBounds();
Rect safeRect = new Rect();
+
int bestArea = 0;
int bestVariant = 0;
for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) {
@@ -247,10 +230,9 @@
Math.max(0, frame.bottom - safeRect.bottom));
}
- boundingRect.offset(-frame.left, -frame.top);
- offset(boundingPolygon, -frame.left, -frame.top);
+ bounds.translate(-frame.left, -frame.top);
- return new DisplayCutout(safeRect, boundingRect, boundingPolygon);
+ return new DisplayCutout(safeRect, bounds);
}
private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant,
@@ -277,11 +259,6 @@
return value < 0 ? 0 : value;
}
- private static void offset(ArrayList<Point> points, int dx, int dy) {
- for (int i = 0; i < points.size(); i++) {
- points.get(i).offset(dx, dy);
- }
- }
/**
* Creates an instance from a bounding polygon.
@@ -289,20 +266,28 @@
* @hide
*/
public static DisplayCutout fromBoundingPolygon(List<Point> points) {
- Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE,
- Integer.MIN_VALUE, Integer.MIN_VALUE);
- ArrayList<Point> boundingPolygon = new ArrayList<>();
+ Region bounds = Region.obtain();
+ Path path = new Path();
+ path.reset();
for (int i = 0; i < points.size(); i++) {
Point point = points.get(i);
- boundingRect.left = Math.min(boundingRect.left, point.x);
- boundingRect.right = Math.max(boundingRect.right, point.x);
- boundingRect.top = Math.min(boundingRect.top, point.y);
- boundingRect.bottom = Math.max(boundingRect.bottom, point.y);
- boundingPolygon.add(new Point(point));
+ if (i == 0) {
+ path.moveTo(point.x, point.y);
+ } else {
+ path.lineTo(point.x, point.y);
+ }
}
+ path.close();
- return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon);
+ RectF clipRect = new RectF();
+ path.computeBounds(clipRect, false /* unused */);
+ Region clipRegion = Region.obtain();
+ clipRegion.set((int) clipRect.left, (int) clipRect.top,
+ (int) clipRect.right, (int) clipRect.bottom);
+
+ bounds.setPath(path, clipRegion);
+ return new DisplayCutout(ZERO_RECT, bounds);
}
/**
@@ -336,8 +321,7 @@
} else {
out.writeInt(1);
out.writeTypedObject(mInner.mSafeInsets, flags);
- out.writeTypedObject(mInner.mBoundingRect, flags);
- out.writeTypedList(mInner.mBoundingPolygon, flags);
+ out.writeTypedObject(mInner.mBounds, flags);
}
}
@@ -368,13 +352,10 @@
return NO_CUTOUT;
}
- ArrayList<Point> boundingPolygon = new ArrayList<>();
-
Rect safeInsets = in.readTypedObject(Rect.CREATOR);
- Rect boundingRect = in.readTypedObject(Rect.CREATOR);
- in.readTypedList(boundingPolygon, Point.CREATOR);
+ Region bounds = in.readTypedObject(Region.CREATOR);
- return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+ return new DisplayCutout(safeInsets, bounds);
}
public DisplayCutout get() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02beee0..cc63a62 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -26890,7 +26890,7 @@
if (mAttachInfo == null || mTooltipInfo == null) {
return false;
}
- if ((mViewFlags & ENABLED_MASK) != ENABLED) {
+ if (fromLongClick && (mViewFlags & ENABLED_MASK) != ENABLED) {
return false;
}
if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) {
@@ -26938,7 +26938,7 @@
}
switch(event.getAction()) {
case MotionEvent.ACTION_HOVER_MOVE:
- if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) {
+ if ((mViewFlags & TOOLTIP) != TOOLTIP) {
break;
}
if (!mTooltipInfo.mTooltipFromLongClick && mTooltipInfo.updateAnchorPos(event)) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2c82ac4..6c5091c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1602,7 +1602,7 @@
if (!layoutInCutout) {
// Window is either not laid out in cutout or the status bar inset takes care of
// clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
- insets = insets.consumeCutout();
+ insets = insets.consumeDisplayCutout();
}
host.dispatchApplyWindowInsets(insets);
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index df124ac..e5cbe96 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -17,7 +17,7 @@
package android.view;
-import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
/**
@@ -49,7 +49,7 @@
private boolean mSystemWindowInsetsConsumed = false;
private boolean mWindowDecorInsetsConsumed = false;
private boolean mStableInsetsConsumed = false;
- private boolean mCutoutConsumed = false;
+ private boolean mDisplayCutoutConsumed = false;
private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
@@ -80,8 +80,9 @@
mIsRound = isRound;
mAlwaysConsumeNavBar = alwaysConsumeNavBar;
- mCutoutConsumed = displayCutout == null;
- mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout;
+ mDisplayCutoutConsumed = displayCutout == null;
+ mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
+ ? null : displayCutout;
}
/**
@@ -99,7 +100,7 @@
mIsRound = src.mIsRound;
mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
mDisplayCutout = src.mDisplayCutout;
- mCutoutConsumed = src.mCutoutConsumed;
+ mDisplayCutoutConsumed = src.mDisplayCutoutConsumed;
}
/** @hide */
@@ -269,15 +270,16 @@
*/
public boolean hasInsets() {
return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
- || mDisplayCutout.hasCutout();
+ || mDisplayCutout != null;
}
/**
- * @return the display cutout
+ * Returns the display cutout if there is one.
+ *
+ * @return the display cutout or null if there is none
* @see DisplayCutout
- * @hide pending API
*/
- @NonNull
+ @Nullable
public DisplayCutout getDisplayCutout() {
return mDisplayCutout;
}
@@ -286,12 +288,11 @@
* Returns a copy of this WindowInsets with the cutout fully consumed.
*
* @return A modified copy of this WindowInsets
- * @hide pending API
*/
- public WindowInsets consumeCutout() {
+ public WindowInsets consumeDisplayCutout() {
final WindowInsets result = new WindowInsets(this);
- result.mDisplayCutout = DisplayCutout.NO_CUTOUT;
- result.mCutoutConsumed = true;
+ result.mDisplayCutout = null;
+ result.mDisplayCutoutConsumed = true;
return result;
}
@@ -311,7 +312,7 @@
*/
public boolean isConsumed() {
return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
- && mCutoutConsumed;
+ && mDisplayCutoutConsumed;
}
/**
@@ -530,7 +531,7 @@
return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
+ " windowDecorInsets=" + mWindowDecorInsets
+ " stableInsets=" + mStableInsets
- + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "")
+ + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "")
+ (isRound() ? " round" : "")
+ "}";
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 012e864..cbe012a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1286,7 +1286,6 @@
* The window must correctly position its contents to take the display cutout into account.
*
* @see DisplayCutout
- * @hide for now
*/
public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001;
@@ -1294,7 +1293,6 @@
* Various behavioral options/flags. Default is none.
*
* @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA
- * @hide for now
*/
@Flags2 public long flags2;
@@ -2249,6 +2247,7 @@
out.writeInt(y);
out.writeInt(type);
out.writeInt(flags);
+ out.writeLong(flags2);
out.writeInt(privateFlags);
out.writeInt(softInputMode);
out.writeInt(gravity);
@@ -2304,6 +2303,7 @@
y = in.readInt();
type = in.readInt();
flags = in.readInt();
+ flags2 = in.readLong();
privateFlags = in.readInt();
softInputMode = in.readInt();
gravity = in.readInt();
@@ -2436,6 +2436,10 @@
flags = o.flags;
changes |= FLAGS_CHANGED;
}
+ if (flags2 != o.flags2) {
+ flags2 = o.flags2;
+ changes |= FLAGS_CHANGED;
+ }
if (privateFlags != o.privateFlags) {
privateFlags = o.privateFlags;
changes |= PRIVATE_FLAGS_CHANGED;
@@ -2689,6 +2693,11 @@
sb.append(System.lineSeparator());
sb.append(prefix).append(" fl=").append(
ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+ if (flags2 != 0) {
+ sb.append(System.lineSeparator());
+ // TODO(roosa): add a long overload for ViewDebug.flagsToString.
+ sb.append(prefix).append(" fl2=0x").append(Long.toHexString(flags2));
+ }
if (privateFlags != 0) {
sb.append(System.lineSeparator());
sb.append(prefix).append(" pfl=").append(ViewDebug.flagsToString(
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9c2f6bb..28ef697 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2325,7 +2325,7 @@
/**
* Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note
* that {@code false} indicates that it is not explicitly marked, not that the node is not
- * a focusable unit. Screen readers should generally used other signals, such as
+ * a focusable unit. Screen readers should generally use other signals, such as
* {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
* focus.
*
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index f11767d..ef1a3f3 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -87,6 +87,7 @@
private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
+ private static final int BOOLEAN_PROPERTY_PICTURE_IN_PICTURE = 1 << 3;
// Housekeeping.
private static final int MAX_POOL_SIZE = 10;
@@ -103,8 +104,7 @@
private final Rect mBoundsInScreen = new Rect();
private LongArray mChildIds;
private CharSequence mTitle;
- private int mAnchorId = UNDEFINED_WINDOW_ID;
- private boolean mInPictureInPicture;
+ private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
private int mConnectionId = UNDEFINED_WINDOW_ID;
@@ -202,7 +202,7 @@
*
* @hide
*/
- public void setAnchorId(int anchorId) {
+ public void setAnchorId(long anchorId) {
mAnchorId = anchorId;
}
@@ -212,7 +212,8 @@
* @return The anchor node, or {@code null} if none exists.
*/
public AccessibilityNodeInfo getAnchor() {
- if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID)
+ if ((mConnectionId == UNDEFINED_WINDOW_ID)
+ || (mAnchorId == AccessibilityNodeInfo.UNDEFINED_NODE_ID)
|| (mParentId == UNDEFINED_WINDOW_ID)) {
return null;
}
@@ -224,17 +225,7 @@
/** @hide */
public void setPictureInPicture(boolean pictureInPicture) {
- mInPictureInPicture = pictureInPicture;
- }
-
- /**
- * Check if the window is in picture-in-picture mode.
- *
- * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
- * @removed
- */
- public boolean inPictureInPicture() {
- return isInPictureInPictureMode();
+ setBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE, pictureInPicture);
}
/**
@@ -243,7 +234,7 @@
* @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
*/
public boolean isInPictureInPictureMode() {
- return mInPictureInPicture;
+ return getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE);
}
/**
@@ -463,7 +454,6 @@
infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
infoClone.mTitle = info.mTitle;
infoClone.mAnchorId = info.mAnchorId;
- infoClone.mInPictureInPicture = info.mInPictureInPicture;
if (info.mChildIds != null && info.mChildIds.size() > 0) {
if (infoClone.mChildIds == null) {
@@ -520,8 +510,7 @@
parcel.writeInt(mParentId);
mBoundsInScreen.writeToParcel(parcel, flags);
parcel.writeCharSequence(mTitle);
- parcel.writeInt(mAnchorId);
- parcel.writeInt(mInPictureInPicture ? 1 : 0);
+ parcel.writeLong(mAnchorId);
final LongArray childIds = mChildIds;
if (childIds == null) {
@@ -545,8 +534,7 @@
mParentId = parcel.readInt();
mBoundsInScreen.readFromParcel(parcel);
mTitle = parcel.readCharSequence();
- mAnchorId = parcel.readInt();
- mInPictureInPicture = parcel.readInt() == 1;
+ mAnchorId = parcel.readLong();
final int childCount = parcel.readInt();
if (childCount > 0) {
@@ -593,7 +581,7 @@
builder.append(", bounds=").append(mBoundsInScreen);
builder.append(", focused=").append(isFocused());
builder.append(", active=").append(isActive());
- builder.append(", pictureInPicture=").append(inPictureInPicture());
+ builder.append(", pictureInPicture=").append(isInPictureInPictureMode());
if (DEBUG) {
builder.append(", parent=").append(mParentId);
builder.append(", children=[");
@@ -611,7 +599,8 @@
builder.append(']');
} else {
builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID);
- builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID);
+ builder.append(", isAnchored=")
+ .append(mAnchorId != AccessibilityNodeInfo.UNDEFINED_NODE_ID);
builder.append(", hasChildren=").append(mChildIds != null
&& mChildIds.size() > 0);
}
@@ -633,8 +622,7 @@
mChildIds.clear();
}
mConnectionId = UNDEFINED_WINDOW_ID;
- mAnchorId = UNDEFINED_WINDOW_ID;
- mInPictureInPicture = false;
+ mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
mTitle = null;
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index d0dbff0e..81b065c 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -530,10 +530,13 @@
* @return whether autofill is enabled for the current user.
*/
public boolean isEnabled() {
- if (!hasAutofillFeature() || isDisabledByService()) {
+ if (!hasAutofillFeature()) {
return false;
}
synchronized (mLock) {
+ if (isDisabledByServiceLocked()) {
+ return false;
+ }
ensureServiceClientAddedIfNeededLocked();
return mEnabled;
}
@@ -605,19 +608,16 @@
}
private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
- if (isDisabledByService()) {
+ if (isDisabledByServiceLocked()) {
if (sVerbose) {
Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ ") on state " + getStateAsStringLocked());
}
return true;
}
- if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
- if (sVerbose) {
- Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
- + ") on state " + getStateAsStringLocked());
- }
- return true;
+ if (sVerbose && isFinishedLocked()) {
+ Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ + ") on state " + getStateAsStringLocked());
}
return false;
}
@@ -1139,10 +1139,10 @@
Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
+ ", flags=" + flags + ", state=" + getStateAsStringLocked());
}
- if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
if (sVerbose) {
Log.v(TAG, "not automatically starting session for " + id
- + " on state " + getStateAsStringLocked());
+ + " on state " + getStateAsStringLocked() + " and flags " + flags);
}
return;
}
@@ -1744,10 +1744,14 @@
return mState == STATE_ACTIVE;
}
- private boolean isDisabledByService() {
+ private boolean isDisabledByServiceLocked() {
return mState == STATE_DISABLED_BY_SERVICE;
}
+ private boolean isFinishedLocked() {
+ return mState == STATE_FINISHED;
+ }
+
private void post(Runnable runnable) {
final AutofillClient client = getClient();
if (client == null) {
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
new file mode 100644
index 0000000..8f0d02f
--- /dev/null
+++ b/core/java/android/widget/OWNERS
@@ -0,0 +1,12 @@
+per-file TextView.java = siyamed@google.com
+per-file TextView.java = nona@google.com
+per-file TextView.java = clarabayarri@google.com
+per-file TextView.java = toki@google.com
+per-file EditText.java = siyamed@google.com
+per-file EditText.java = nona@google.com
+per-file EditText.java = clarabayarri@google.com
+per-file EditText.java = toki@google.com
+per-file Editor.java = siyamed@google.com
+per-file Editor.java = nona@google.com
+per-file Editor.java = clarabayarri@google.com
+per-file Editor.java = toki@google.com
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9ac443b..1e17f34 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4867,6 +4867,10 @@
* Sets line spacing for this TextView. Each line other than the last line will have its height
* multiplied by {@code mult} and have {@code add} added to it.
*
+ * @param add The value in pixels that should be added to each line other than the last line.
+ * This will be applied after the multiplier
+ * @param mult The value by which each line height other than the last line will be multiplied
+ * by
*
* @attr ref android.R.styleable#TextView_lineSpacingExtra
* @attr ref android.R.styleable#TextView_lineSpacingMultiplier
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 8016a65..2eadaf3 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -111,7 +111,7 @@
@Override
public void onClick(DialogInterface dialog, int which) {
if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) {
- UserManager.get(this).trySetQuietModeDisabled(mUserId, mTarget);
+ UserManager.get(this).trySetQuietModeEnabled(false, UserHandle.of(mUserId), mTarget);
}
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 3aca798..efc9c02 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -91,13 +91,13 @@
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
STATE_BACKUP, // ActivityManager.PROCESS_STATE_BACKUP
STATE_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE
STATE_RECEIVER, // ActivityManager.PROCESS_STATE_RECEIVER
+ STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
STATE_HEAVY_WEIGHT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
STATE_HOME, // ActivityManager.PROCESS_STATE_HOME
STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index bef247f..6510a70 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 170 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 171 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -8678,13 +8678,15 @@
} else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
// Persistent and other foreground states go here.
uidRunningState = PROCESS_STATE_FOREGROUND_SERVICE;
- } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
- uidRunningState = PROCESS_STATE_TOP_SLEEPING;
} else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
// Persistent and other foreground states go here.
uidRunningState = PROCESS_STATE_FOREGROUND;
} else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
uidRunningState = PROCESS_STATE_BACKGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ uidRunningState = PROCESS_STATE_TOP_SLEEPING;
+ } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+ uidRunningState = PROCESS_STATE_HEAVY_WEIGHT;
} else {
uidRunningState = PROCESS_STATE_CACHED;
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index f983de1..7985e57 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -290,11 +290,11 @@
if (cur instanceof ArraySet) {
ArraySet<T> arraySet = (ArraySet<T>) cur;
for (int i = 0; i < size; i++) {
- action.accept(arraySet.valueAt(i));
+ action.acceptOrThrow(arraySet.valueAt(i));
}
} else {
for (T t : cur) {
- action.accept(t);
+ action.acceptOrThrow(t);
}
}
} catch (Exception e) {
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index eb92c1c..82ac241 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -16,6 +16,9 @@
package com.android.internal.util;
+import android.os.RemoteException;
+
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -25,6 +28,21 @@
private FunctionalUtils() {}
/**
+ * Converts a lambda expression that throws a checked exception(s) into a regular
+ * {@link Consumer} by propagating any checked exceptions as {@link RuntimeException}
+ */
+ public static <T> Consumer<T> uncheckExceptions(ThrowingConsumer<T> action) {
+ return action;
+ }
+
+ /**
+ *
+ */
+ public static <T> Consumer<T> ignoreRemoteException(RemoteExceptionIgnoringConsumer<T> action) {
+ return action;
+ }
+
+ /**
* An equivalent of {@link Runnable} that allows throwing checked exceptions
*
* This can be used to specify a lambda argument without forcing all the checked exceptions
@@ -47,13 +65,43 @@
}
/**
- * An equivalent of {@link java.util.function.Consumer} that allows throwing checked exceptions
+ * A {@link Consumer} that allows throwing checked exceptions from its single abstract method.
*
- * This can be used to specify a lambda argument without forcing all the checked exceptions
- * to be handled within it
+ * Can be used together with {@link #uncheckExceptions} to effectively turn a lambda expression
+ * that throws a checked exception into a regular {@link Consumer}
*/
@FunctionalInterface
- public interface ThrowingConsumer<T> {
- void accept(T t) throws Exception;
+ @SuppressWarnings("FunctionalInterfaceMethodChanged")
+ public interface ThrowingConsumer<T> extends Consumer<T> {
+ void acceptOrThrow(T t) throws Exception;
+
+ @Override
+ default void accept(T t) {
+ try {
+ acceptOrThrow(t);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ /**
+ * A {@link Consumer} that automatically ignores any {@link RemoteException}s.
+ *
+ * Used by {@link #ignoreRemoteException}
+ */
+ @FunctionalInterface
+ @SuppressWarnings("FunctionalInterfaceMethodChanged")
+ public interface RemoteExceptionIgnoringConsumer<T> extends Consumer<T> {
+ void acceptOrThrow(T t) throws RemoteException;
+
+ @Override
+ default void accept(T t) {
+ try {
+ acceptOrThrow(t);
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ }
}
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8df0fb8..297bae2 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -123,6 +123,7 @@
"android_graphics_Picture.cpp",
"android/graphics/Bitmap.cpp",
"android/graphics/BitmapFactory.cpp",
+ "android/graphics/ByteBufferStreamAdaptor.cpp",
"android/graphics/Camera.cpp",
"android/graphics/CanvasProperty.cpp",
"android/graphics/ColorFilter.cpp",
@@ -134,6 +135,7 @@
"android/graphics/GraphicBuffer.cpp",
"android/graphics/Graphics.cpp",
"android/graphics/HarfBuzzNGFaceSkia.cpp",
+ "android/graphics/ImageDecoder.cpp",
"android/graphics/Interpolator.cpp",
"android/graphics/MaskFilter.cpp",
"android/graphics/Matrix.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9e907bd..b5d1868 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -57,10 +57,12 @@
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
+extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
+extern int register_android_graphics_ImageDecoder(JNIEnv*);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
@@ -644,6 +646,7 @@
char methodTraceFileBuf[sizeof("-Xmethod-trace-file:") + PROPERTY_VALUE_MAX];
char methodTraceFileSizeBuf[sizeof("-Xmethod-trace-file-size:") + PROPERTY_VALUE_MAX];
std::string fingerprintBuf;
+ char jdwpProviderBuf[sizeof("-XjdwpProvider:") - 1 + PROPERTY_VALUE_MAX];
bool checkJni = false;
property_get("dalvik.vm.checkjni", propBuf, "");
@@ -766,9 +769,15 @@
* Set suspend=y to pause during VM init and use android ADB transport.
*/
if (zygote) {
- addOption("-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y");
+ addOption("-XjdwpOptions:suspend=n,server=y");
}
+ // Set the JDWP provider. By default let the runtime choose.
+ parseRuntimeOption("dalvik.vm.jdwp-provider",
+ jdwpProviderBuf,
+ "-XjdwpProvider:",
+ "default");
+
parseRuntimeOption("dalvik.vm.lockprof.threshold",
lockProfThresholdBuf,
"-Xlockprofthreshold:");
@@ -1380,6 +1389,7 @@
REG_JNI(register_android_graphics_Bitmap),
REG_JNI(register_android_graphics_BitmapFactory),
REG_JNI(register_android_graphics_BitmapRegionDecoder),
+ REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
REG_JNI(register_android_graphics_Camera),
REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
REG_JNI(register_android_graphics_CanvasProperty),
@@ -1387,6 +1397,7 @@
REG_JNI(register_android_graphics_DrawFilter),
REG_JNI(register_android_graphics_FontFamily),
REG_JNI(register_android_graphics_GraphicBuffer),
+ REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 2e8c27a..79aa5ac 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -47,9 +47,6 @@
jfieldID gBitmap_ninePatchInsetsFieldID;
-jclass gInsetStruct_class;
-jmethodID gInsetStruct_constructorMethodID;
-
jclass gBitmapConfig_class;
jmethodID gBitmapConfig_nativeToConfigMethodID;
@@ -99,43 +96,6 @@
return jstr;
}
-static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
- for (int i = 0; i < count; i++) {
- divs[i] = int32_t(divs[i] * scale + 0.5f);
- if (i > 0 && divs[i] == divs[i - 1]) {
- divs[i]++; // avoid collisions
- }
- }
-
- if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
- // if the collision avoidance above put some divs outside the bounds of the bitmap,
- // slide outer stretchable divs inward to stay within bounds
- int highestAvailable = maxValue;
- for (int i = count - 1; i >= 0; i--) {
- divs[i] = highestAvailable;
- if (i > 0 && divs[i] <= divs[i-1]){
- // keep shifting
- highestAvailable = divs[i] - 1;
- } else {
- break;
- }
- }
- }
-}
-
-static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale,
- int scaledWidth, int scaledHeight) {
- chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
- chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
- chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
- chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
-
- // The max value for the divRange is one pixel less than the actual max to ensure that the size
- // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
- scaleDivRange(chunk->getXDivs(), chunk->numXDivs, scale, scaledWidth - 1);
- scaleDivRange(chunk->getYDivs(), chunk->numYDivs, scale, scaledHeight - 1);
-}
-
class ScaleCheckingAllocator : public SkBitmap::HeapAllocator {
public:
ScaleCheckingAllocator(float scale, int size)
@@ -425,10 +385,18 @@
return nullObjectReturn("codec->getAndroidPixels() failed.");
}
+ // This is weird so let me explain: we could use the scale parameter
+ // directly, but for historical reasons this is how the corresponding
+ // Dalvik code has always behaved. We simply recreate the behavior here.
+ // The result is slightly different from simply using scale because of
+ // the 0.5f rounding bias applied when computing the target image size
+ const float scaleX = scaledWidth / float(decodingBitmap.width());
+ const float scaleY = scaledHeight / float(decodingBitmap.height());
+
jbyteArray ninePatchChunk = NULL;
if (peeker.mPatch != NULL) {
if (willScale) {
- scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
+ peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight);
}
size_t ninePatchArraySize = peeker.mPatch->serializedSize();
@@ -448,12 +416,7 @@
jobject ninePatchInsets = NULL;
if (peeker.mHasInsets) {
- ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
- peeker.mOpticalInsets[0], peeker.mOpticalInsets[1],
- peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
- peeker.mOutlineInsets[0], peeker.mOutlineInsets[1],
- peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
- peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
+ ninePatchInsets = peeker.createNinePatchInsets(env, scale);
if (ninePatchInsets == NULL) {
return nullObjectReturn("nine patch insets == null");
}
@@ -464,14 +427,6 @@
SkBitmap outputBitmap;
if (willScale) {
- // This is weird so let me explain: we could use the scale parameter
- // directly, but for historical reasons this is how the corresponding
- // Dalvik code has always behaved. We simply recreate the behavior here.
- // The result is slightly different from simply using scale because of
- // the 0.5f rounding bias applied when computing the target image size
- const float sx = scaledWidth / float(decodingBitmap.width());
- const float sy = scaledHeight / float(decodingBitmap.height());
-
// Set the allocator for the outputBitmap.
SkBitmap::Allocator* outputAllocator;
if (javaBitmap != nullptr) {
@@ -501,20 +456,14 @@
paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
- canvas.scale(sx, sy);
+ canvas.scale(scaleX, scaleY);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
outputBitmap.swap(decodingBitmap);
}
if (padding) {
- if (peeker.mPatch != NULL) {
- GraphicsJNI::set_jrect(env, padding,
- peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
- peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
- } else {
- GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
- }
+ peeker.getPadding(env, padding);
}
// If we get here, the outputBitmap should have an installed pixelref.
@@ -705,11 +654,6 @@
gBitmap_ninePatchInsetsFieldID = GetFieldIDOrDie(env, bitmap_class, "mNinePatchInsets",
"Landroid/graphics/NinePatch$InsetStruct;");
- gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
- "android/graphics/NinePatch$InsetStruct"));
- gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
- "(IIIIIIIIFIF)V");
-
gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
"android/graphics/Bitmap$Config"));
gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class,
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
new file mode 100644
index 0000000..115edd4
--- /dev/null
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
@@ -0,0 +1,323 @@
+#include "ByteBufferStreamAdaptor.h"
+#include "core_jni_helpers.h"
+
+#include <SkStream.h>
+
+using namespace android;
+
+static jmethodID gByteBuffer_getMethodID;
+static jmethodID gByteBuffer_setPositionMethodID;
+
+static JNIEnv* get_env_or_die(JavaVM* jvm) {
+ JNIEnv* env;
+ if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
+ }
+ return env;
+}
+
+class ByteBufferStream : public SkStreamAsset {
+private:
+ ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length,
+ jbyteArray storage)
+ : mJvm(jvm)
+ , mByteBuffer(jbyteBuffer)
+ , mPosition(0)
+ , mInitialPosition(initialPosition)
+ , mLength(length)
+ , mStorage(storage) {}
+
+public:
+ static ByteBufferStream* Create(JavaVM* jvm, JNIEnv* env, jobject jbyteBuffer,
+ size_t position, size_t length) {
+ // This object outlives its native method call.
+ jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
+ if (!jbyteBuffer) {
+ return nullptr;
+ }
+
+ jbyteArray storage = env->NewByteArray(kStorageSize);
+ if (!storage) {
+ env->DeleteGlobalRef(jbyteBuffer);
+ return nullptr;
+ }
+
+ // This object outlives its native method call.
+ storage = static_cast<jbyteArray>(env->NewGlobalRef(storage));
+ if (!storage) {
+ env->DeleteGlobalRef(jbyteBuffer);
+ return nullptr;
+ }
+
+ return new ByteBufferStream(jvm, jbyteBuffer, position, length, storage);
+ }
+
+ ~ByteBufferStream() override {
+ auto* env = get_env_or_die(mJvm);
+ env->DeleteGlobalRef(mByteBuffer);
+ env->DeleteGlobalRef(mStorage);
+ }
+
+ size_t read(void* buffer, size_t size) override {
+ if (size > mLength - mPosition) {
+ size = mLength - mPosition;
+ }
+ if (!size) {
+ return 0;
+ }
+
+ if (!buffer) {
+ return this->setPosition(mPosition + size);
+ }
+
+ auto* env = get_env_or_die(mJvm);
+ size_t bytesRead = 0;
+ do {
+ const size_t requested = (size > kStorageSize) ? kStorageSize : size;
+ const jint jrequested = static_cast<jint>(requested);
+ env->CallObjectMethod(mByteBuffer, gByteBuffer_getMethodID, mStorage, 0, jrequested);
+ if (env->ExceptionCheck()) {
+ ALOGE("Error in ByteBufferStream::read - was the ByteBuffer modified externally?");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ mPosition = mLength;
+ return bytesRead;
+ }
+
+ env->GetByteArrayRegion(mStorage, 0, requested, reinterpret_cast<jbyte*>(buffer));
+ if (env->ExceptionCheck()) {
+ ALOGE("Internal error in ByteBufferStream::read");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ mPosition = mLength;
+ return bytesRead;
+ }
+
+ mPosition += requested;
+ buffer = reinterpret_cast<void*>(reinterpret_cast<char*>(buffer) + requested);
+ bytesRead += requested;
+ size -= requested;
+ } while (size);
+ return bytesRead;
+ }
+
+ bool isAtEnd() const override { return mLength == mPosition; }
+
+ // SkStreamRewindable overrides
+ bool rewind() override { return this->setPosition(0); }
+
+ SkStreamAsset* onDuplicate() const override {
+ // SkStreamRewindable requires overriding this, but it is not called by
+ // decoders, so does not need a true implementation. A proper
+ // implementation would require duplicating the ByteBuffer, which has
+ // its own internal position state.
+ return nullptr;
+ }
+
+ // SkStreamSeekable overrides
+ size_t getPosition() const override { return mPosition; }
+
+ bool seek(size_t position) override {
+ return this->setPosition(position > mLength ? mLength : position);
+ }
+
+ bool move(long offset) override {
+ long newPosition = mPosition + offset;
+ if (newPosition < 0) {
+ return this->setPosition(0);
+ }
+ return this->seek(static_cast<size_t>(newPosition));
+ }
+
+ SkStreamAsset* onFork() const override {
+ // SkStreamSeekable requires overriding this, but it is not called by
+ // decoders, so does not need a true implementation. A proper
+ // implementation would require duplicating the ByteBuffer, which has
+ // its own internal position state.
+ return nullptr;
+ }
+
+ // SkStreamAsset overrides
+ size_t getLength() const override { return mLength; }
+
+private:
+ JavaVM* mJvm;
+ jobject mByteBuffer;
+ // Logical position of the SkStream, between 0 and mLength.
+ size_t mPosition;
+ // Initial position of mByteBuffer, treated as mPosition 0.
+ const size_t mInitialPosition;
+ // Logical length of the SkStream, from mInitialPosition to
+ // mByteBuffer.limit().
+ const size_t mLength;
+
+ // Range has already been checked by the caller.
+ bool setPosition(size_t newPosition) {
+ auto* env = get_env_or_die(mJvm);
+ env->CallObjectMethod(mByteBuffer, gByteBuffer_setPositionMethodID,
+ newPosition + mInitialPosition);
+ if (env->ExceptionCheck()) {
+ ALOGE("Internal error in ByteBufferStream::setPosition");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ mPosition = mLength;
+ return false;
+ }
+ mPosition = newPosition;
+ return true;
+ }
+
+ // FIXME: This is an arbitrary storage size, which should be plenty for
+ // some formats (png, gif, many bmps). But for jpeg, the more we can supply
+ // in one call the better, and webp really wants all of the data. How to
+ // best choose the amount of storage used?
+ static constexpr size_t kStorageSize = 4096;
+ jbyteArray mStorage;
+};
+
+class ByteArrayStream : public SkStreamAsset {
+private:
+ ByteArrayStream(JavaVM* jvm, jbyteArray jarray, size_t offset, size_t length)
+ : mJvm(jvm), mByteArray(jarray), mOffset(offset), mPosition(0), mLength(length) {}
+
+public:
+ static ByteArrayStream* Create(JavaVM* jvm, JNIEnv* env, jbyteArray jarray, size_t offset,
+ size_t length) {
+ // This object outlives its native method call.
+ jarray = static_cast<jbyteArray>(env->NewGlobalRef(jarray));
+ if (!jarray) {
+ return nullptr;
+ }
+ return new ByteArrayStream(jvm, jarray, offset, length);
+ }
+
+ ~ByteArrayStream() override {
+ auto* env = get_env_or_die(mJvm);
+ env->DeleteGlobalRef(mByteArray);
+ }
+
+ size_t read(void* buffer, size_t size) override {
+ if (size > mLength - mPosition) {
+ size = mLength - mPosition;
+ }
+ if (!size) {
+ return 0;
+ }
+
+ auto* env = get_env_or_die(mJvm);
+ if (buffer) {
+ env->GetByteArrayRegion(mByteArray, mPosition + mOffset, size,
+ reinterpret_cast<jbyte*>(buffer));
+ if (env->ExceptionCheck()) {
+ ALOGE("Internal error in ByteArrayStream::read");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ mPosition = mLength;
+ return 0;
+ }
+ }
+
+ mPosition += size;
+ return size;
+ }
+
+ bool isAtEnd() const override { return mLength == mPosition; }
+
+ // SkStreamRewindable overrides
+ bool rewind() override {
+ mPosition = 0;
+ return true;
+ }
+ SkStreamAsset* onDuplicate() const override {
+ // SkStreamRewindable requires overriding this, but it is not called by
+ // decoders, so does not need a true implementation. Note that a proper
+ // implementation is fairly straightforward
+ return nullptr;
+ }
+
+ // SkStreamSeekable overrides
+ size_t getPosition() const override { return mPosition; }
+
+ bool seek(size_t position) override {
+ mPosition = (position > mLength) ? mLength : position;
+ return true;
+ }
+
+ bool move(long offset) override {
+ long newPosition = mPosition + offset;
+ if (newPosition < 0) {
+ return this->seek(0);
+ }
+ return this->seek(static_cast<size_t>(newPosition));
+ }
+
+ SkStreamAsset* onFork() const override {
+ // SkStreamSeekable requires overriding this, but it is not called by
+ // decoders, so does not need a true implementation. Note that a proper
+ // implementation is fairly straightforward
+ return nullptr;
+ }
+
+ // SkStreamAsset overrides
+ size_t getLength() const override { return mLength; }
+
+private:
+ JavaVM* mJvm;
+ jbyteArray mByteArray;
+ // Offset in mByteArray. Only used when communicating with Java.
+ const size_t mOffset;
+ // Logical position of the SkStream, between 0 and mLength.
+ size_t mPosition;
+ const size_t mLength;
+};
+
+struct release_proc_context {
+ JavaVM* jvm;
+ jobject jbyteBuffer;
+};
+
+std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv* env, jobject jbyteBuffer,
+ size_t position, size_t limit) {
+ JavaVM* jvm;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
+
+ const size_t length = limit - position;
+ void* addr = env->GetDirectBufferAddress(jbyteBuffer);
+ if (addr) {
+ addr = reinterpret_cast<void*>(reinterpret_cast<char*>(addr) + position);
+ jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
+ if (!jbyteBuffer) {
+ return nullptr;
+ }
+
+ auto* context = new release_proc_context{jvm, jbyteBuffer};
+ auto releaseProc = [](const void*, void* context) {
+ auto* c = reinterpret_cast<release_proc_context*>(context);
+ JNIEnv* env = get_env_or_die(c->jvm);
+ env->DeleteGlobalRef(c->jbyteBuffer);
+ delete c;
+ };
+ auto data = SkData::MakeWithProc(addr, length, releaseProc, context);
+ // The new SkMemoryStream will read directly from addr.
+ return std::unique_ptr<SkStream>(new SkMemoryStream(std::move(data)));
+ }
+
+ // Non-direct, or direct access is not supported.
+ return std::unique_ptr<SkStream>(ByteBufferStream::Create(jvm, env, jbyteBuffer, position,
+ length));
+}
+
+std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv* env, jbyteArray array, size_t offset,
+ size_t length) {
+ JavaVM* jvm;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
+
+ return std::unique_ptr<SkStream>(ByteArrayStream::Create(jvm, env, array, offset, length));
+}
+
+int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env) {
+ jclass byteBuffer_class = FindClassOrDie(env, "java/nio/ByteBuffer");
+ gByteBuffer_getMethodID = GetMethodIDOrDie(env, byteBuffer_class, "get", "([BII)Ljava/nio/ByteBuffer;");
+ gByteBuffer_setPositionMethodID = GetMethodIDOrDie(env, byteBuffer_class, "position", "(I)Ljava/nio/Buffer;");
+ return true;
+}
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.h b/core/jni/android/graphics/ByteBufferStreamAdaptor.h
new file mode 100644
index 0000000..367a48f
--- /dev/null
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.h
@@ -0,0 +1,37 @@
+#ifndef _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
+#define _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
+
+#include <jni.h>
+#include <memory>
+
+class SkStream;
+
+/**
+ * Create an adaptor for treating a java.nio.ByteBuffer as an SkStream.
+ *
+ * This will special case direct ByteBuffers, but not the case where a byte[]
+ * can be used directly. For that, use CreateByteArrayStreamAdaptor.
+ *
+ * @param jbyteBuffer corresponding to the java ByteBuffer. This method will
+ * add a global ref.
+ * @param initialPosition returned by ByteBuffer.position(). Decoding starts
+ * from here.
+ * @param limit returned by ByteBuffer.limit().
+ *
+ * Returns null on failure.
+ */
+std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv*, jobject jbyteBuffer,
+ size_t initialPosition, size_t limit);
+
+/**
+ * Create an adaptor for treating a Java byte[] as an SkStream.
+ *
+ * @param offset into the byte[] of the beginning of the data to use.
+ * @param length of data to use, starting from offset.
+ *
+ * Returns null on failure.
+ */
+std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv*, jbyteArray array, size_t offset,
+ size_t length);
+
+#endif // _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
new file mode 100644
index 0000000..bacab2a
--- /dev/null
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Bitmap.h"
+#include "ByteBufferStreamAdaptor.h"
+#include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
+#include "Utils.h"
+#include "core_jni_helpers.h"
+
+#include <hwui/Bitmap.h>
+#include <hwui/Canvas.h>
+
+#include <SkAndroidCodec.h>
+#include <SkEncodedImageFormat.h>
+#include <SkStream.h>
+
+#include <androidfw/Asset.h>
+#include <jni.h>
+
+using namespace android;
+
+static jclass gImageDecoder_class;
+static jclass gPoint_class;
+static jclass gIncomplete_class;
+static jclass gCorrupt_class;
+static jclass gCanvas_class;
+static jmethodID gImageDecoder_constructorMethodID;
+static jmethodID gPoint_constructorMethodID;
+static jmethodID gIncomplete_constructorMethodID;
+static jmethodID gCorrupt_constructorMethodID;
+static jmethodID gCallback_onExceptionMethodID;
+static jmethodID gPostProcess_postProcessMethodID;
+static jmethodID gCanvas_constructorMethodID;
+static jmethodID gCanvas_releaseMethodID;
+
+struct ImageDecoder {
+ // These need to stay in sync with ImageDecoder.java's Allocator constants.
+ enum Allocator {
+ kDefault_Allocator = 0,
+ kSoftware_Allocator = 1,
+ kSharedMemory_Allocator = 2,
+ kHardware_Allocator = 3,
+ };
+
+ // These need to stay in sync with PixelFormat.java's Format constants.
+ enum PixelFormat {
+ kUnknown = 0,
+ kTranslucent = -3,
+ kOpaque = -1,
+ };
+
+ std::unique_ptr<SkAndroidCodec> mCodec;
+ NinePatchPeeker mPeeker;
+};
+
+static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
+ if (!stream.get()) {
+ return nullObjectReturn("Failed to create a stream");
+ }
+ std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
+ decoder->mCodec = SkAndroidCodec::MakeFromStream(std::move(stream), &decoder->mPeeker);
+ if (!decoder->mCodec.get()) {
+ // FIXME: Add an error code to SkAndroidCodec::MakeFromStream, like
+ // SkCodec? Then this can print a more informative error message.
+ // (Or we can print one from within SkCodec.)
+ ALOGE("Failed to create an SkCodec");
+ return nullptr;
+ }
+
+ const auto& info = decoder->mCodec->getInfo();
+ const int width = info.width();
+ const int height = info.height();
+ return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
+ reinterpret_cast<jlong>(decoder.release()), width, height);
+}
+
+static jobject ImageDecoder_nCreate(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) {
+ Asset* asset = reinterpret_cast<Asset*>(assetPtr);
+ std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset));
+ return native_create(env, std::move(stream));
+}
+
+static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jobject jbyteBuffer,
+ jint initialPosition, jint limit) {
+ std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
+ initialPosition, limit);
+ if (!stream) {
+ return nullptr;
+ }
+ return native_create(env, std::move(stream));
+}
+
+static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jbyteArray byteArray,
+ jint offset, jint length) {
+ std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length));
+ return native_create(env, std::move(stream));
+}
+
+static bool supports_any_down_scale(const SkAndroidCodec* codec) {
+ return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP;
+}
+
+static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jobject jcallback, jobject jpostProcess,
+ jint desiredWidth, jint desiredHeight, jobject jsubset,
+ jboolean requireMutable, jint allocator,
+ jboolean requireUnpremul, jboolean preferRamOverQuality,
+ jboolean asAlphaMask) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ SkAndroidCodec* codec = decoder->mCodec.get();
+ SkImageInfo decodeInfo = codec->getInfo();
+ bool scale = false;
+ int sampleSize = 1;
+ if (desiredWidth != decodeInfo.width() || desiredHeight != decodeInfo.height()) {
+ bool match = false;
+ if (desiredWidth < decodeInfo.width() && desiredHeight < decodeInfo.height()) {
+ if (supports_any_down_scale(codec)) {
+ match = true;
+ decodeInfo = decodeInfo.makeWH(desiredWidth, desiredHeight);
+ } else {
+ int sampleX = decodeInfo.width() / desiredWidth;
+ int sampleY = decodeInfo.height() / desiredHeight;
+ sampleSize = std::min(sampleX, sampleY);
+ SkISize sampledSize = codec->getSampledDimensions(sampleSize);
+ decodeInfo = decodeInfo.makeWH(sampledSize.width(), sampledSize.height());
+ if (decodeInfo.width() == desiredWidth && decodeInfo.height() == desiredHeight) {
+ match = true;
+ }
+ }
+ }
+ if (!match) {
+ scale = true;
+ if (requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) {
+ doThrowISE(env, "Cannot scale unpremultiplied pixels!");
+ return nullptr;
+ }
+ }
+ }
+
+ switch (decodeInfo.alphaType()) {
+ case kUnpremul_SkAlphaType:
+ if (!requireUnpremul) {
+ decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
+ }
+ break;
+ case kPremul_SkAlphaType:
+ if (requireUnpremul) {
+ decodeInfo = decodeInfo.makeAlphaType(kUnpremul_SkAlphaType);
+ }
+ break;
+ case kOpaque_SkAlphaType:
+ break;
+ case kUnknown_SkAlphaType:
+ return nullObjectReturn("Unknown alpha type");
+ }
+
+ SkColorType colorType = kN32_SkColorType;
+ if (asAlphaMask && decodeInfo.colorType() == kGray_8_SkColorType) {
+ // We have to trick Skia to decode this to a single channel.
+ colorType = kGray_8_SkColorType;
+ } else if (preferRamOverQuality) {
+ // FIXME: The post-process might add alpha, which would make a 565
+ // result incorrect. If we call the postProcess before now and record
+ // to a picture, we can know whether alpha was added, and if not, we
+ // can still use 565.
+ if (decodeInfo.alphaType() == kOpaque_SkAlphaType && !jpostProcess) {
+ // If the final result will be hardware, decoding to 565 and then
+ // uploading to the gpu as 8888 will not save memory. This still
+ // may save us from using F16, but do not go down to 565.
+ if (allocator != ImageDecoder::kHardware_Allocator &&
+ (allocator != ImageDecoder::kDefault_Allocator || requireMutable)) {
+ colorType = kRGB_565_SkColorType;
+ }
+ }
+ // Otherwise, stick with N32
+ } else {
+ // This is currently the only way to know that we should decode to F16.
+ colorType = codec->computeOutputColorType(colorType);
+ }
+ sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType);
+ decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
+
+ SkBitmap bm;
+ auto bitmapInfo = decodeInfo;
+ if (asAlphaMask && colorType == kGray_8_SkColorType) {
+ bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+ }
+ if (!bm.setInfo(bitmapInfo)) {
+ return nullObjectReturn("Failed to setInfo properly");
+ }
+
+ sk_sp<Bitmap> nativeBitmap;
+ // If we are going to scale or subset, we will create a new bitmap later on,
+ // so use the heap for the temporary.
+ // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
+ if (allocator == ImageDecoder::kSharedMemory_Allocator && !scale && !jsubset) {
+ nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+ } else {
+ nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ }
+ if (!nativeBitmap) {
+ ALOGE("OOM allocating Bitmap with dimensions %i x %i",
+ decodeInfo.width(), decodeInfo.height());
+ doThrowOOME(env);
+ return nullptr;
+ }
+
+ jobject jexception = nullptr;
+ SkAndroidCodec::AndroidOptions options;
+ options.fSampleSize = sampleSize;
+ auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options);
+ switch (result) {
+ case SkCodec::kSuccess:
+ break;
+ case SkCodec::kIncompleteInput:
+ if (jcallback) {
+ jexception = env->NewObject(gIncomplete_class, gIncomplete_constructorMethodID);
+ }
+ break;
+ case SkCodec::kErrorInInput:
+ if (jcallback) {
+ jexception = env->NewObject(gCorrupt_class, gCorrupt_constructorMethodID);
+ }
+ break;
+ default:
+ ALOGE("getPixels failed with error %i", result);
+ return nullptr;
+ }
+
+ if (jexception) {
+ if (!env->CallBooleanMethod(jcallback, gCallback_onExceptionMethodID, jexception) ||
+ env->ExceptionCheck()) {
+ return nullptr;
+ }
+ }
+
+ float scaleX = 1.0f;
+ float scaleY = 1.0f;
+ if (scale) {
+ scaleX = (float) desiredWidth / decodeInfo.width();
+ scaleY = (float) desiredHeight / decodeInfo.height();
+ }
+
+ jbyteArray ninePatchChunk = nullptr;
+ jobject ninePatchInsets = nullptr;
+
+ // Ignore ninepatch when post-processing.
+ if (!jpostProcess) {
+ // FIXME: Share more code with BitmapFactory.cpp.
+ if (decoder->mPeeker.mPatch != nullptr) {
+ if (scale) {
+ decoder->mPeeker.scale(scaleX, scaleY, desiredWidth, desiredHeight);
+ }
+ size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize();
+ ninePatchChunk = env->NewByteArray(ninePatchArraySize);
+ if (ninePatchChunk == nullptr) {
+ return nullObjectReturn("ninePatchChunk == null");
+ }
+
+ env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
+ reinterpret_cast<jbyte*>(decoder->mPeeker.mPatch));
+ }
+
+ if (decoder->mPeeker.mHasInsets) {
+ ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
+ if (ninePatchInsets == nullptr) {
+ return nullObjectReturn("nine patch insets == null");
+ }
+ }
+ }
+
+ if (scale || jsubset) {
+ int translateX = 0;
+ int translateY = 0;
+ if (jsubset) {
+ SkIRect subset;
+ GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
+
+ // FIXME: If there is no scale, should this instead call
+ // SkBitmap::extractSubset? If we could upload a subset
+ // (b/70626068), this would save memory and time. Even for a
+ // software Bitmap, the extra speed might be worth the memory
+ // tradeoff if the subset is large?
+ translateX = -subset.fLeft;
+ translateY = -subset.fTop;
+ desiredWidth = subset.width();
+ desiredHeight = subset.height();
+ }
+ SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
+ SkBitmap scaledBm;
+ if (!scaledBm.setInfo(scaledInfo)) {
+ nullObjectReturn("Failed scaled setInfo");
+ }
+
+ sk_sp<Bitmap> scaledPixelRef;
+ if (allocator == ImageDecoder::kSharedMemory_Allocator) {
+ scaledPixelRef = Bitmap::allocateAshmemBitmap(&scaledBm);
+ } else {
+ scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm);
+ }
+ if (!scaledPixelRef) {
+ ALOGE("OOM allocating scaled Bitmap with dimensions %i x %i",
+ desiredWidth, desiredHeight);
+ doThrowOOME(env);
+ return nullptr;
+ }
+
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc);
+ paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
+
+ SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
+ canvas.translate(translateX, translateY);
+ canvas.scale(scaleX, scaleY);
+ canvas.drawBitmap(bm, 0.0f, 0.0f, &paint);
+
+ bm.swap(scaledBm);
+ nativeBitmap = scaledPixelRef;
+ }
+
+ if (jpostProcess) {
+ std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
+ if (!canvas) {
+ return nullObjectReturn("Failed to create Canvas for PostProcess!");
+ }
+ jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
+ reinterpret_cast<jlong>(canvas.get()));
+ if (!jcanvas) {
+ return nullObjectReturn("Failed to create Java Canvas for PostProcess!");
+ }
+ // jcanvas will now own canvas.
+ canvas.release();
+
+ jint pixelFormat = env->CallIntMethod(jpostProcess, gPostProcess_postProcessMethodID,
+ jcanvas, bm.width(), bm.height());
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ // The Canvas objects are no longer needed, and will not remain valid.
+ env->CallVoidMethod(jcanvas, gCanvas_releaseMethodID);
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ SkAlphaType newAlphaType = bm.alphaType();
+ switch (pixelFormat) {
+ case ImageDecoder::kUnknown:
+ break;
+ case ImageDecoder::kTranslucent:
+ newAlphaType = kPremul_SkAlphaType;
+ break;
+ case ImageDecoder::kOpaque:
+ newAlphaType = kOpaque_SkAlphaType;
+ break;
+ default:
+ ALOGE("invalid return from postProcess: %i", pixelFormat);
+ doThrowIAE(env);
+ return nullptr;
+ }
+
+ if (newAlphaType != bm.alphaType()) {
+ if (!bm.setAlphaType(newAlphaType)) {
+ ALOGE("incompatible return from postProcess: %i", pixelFormat);
+ doThrowIAE(env);
+ return nullptr;
+ }
+ nativeBitmap->setAlphaType(newAlphaType);
+ }
+ }
+
+ int bitmapCreateFlags = 0x0;
+ if (!requireUnpremul) {
+ // Even if the image is opaque, setting this flag means that
+ // if alpha is added (e.g. by PostProcess), it will be marked as
+ // premultiplied.
+ bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Premultiplied;
+ }
+
+ if (requireMutable) {
+ bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Mutable;
+ } else {
+ if ((allocator == ImageDecoder::kDefault_Allocator ||
+ allocator == ImageDecoder::kHardware_Allocator)
+ && bm.colorType() != kAlpha_8_SkColorType)
+ {
+ sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm);
+ if (hwBitmap) {
+ hwBitmap->setImmutable();
+ return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
+ ninePatchChunk, ninePatchInsets);
+ }
+ if (allocator == ImageDecoder::kHardware_Allocator) {
+ return nullObjectReturn("failed to allocate hardware Bitmap!");
+ }
+ // If we failed to create a hardware bitmap, go ahead and create a
+ // software one.
+ }
+
+ nativeBitmap->setImmutable();
+ }
+ return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
+ ninePatchInsets);
+}
+
+static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jint sampleSize) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ SkISize size = decoder->mCodec->getSampledDimensions(sampleSize);
+ return env->NewObject(gPoint_class, gPoint_constructorMethodID, size.width(), size.height());
+}
+
+static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jobject outPadding) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ decoder->mPeeker.getPadding(env, outPadding);
+}
+
+static void ImageDecoder_nRecycle(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
+ delete reinterpret_cast<ImageDecoder*>(nativePtr);
+}
+
+static const JNINativeMethod gImageDecoderMethods[] = {
+ { "nCreate", "(J)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreate },
+ { "nCreate", "(Ljava/nio/ByteBuffer;II)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer },
+ { "nCreate", "([BII)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
+ { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder$OnExceptionListener;Landroid/graphics/PostProcess;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
+ (void*) ImageDecoder_nDecodeBitmap },
+ { "nGetSampledSize","(JI)Landroid/graphics/Point;", (void*) ImageDecoder_nGetSampledSize },
+ { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding },
+ { "nRecycle", "(J)V", (void*) ImageDecoder_nRecycle},
+};
+
+int register_android_graphics_ImageDecoder(JNIEnv* env) {
+ gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
+ gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JII)V");
+
+ gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
+ gPoint_constructorMethodID = GetMethodIDOrDie(env, gPoint_class, "<init>", "(II)V");
+
+ gIncomplete_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$IncompleteException"));
+ gIncomplete_constructorMethodID = GetMethodIDOrDie(env, gIncomplete_class, "<init>", "()V");
+
+ gCorrupt_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$CorruptException"));
+ gCorrupt_constructorMethodID = GetMethodIDOrDie(env, gCorrupt_class, "<init>", "()V");
+
+ jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnExceptionListener");
+ gCallback_onExceptionMethodID = GetMethodIDOrDie(env, callback_class, "onException", "(Ljava/lang/Exception;)Z");
+
+ jclass postProcess_class = FindClassOrDie(env, "android/graphics/PostProcess");
+ gPostProcess_postProcessMethodID = GetMethodIDOrDie(env, postProcess_class, "postProcess", "(Landroid/graphics/Canvas;II)I");
+
+ gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
+ gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V");
+ gCanvas_releaseMethodID = GetMethodIDOrDie(env, gCanvas_class, "release", "()V");
+
+ return android::RegisterMethodsOrDie(env, "android/graphics/ImageDecoder", gImageDecoderMethods,
+ NELEM(gImageDecoderMethods));
+}
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 564afeb..2619107 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -29,11 +29,15 @@
#include "SkLatticeIter.h"
#include "SkRegion.h"
#include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
#include "NinePatchUtils.h"
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
+jclass gInsetStruct_class;
+jmethodID gInsetStruct_constructorMethodID;
+
using namespace android;
/**
@@ -128,6 +132,30 @@
};
+jobject NinePatchPeeker::createNinePatchInsets(JNIEnv* env, float scale) const {
+ if (!mHasInsets) {
+ return nullptr;
+ }
+
+ return env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
+ mOpticalInsets[0], mOpticalInsets[1],
+ mOpticalInsets[2], mOpticalInsets[3],
+ mOutlineInsets[0], mOutlineInsets[1],
+ mOutlineInsets[2], mOutlineInsets[3],
+ mOutlineRadius, mOutlineAlpha, scale);
+}
+
+void NinePatchPeeker::getPadding(JNIEnv* env, jobject outPadding) const {
+ if (mPatch) {
+ GraphicsJNI::set_jrect(env, outPadding,
+ mPatch->paddingLeft, mPatch->paddingTop,
+ mPatch->paddingRight, mPatch->paddingBottom);
+
+ } else {
+ GraphicsJNI::set_jrect(env, outPadding, -1, -1, -1, -1);
+ }
+}
+
/////////////////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gNinePatchMethods[] = {
@@ -140,6 +168,10 @@
};
int register_android_graphics_NinePatch(JNIEnv* env) {
+ gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
+ "android/graphics/NinePatch$InsetStruct"));
+ gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
+ "(IIIIIIIIFIF)V");
return android::RegisterMethodsOrDie(env,
"android/graphics/NinePatch", gNinePatchMethods, NELEM(gNinePatchMethods));
}
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 1ea5650..9171fc6 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -16,7 +16,8 @@
#include "NinePatchPeeker.h"
-#include "SkBitmap.h"
+#include <SkBitmap.h>
+#include <cutils/compiler.h>
using namespace android;
@@ -46,3 +47,47 @@
}
return true; // keep on decoding
}
+
+static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
+ for (int i = 0; i < count; i++) {
+ divs[i] = int32_t(divs[i] * scale + 0.5f);
+ if (i > 0 && divs[i] == divs[i - 1]) {
+ divs[i]++; // avoid collisions
+ }
+ }
+
+ if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
+ // if the collision avoidance above put some divs outside the bounds of the bitmap,
+ // slide outer stretchable divs inward to stay within bounds
+ int highestAvailable = maxValue;
+ for (int i = count - 1; i >= 0; i--) {
+ divs[i] = highestAvailable;
+ if (i > 0 && divs[i] <= divs[i-1]) {
+ // keep shifting
+ highestAvailable = divs[i] - 1;
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+void NinePatchPeeker::scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight) {
+ if (!mPatch) {
+ return;
+ }
+
+ // The max value for the divRange is one pixel less than the actual max to ensure that the size
+ // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
+ if (!SkScalarNearlyEqual(scaleX, 1.0f)) {
+ mPatch->paddingLeft = int(mPatch->paddingLeft * scaleX + 0.5f);
+ mPatch->paddingRight = int(mPatch->paddingRight * scaleX + 0.5f);
+ scaleDivRange(mPatch->getXDivs(), mPatch->numXDivs, scaleX, scaledWidth - 1);
+ }
+
+ if (!SkScalarNearlyEqual(scaleY, 1.0f)) {
+ mPatch->paddingTop = int(mPatch->paddingTop * scaleY + 0.5f);
+ mPatch->paddingBottom = int(mPatch->paddingBottom * scaleY + 0.5f);
+ scaleDivRange(mPatch->getYDivs(), mPatch->numYDivs, scaleY, scaledHeight - 1);
+ }
+}
diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h
index 126eab2..e4e58dd 100644
--- a/core/jni/android/graphics/NinePatchPeeker.h
+++ b/core/jni/android/graphics/NinePatchPeeker.h
@@ -20,7 +20,7 @@
#include "SkPngChunkReader.h"
#include <androidfw/ResourceTypes.h>
-class SkImageDecoder;
+#include <jni.h>
using namespace android;
@@ -42,9 +42,14 @@
bool readChunk(const char tag[], const void* data, size_t length) override;
+ jobject createNinePatchInsets(JNIEnv*, float scale) const;
+ void getPadding(JNIEnv*, jobject outPadding) const;
+ void scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight);
+
Res_png_9patch* mPatch;
size_t mPatchSize;
bool mHasInsets;
+private:
int32_t mOpticalInsets[4];
int32_t mOutlineInsets[4];
float mOutlineRadius;
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 1522c20..1676d4b 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -25,7 +25,8 @@
#include <assert.h>
#include <dlfcn.h>
-#include <GLES/gl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
#include <ETC1/etc1.h>
#include <SkBitmap.h>
@@ -639,6 +640,10 @@
return 0;
}
break;
+ case kRGBA_F16_SkColorType:
+ if (type == GL_HALF_FLOAT_OES && format == PIXEL_FORMAT_RGBA_FP16)
+ return 0;
+ break;
default:
break;
}
@@ -656,6 +661,8 @@
return GL_RGBA;
case kRGB_565_SkColorType:
return GL_RGB;
+ case kRGBA_F16_SkColorType:
+ return PIXEL_FORMAT_RGBA_FP16;
default:
return -1;
}
@@ -672,6 +679,8 @@
return GL_UNSIGNED_BYTE;
case kRGB_565_SkColorType:
return GL_UNSIGNED_SHORT_5_6_5;
+ case kRGBA_F16_SkColorType:
+ return GL_HALF_FLOAT_OES;
default:
return -1;
}
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index a95fa57..522ff24 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -18,8 +18,6 @@
option java_multiple_files = true;
option java_outer_classname = "CpuInfoProto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
package android.os;
/**
@@ -31,8 +29,6 @@
message CpuInfo {
message TaskStats {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
optional int32 total = 1; // total number of cpu tasks
optional int32 running = 2; // number of running tasks
optional int32 sleeping = 3; // number of sleeping tasks
@@ -42,8 +38,6 @@
optional TaskStats task_stats = 1;
message MemStats { // unit in kB
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
optional int32 total = 1;
optional int32 used = 2;
optional int32 free = 3;
@@ -54,8 +48,6 @@
optional MemStats swap = 3;
message CpuUsage { // unit is percentage %
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
optional int32 cpu = 1; // 400% cpu indicates 4 cores
optional int32 user = 2;
optional int32 nice = 3;
@@ -70,8 +62,6 @@
// Next Tag: 13
message Task {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
optional int32 pid = 1;
optional int32 tid = 2;
optional string user = 3;
@@ -80,8 +70,6 @@
optional float cpu = 6; // precentage of cpu usage of the task
enum Status {
- option (stream_proto.stream_enum).enable_enums_mapping = true;
-
STATUS_UNKNOWN = 0;
STATUS_D = 1; // uninterruptible sleep
STATUS_R = 2; // running
@@ -95,8 +83,6 @@
// How Android memory manager will treat the task
enum Policy {
- option (stream_proto.stream_enum).enable_enums_mapping = true;
-
POLICY_UNKNOWN = 0;
POLICY_fg = 1; // foreground, the name is lower case for parsing the value
POLICY_bg = 2; // background, the name is lower case for parsing the value
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index eaad37a..7e5be9d 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -18,8 +18,6 @@
option java_multiple_files = true;
option java_outer_classname = "WakeupSourcesProto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
package android.os;
message KernelWakeSources {
@@ -29,8 +27,6 @@
// Next Tag: 11
message WakeupSourceProto {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
-
// Name of the event which triggers application processor
optional string name = 1;
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index b86ee01..f82ea76 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -18,8 +18,6 @@
option java_multiple_files = true;
option java_outer_classname = "PageTypeInfoProto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
package android.os;
/*
@@ -63,7 +61,6 @@
// Next tag: 9
message BlockProto {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
optional int32 node = 1;
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index f684c84..204a5af 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -19,7 +19,6 @@
option java_outer_classname = "ProcrankProto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
package android.os;
@@ -36,7 +35,6 @@
// Next Tag: 11
message ProcessProto {
- option (stream_proto.stream_msg).enable_fields_mapping = true;
option (android.msg_privacy).dest = DEST_AUTOMATIC;
// ID of the process
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 76a108b..a51253f 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -19,14 +19,12 @@
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
package android.os;
// Android Platform Exported System Properties
// TODO: This is not the completed list, new properties need to be whitelisted.
message SystemPropertiesProto {
- option (stream_proto.stream_msg).enable_fields_mapping_recursively = true;
// Properties that are not specified below would be appended here.
// These values stay on device only.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 15e439e..e303927 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1620,6 +1620,11 @@
<permission android:name="android.permission.ACCESS_PDB_STATE"
android:protectionLevel="signature" />
+ <!-- Allows testing if a passwords is forbidden by the admins.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows system update service to notify device owner about pending updates.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
@@ -3627,6 +3632,11 @@
<permission android:name="android.permission.READ_RUNTIME_PROFILES"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to turn on / off quiet mode.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MODIFY_QUIET_MODE"
+ android:protectionLevel="signature|privileged" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/drawable/ic_wifi_settings.xml b/core/res/res/drawable/ic_wifi_settings.xml
new file mode 100644
index 0000000..c678ad4
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_settings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M12.584,15.93c0.026-0.194,0.044-0.397,0.044-0.608c0-0.211-0.018-0.405-0.044-0.608l1.304-1.022
+c0.115-0.088,0.15-0.256,0.071-0.397l-1.234-2.133c-0.071-0.132-0.238-0.185-0.379-0.132l-1.533,0.617
+c-0.317-0.247-0.67-0.449-1.04-0.608L9.535,9.4c-0.018-0.132-0.141-0.247-0.3-0.247H6.768c-0.15,0-0.282,0.115-0.3,0.256
+L6.23,11.048c-0.379,0.159-0.723,0.361-1.04,0.608l-1.533-0.617c-0.141-0.053-0.3,0-0.379,0.132l-1.234,2.133
+c-0.079,0.132-0.044,0.3,0.07,0.397l1.304,1.022c-0.026,0.194-0.044,0.405-0.044,0.608s0.018,0.405,0.044,0.608l-1.304,1.022
+c-0.115,0.088-0.15,0.256-0.07,0.397l1.234,2.133c0.07,0.132,0.238,0.185,0.379,0.132l1.533-0.617
+c0.317,0.247,0.67,0.449,1.04,0.608l0.238,1.639c0.018,0.15,0.15,0.256,0.3,0.256h2.467c0.159,0,0.282-0.115,0.3-0.256
+l0.238-1.639c0.379-0.15,0.723-0.361,1.04-0.608l1.533,0.617c0.141,0.053,0.3,0,0.379-0.132l1.234-2.133
+c0.071-0.132,0.044-0.3-0.07-0.397L12.584,15.93z
+M8.002,17.481c-1.19,0-2.159-0.969-2.159-2.159s0.969-2.159,2.159-2.159
+s2.159,0.969,2.159,2.159C10.161,16.512,9.191,17.481,8.002,17.481z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M16.003,12.026l5.995-7.474c-0.229-0.172-2.537-2.06-6-2.06s-5.771,1.889-6,2.06l5.995,7.469l0.005,0.01L16.003,12.026z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 865685f..435289d 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -72,7 +72,7 @@
android:textColor="#eeffffff"
android:layout_marginTop="4dp"
android:ellipsize="end"
- android:maxLines="7"
+ android:maxLines="3"
/>
</LinearLayout>
</LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5ccaf5c..5783435 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3014,6 +3014,13 @@
<!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. -->
<string name="wifi_available_action_all_networks">All Networks</string>
+ <!--Notification title for Wi-Fi Wake onboarding. This is displayed the first time a user disables Wi-Fi with the feature enabled. -->
+ <string name="wifi_wakeup_onboarding_title">Wi\u2011Fi will turn on automatically</string>
+ <!--Notification subtext for Wi-Fi Wake onboarding.-->
+ <string name="wifi_wakeup_onboarding_subtext">When you\'re near a high quality saved network</string>
+ <!--Notification action to disable Wi-Fi Wake during onboarding.-->
+ <string name="wifi_wakeup_onboarding_action_disable">Don\'t turn back on</string>
+
<!-- A notification is shown when a wifi captive portal network is detected. This is the notification's title. -->
<string name="wifi_available_sign_in">Sign in to Wi-Fi network</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4b2424f..7f71446 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1308,6 +1308,7 @@
<java-symbol type="drawable" name="platlogo" />
<java-symbol type="drawable" name="stat_notify_sync_error" />
<java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+ <java-symbol type="drawable" name="ic_wifi_settings" />
<java-symbol type="drawable" name="ic_wifi_signal_0" />
<java-symbol type="drawable" name="ic_wifi_signal_1" />
<java-symbol type="drawable" name="ic_wifi_signal_2" />
@@ -1905,6 +1906,9 @@
<java-symbol type="string" name="wifi_available_content_failed_to_connect" />
<java-symbol type="string" name="wifi_available_action_connect" />
<java-symbol type="string" name="wifi_available_action_all_networks" />
+ <java-symbol type="string" name="wifi_wakeup_onboarding_title" />
+ <java-symbol type="string" name="wifi_wakeup_onboarding_subtext" />
+ <java-symbol type="string" name="wifi_wakeup_onboarding_action_disable" />
<java-symbol type="string" name="accessibility_binding_label" />
<java-symbol type="string" name="adb_active_notification_message" />
<java-symbol type="string" name="adb_active_notification_title" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3f2a46a..e094772 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1051,7 +1051,7 @@
</intent-filter>
</activity>
- <activity android:name=".menus.MenuLayoutPortrait" android:label="MenuLayoutPortrait"
+ <activity android:name="android.view.menu.MenuLayoutPortrait" android:label="MenuLayoutPortrait"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
index 3c81853..e26bdf5 100644
--- a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
@@ -15,6 +15,7 @@
*/
package android.animation;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import java.util.HashSet;
@@ -24,6 +25,7 @@
import com.android.frameworks.coretests.R;
+@LargeTest
public class AnimatorInflaterTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
Set<Integer> identityHashes = new HashSet<Integer>();
diff --git a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
index 38df78d..a9961e1 100644
--- a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
+++ b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
@@ -17,6 +17,7 @@
package android.animation;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.util.StateSet;
@@ -27,7 +28,7 @@
import java.util.concurrent.atomic.AtomicInteger;
-
+@LargeTest
public class StateListAnimatorTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
public StateListAnimatorTest() {
diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
index f60bf94..063bef7 100644
--- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
+++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageInfo;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
+import android.support.test.filters.LargeTest;
import junit.framework.TestCase;
@@ -34,6 +35,7 @@
import static android.os.storage.VolumeInfo.STATE_MOUNTED;
import static android.os.storage.VolumeInfo.STATE_UNMOUNTED;
+@LargeTest
public class ApplicationPackageManagerTest extends TestCase {
private static final String sInternalVolPath = "/data";
private static final String sAdoptedVolPath = "/mnt/expand/123";
diff --git a/core/tests/coretests/src/android/app/InstrumentationTest.java b/core/tests/coretests/src/android/app/InstrumentationTest.java
index ee3834c..9b59da4 100644
--- a/core/tests/coretests/src/android/app/InstrumentationTest.java
+++ b/core/tests/coretests/src/android/app/InstrumentationTest.java
@@ -17,8 +17,10 @@
package android.app;
import android.os.Bundle;
+import android.support.test.filters.LargeTest;
import android.test.InstrumentationTestCase;
+@LargeTest
public class InstrumentationTest extends InstrumentationTestCase {
/**
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index c14dc90..7183934 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -214,6 +214,20 @@
assertTrue(n.allPendingIntents.contains(intent));
}
+ @Test
+ public void testMessagingStyle_isGroupConversation() {
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+ .setGroupConversation(true);
+ Notification notification = new Notification.Builder(mContext, "test id")
+ .setSmallIcon(1)
+ .setContentTitle("test title")
+ .setStyle(messagingStyle)
+ .build();
+
+ assertTrue(messagingStyle.isGroupConversation());
+ assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+ }
+
private Notification.Builder getMediaNotification() {
MediaSession session = new MediaSession(mContext, "test");
return new Notification.Builder(mContext, "color")
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index e9e8bfc..13e70eb 100644
--- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -27,12 +27,13 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
import android.test.FlakyTest;
-import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
import java.util.Arrays;
+@LargeTest
public class BroadcastTest extends ActivityTestsBase {
public static final int BROADCAST_TIMEOUT = 5 * 1000;
diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
index 3c30915..8c1d79b 100644
--- a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
+++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
@@ -20,10 +20,10 @@
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
-import android.test.suitebuilder.annotation.Suppress;
import android.os.Bundle;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.filters.LargeTest;
+@LargeTest
public class IntentSenderTest extends BroadcastTest {
public void testRegisteredReceivePermissionGranted() throws Exception {
diff --git a/core/tests/coretests/src/android/app/backup/BackupDataTest.java b/core/tests/coretests/src/android/app/backup/BackupDataTest.java
index 0c204e0..5b8e481 100644
--- a/core/tests/coretests/src/android/app/backup/BackupDataTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupDataTest.java
@@ -23,6 +23,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.test.InstrumentationTestCase;
import android.util.Base64;
@@ -41,6 +42,7 @@
import java.lang.Exception;
import java.nio.ByteBuffer;
+@LargeTest
public class BackupDataTest extends AndroidTestCase {
private static final String KEY1 = "key1";
private static final String KEY2 = "key2a";
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
index 3869cd2..bc6fc15 100644
--- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -36,6 +37,7 @@
import java.util.Map;
import java.util.Set;
+@LargeTest
public class FullBackupTest extends AndroidTestCase {
private XmlPullParserFactory mFactory;
private XmlPullParser mXpp;
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
index 70a0877..e20645c 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
@@ -21,12 +21,14 @@
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import org.junit.Test;
/**
* Tests for {@link DistroFormatVersion}.
*/
+@LargeTest
public class DistroFormatVersionTest {
@Test
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
index eecae46..b69054c 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
@@ -21,12 +21,14 @@
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import org.junit.Test;
/**
* Tests for {@link DistroRulesVersion}.
*/
+@LargeTest
public class DistroRulesVersionTest {
@Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
index 99abe24..dd46240 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -23,12 +23,14 @@
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import org.junit.Test;
/**
* Tests for {@link RulesState}.
*/
+@LargeTest
public class RulesStateTest {
@Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
index 91f8ebc..e4aac50 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
+import android.support.test.filters.LargeTest;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
@@ -33,6 +34,7 @@
/**
* Tests for {@link RulesUpdaterContract}.
*/
+@LargeTest
public class RulesUpdaterContractTest {
@Test
diff --git a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
index d92eece..10d74f7 100644
--- a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
+++ b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
@@ -17,6 +17,7 @@
import android.os.Bundle;
import android.os.Parcelable;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import java.util.Arrays;
@@ -24,6 +25,7 @@
import java.util.List;
import java.util.Set;
+@LargeTest
public class RestrictionsManagerTest extends AndroidTestCase {
private RestrictionsManager mRm;
diff --git a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
index 948e722..659f9ea 100644
--- a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
+++ b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import java.io.ByteArrayInputStream;
@@ -27,6 +28,7 @@
import libcore.io.Streams;
+@LargeTest
public class MacAuthenticatedInputStreamTest extends AndroidTestCase {
private static final SecretKey HMAC_KEY_1 = new SecretKeySpec("test_key_1".getBytes(), "HMAC");
diff --git a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
index a9d19b4..952bb55 100644
--- a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
+++ b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
@@ -2,6 +2,7 @@
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.test.filters.LargeTest;
import junit.framework.TestCase;
@@ -9,6 +10,7 @@
import java.util.Collections;
import java.util.List;
+@LargeTest
public class ParceledListSliceTest extends TestCase {
public void testSmallList() throws Exception {
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 271c639..d3d1f22a 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -21,6 +21,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -43,6 +44,7 @@
/**
* Tests for {@link android.content.pm.RegisteredServicesCache}
*/
+@LargeTest
public class RegisteredServicesCacheTest extends AndroidTestCase {
private static final int U0 = 0;
private static final int U1 = 1;
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index 89d5997..a3fa1a9 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -16,8 +16,11 @@
package android.content.pm;
+import android.support.test.filters.LargeTest;
+
import junit.framework.TestCase;
+@LargeTest
public class SignatureTest extends TestCase {
/** Cert A with valid syntax */
diff --git a/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java b/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
index d963812..68942cb 100644
--- a/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
+++ b/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
@@ -19,6 +19,7 @@
import android.content.pm.VerificationParams;
import android.net.Uri;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
/**
@@ -27,6 +28,7 @@
* To test run:
* ./development/testrunner/runtest.py frameworks-core -c android.content.pm.VerificationParamsTest
*/
+@LargeTest
public class VerificationParamsTest extends AndroidTestCase {
private final static String VERIFICATION_URI_STRING = "http://verification.uri/path";
diff --git a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
index cb13eb7..88d7a59 100644
--- a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
+++ b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
@@ -17,9 +17,11 @@
package android.content.pm;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import java.util.Random;
+@LargeTest
public class VerifierDeviceIdentityTest extends android.test.AndroidTestCase {
private static final long TEST_1 = 0x7A5F00FF5A55AAA5L;
diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
index 7550cb5..b28a4b5 100644
--- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
@@ -15,6 +15,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.util.Log;
import android.util.PathParser;
@@ -23,6 +24,7 @@
import java.util.Arrays;
import org.junit.Test;
+@LargeTest
public class AdaptiveIconDrawableTest extends AndroidTestCase {
public static final String TAG = AdaptiveIconDrawableTest.class.getSimpleName();
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
index a2e9ae8..f30b1a2 100644
--- a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -30,6 +30,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -49,6 +50,7 @@
* Contains additional tests that cannot be included in CTS because they require
* system permissions. See also the CTS version of VirtualDisplayTest.
*/
+@LargeTest
public class VirtualDisplayTest extends AndroidTestCase {
private static final String TAG = "VirtualDisplayTest";
diff --git a/core/tests/coretests/src/android/metrics/LogMakerTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
index ada59cd..3be776d 100644
--- a/core/tests/coretests/src/android/metrics/LogMakerTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -15,9 +15,13 @@
*/
package android.metrics;
+import android.support.test.filters.LargeTest;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
import junit.framework.TestCase;
+@LargeTest
public class LogMakerTest extends TestCase {
public void testSerialize() {
diff --git a/core/tests/coretests/src/android/metrics/MetricsReaderTest.java b/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
index d10b351..784a12f 100644
--- a/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
+++ b/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
@@ -16,6 +16,7 @@
package android.metrics;
import android.metrics.MetricsReader.Event;
+import android.support.test.filters.LargeTest;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -23,6 +24,7 @@
import java.util.Collection;
+@LargeTest
public class MetricsReaderTest extends TestCase {
private static final int FULL_N = 10;
private static final int CHECKPOINTED_N = 4;
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
new file mode 100644
index 0000000..7350db7
--- /dev/null
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.WorkSource.WorkChain;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Provides unit tests for hidden / unstable WorkSource APIs that are not CTS testable.
+ *
+ * These tests will be moved to CTS when finalized.
+ */
+public class WorkSourceTest extends TestCase {
+ public void testWorkChain_add() {
+ WorkChain wc1 = new WorkChain();
+ wc1.addNode(56, null);
+
+ assertEquals(56, wc1.getUids()[0]);
+ assertEquals(null, wc1.getTags()[0]);
+ assertEquals(1, wc1.getSize());
+
+ wc1.addNode(57, "foo");
+ assertEquals(56, wc1.getUids()[0]);
+ assertEquals(null, wc1.getTags()[0]);
+ assertEquals(57, wc1.getUids()[1]);
+ assertEquals("foo", wc1.getTags()[1]);
+
+ assertEquals(2, wc1.getSize());
+ }
+
+ public void testWorkChain_equalsHashCode() {
+ WorkChain wc1 = new WorkChain();
+ WorkChain wc2 = new WorkChain();
+
+ assertEquals(wc1, wc2);
+ assertEquals(wc1.hashCode(), wc2.hashCode());
+
+ wc1.addNode(1, null);
+ wc2.addNode(1, null);
+ assertEquals(wc1, wc2);
+ assertEquals(wc1.hashCode(), wc2.hashCode());
+
+ wc1.addNode(2, "tag");
+ wc2.addNode(2, "tag");
+ assertEquals(wc1, wc2);
+ assertEquals(wc1.hashCode(), wc2.hashCode());
+
+ wc1 = new WorkChain();
+ wc2 = new WorkChain();
+ wc1.addNode(5, null);
+ wc2.addNode(6, null);
+ assertFalse(wc1.equals(wc2));
+ assertFalse(wc1.hashCode() == wc2.hashCode());
+
+ wc1 = new WorkChain();
+ wc2 = new WorkChain();
+ wc1.addNode(5, "tag1");
+ wc2.addNode(5, "tag2");
+ assertFalse(wc1.equals(wc2));
+ assertFalse(wc1.hashCode() == wc2.hashCode());
+ }
+
+ public void testWorkChain_constructor() {
+ WorkChain wc1 = new WorkChain();
+ wc1.addNode(1, "foo")
+ .addNode(2, null)
+ .addNode(3, "baz");
+
+ WorkChain wc2 = new WorkChain(wc1);
+ assertEquals(wc1, wc2);
+
+ wc1.addNode(4, "baz");
+ assertFalse(wc1.equals(wc2));
+ }
+
+ public void testDiff_workChains() {
+ WorkSource ws1 = new WorkSource();
+ ws1.add(50);
+ ws1.createWorkChain().addNode(52, "foo");
+ WorkSource ws2 = new WorkSource();
+ ws2.add(50);
+ ws2.createWorkChain().addNode(60, "bar");
+
+ // Diffs don't take WorkChains into account for the sake of backward compatibility.
+ assertFalse(ws1.diff(ws2));
+ assertFalse(ws2.diff(ws1));
+ }
+
+ public void testEquals_workChains() {
+ WorkSource ws1 = new WorkSource();
+ ws1.add(50);
+ ws1.createWorkChain().addNode(52, "foo");
+
+ WorkSource ws2 = new WorkSource();
+ ws2.add(50);
+ ws2.createWorkChain().addNode(52, "foo");
+
+ assertEquals(ws1, ws2);
+
+ // Unequal number of WorkChains.
+ ws2.createWorkChain().addNode(53, "baz");
+ assertFalse(ws1.equals(ws2));
+
+ // Different WorkChain contents.
+ WorkSource ws3 = new WorkSource();
+ ws3.add(50);
+ ws3.createWorkChain().addNode(60, "bar");
+
+ assertFalse(ws1.equals(ws3));
+ assertFalse(ws3.equals(ws1));
+ }
+
+ public void testWorkSourceParcelling() {
+ WorkSource ws = new WorkSource();
+
+ WorkChain wc = ws.createWorkChain();
+ wc.addNode(56, "foo");
+ wc.addNode(75, "baz");
+ WorkChain wc2 = ws.createWorkChain();
+ wc2.addNode(20, "foo2");
+ wc2.addNode(30, "baz2");
+
+ Parcel p = Parcel.obtain();
+ ws.writeToParcel(p, 0);
+ p.setDataPosition(0);
+
+ WorkSource unparcelled = WorkSource.CREATOR.createFromParcel(p);
+
+ assertEquals(unparcelled, ws);
+ }
+
+ public void testSet_workChains() {
+ WorkSource ws1 = new WorkSource();
+ ws1.add(50);
+
+ WorkSource ws2 = new WorkSource();
+ ws2.add(60);
+ WorkChain wc = ws2.createWorkChain();
+ wc.addNode(75, "tag");
+
+ ws1.set(ws2);
+
+ // Assert that the WorkChains are copied across correctly to the new WorkSource object.
+ List<WorkChain> workChains = ws1.getWorkChains();
+ assertEquals(1, workChains.size());
+
+ assertEquals(1, workChains.get(0).getSize());
+ assertEquals(75, workChains.get(0).getUids()[0]);
+ assertEquals("tag", workChains.get(0).getTags()[0]);
+
+ // Also assert that a deep copy of workchains is made, so the addition of a new WorkChain
+ // or the modification of an existing WorkChain has no effect.
+ ws2.createWorkChain();
+ assertEquals(1, ws1.getWorkChains().size());
+
+ wc.addNode(50, "tag2");
+ assertEquals(1, ws1.getWorkChains().size());
+ assertEquals(1, ws1.getWorkChains().get(0).getSize());
+ }
+
+ public void testSet_nullWorkChain() {
+ WorkSource ws = new WorkSource();
+ ws.add(60);
+ WorkChain wc = ws.createWorkChain();
+ wc.addNode(75, "tag");
+
+ ws.set(null);
+ assertEquals(0, ws.getWorkChains().size());
+ }
+
+ public void testAdd_workChains() {
+ WorkSource ws = new WorkSource();
+ ws.createWorkChain().addNode(70, "foo");
+
+ WorkSource ws2 = new WorkSource();
+ ws2.createWorkChain().addNode(60, "tag");
+
+ ws.add(ws2);
+
+ // Check that the new WorkChain is added to the end of the list.
+ List<WorkChain> workChains = ws.getWorkChains();
+ assertEquals(2, workChains.size());
+ assertEquals(1, workChains.get(1).getSize());
+ assertEquals(60, ws.getWorkChains().get(1).getUids()[0]);
+ assertEquals("tag", ws.getWorkChains().get(1).getTags()[0]);
+
+ // Adding the same WorkChain twice should be a no-op.
+ ws.add(ws2);
+ assertEquals(2, workChains.size());
+ }
+}
diff --git a/core/tests/coretests/src/android/preference/ListPreferenceTest.java b/core/tests/coretests/src/android/preference/ListPreferenceTest.java
index 41f8e03..72f62f1 100644
--- a/core/tests/coretests/src/android/preference/ListPreferenceTest.java
+++ b/core/tests/coretests/src/android/preference/ListPreferenceTest.java
@@ -17,8 +17,10 @@
package android.preference;
import android.preference.ListPreference;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
+@LargeTest
public class ListPreferenceTest extends AndroidTestCase {
public void testListPreferenceSummaryFromEntries() {
String[] entries = { "one", "two", "three" };
diff --git a/core/tests/coretests/src/android/text/OWNERS b/core/tests/coretests/src/android/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/core/tests/coretests/src/android/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index f4514b5..d817278 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -697,9 +697,13 @@
public void testGetOffset_UNICODE_Hebrew() {
String testString = "\u05DE\u05E1\u05E2\u05D3\u05D4"; // Hebrew Characters
for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) {
- StaticLayout layout = new StaticLayout(seq, mDefaultPaint,
- DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN,
- TextDirectionHeuristics.RTL, SPACE_MULTI, SPACE_ADD, true);
+ StaticLayout.Builder b = StaticLayout.Builder.obtain(
+ seq, 0, seq.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH)
+ .setAlignment(DEFAULT_ALIGN)
+ .setTextDirection(TextDirectionHeuristics.RTL)
+ .setLineSpacing(SPACE_ADD, SPACE_MULTI)
+ .setIncludePad(true);
+ StaticLayout layout = b.build();
String testLabel = buildTestMessage(seq);
diff --git a/core/tests/coretests/src/android/transition/TransitionTest.java b/core/tests/coretests/src/android/transition/TransitionTest.java
index ab4320c..7e629f9 100644
--- a/core/tests/coretests/src/android/transition/TransitionTest.java
+++ b/core/tests/coretests/src/android/transition/TransitionTest.java
@@ -19,6 +19,7 @@
import android.animation.AnimatorSetActivity;
import android.app.Activity;
import android.graphics.Rect;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.transition.Transition.EpicenterCallback;
import android.util.ArrayMap;
@@ -30,6 +31,7 @@
import java.lang.reflect.Field;
+@LargeTest
public class TransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {
Activity mActivity;
public TransitionTest() {
diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java
index 32aa29f..f0cc7f7 100644
--- a/core/tests/coretests/src/android/util/ArrayMapTest.java
+++ b/core/tests/coretests/src/android/util/ArrayMapTest.java
@@ -14,6 +14,7 @@
package android.util;
+import android.support.test.filters.LargeTest;
import android.util.ArrayMap;
import junit.framework.TestCase;
@@ -24,6 +25,7 @@
/**
* Unit tests for ArrayMap that don't belong in CTS.
*/
+@LargeTest
public class ArrayMapTest extends TestCase {
private static final String TAG = "ArrayMapTest";
ArrayMap<String, String> map = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/util/Base64Test.java b/core/tests/coretests/src/android/util/Base64Test.java
index 53368d4..3aee583 100644
--- a/core/tests/coretests/src/android/util/Base64Test.java
+++ b/core/tests/coretests/src/android/util/Base64Test.java
@@ -16,15 +16,17 @@
package android.util;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import junit.framework.TestCase;
+import android.support.test.filters.LargeTest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
import java.util.Random;
+import junit.framework.TestCase;
+@LargeTest
public class Base64Test extends TestCase {
private static final String TAG = "Base64Test";
diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/coretests/src/android/util/LocalLogTest.java
index a63c8a0..5144c85e 100644
--- a/core/tests/coretests/src/android/util/LocalLogTest.java
+++ b/core/tests/coretests/src/android/util/LocalLogTest.java
@@ -16,6 +16,8 @@
package android.util;
+import android.support.test.filters.LargeTest;
+
import junit.framework.TestCase;
import java.io.PrintWriter;
@@ -24,7 +26,7 @@
import java.util.Collections;
import java.util.List;
-
+@LargeTest
public class LocalLogTest extends TestCase {
public void testA() {
diff --git a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
index cb468bc..3f03db9 100644
--- a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
@@ -16,6 +16,8 @@
package android.util;
+import android.support.test.filters.LargeTest;
+
import junit.framework.TestCase;
import java.util.HashMap;
@@ -26,6 +28,7 @@
/**
* Tests for {@link LongSparseLongArray}.
*/
+@LargeTest
public class LongSparseLongArrayTest extends TestCase {
private static final String TAG = "LongSparseLongArrayTest";
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index 6dd787d..0d8c679 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -25,6 +25,7 @@
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
@@ -34,7 +35,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@@ -45,19 +45,14 @@
/** This is not a consistent cutout. Useful for verifying insets in one go though. */
final DisplayCutout mCutoutNumbers = new DisplayCutout(
new Rect(1, 2, 3, 4),
- new Rect(5, 6, 7, 8),
- Arrays.asList(
- new Point(9, 10),
- new Point(11, 12),
- new Point(13, 14),
- new Point(15, 16)));
+ new Region(5, 6, 7, 8));
final DisplayCutout mCutoutTop = createCutoutTop();
@Test
public void hasCutout() throws Exception {
- assertFalse(NO_CUTOUT.hasCutout());
- assertTrue(mCutoutTop.hasCutout());
+ assertTrue(NO_CUTOUT.isEmpty());
+ assertFalse(mCutoutTop.isEmpty());
}
@Test
@@ -67,30 +62,12 @@
assertEquals(3, mCutoutNumbers.getSafeInsetRight());
assertEquals(4, mCutoutNumbers.getSafeInsetBottom());
- Rect safeInsets = new Rect();
- mCutoutNumbers.getSafeInsets(safeInsets);
-
- assertEquals(new Rect(1, 2, 3, 4), safeInsets);
+ assertEquals(new Rect(1, 2, 3, 4), mCutoutNumbers.getSafeInsets());
}
@Test
public void getBoundingRect() throws Exception {
- Rect boundingRect = new Rect();
- mCutoutTop.getBoundingRect(boundingRect);
-
- assertEquals(new Rect(50, 0, 75, 100), boundingRect);
- }
-
- @Test
- public void getBoundingPolygon() throws Exception {
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- mCutoutTop.getBoundingPolygon(boundingPolygon);
-
- assertEquals(Arrays.asList(
- new Point(75, 0),
- new Point(50, 0),
- new Point(75, 100),
- new Point(50, 100)), boundingPolygon);
+ assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBoundingRect());
}
@Test
@@ -167,104 +144,61 @@
assertEquals(cutout.getSafeInsetRight(), 0);
assertEquals(cutout.getSafeInsetBottom(), 0);
- assertFalse(cutout.hasCutout());
+ assertTrue(cutout.isEmpty());
}
@Test
public void inset_bounds() throws Exception {
DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4);
- Rect boundingRect = new Rect();
- cutout.getBoundingRect(boundingRect);
-
- assertEquals(new Rect(49, -2, 74, 98), boundingRect);
-
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- cutout.getBoundingPolygon(boundingPolygon);
-
- assertEquals(Arrays.asList(
- new Point(74, -2),
- new Point(49, -2),
- new Point(74, 98),
- new Point(49, 98)), boundingPolygon);
+ assertEquals(new Rect(49, -2, 74, 98), cutout.getBoundingRect());
}
@Test
public void calculateRelativeTo_top() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 200, 400));
- Rect insets = new Rect();
- cutout.getSafeInsets(insets);
-
- assertEquals(new Rect(0, 100, 0, 0), insets);
+ assertEquals(new Rect(0, 100, 0, 0), cutout.getSafeInsets());
}
@Test
public void calculateRelativeTo_left() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 400, 200));
- Rect insets = new Rect();
- cutout.getSafeInsets(insets);
-
- assertEquals(new Rect(75, 0, 0, 0), insets);
+ assertEquals(new Rect(75, 0, 0, 0), cutout.getSafeInsets());
}
@Test
public void calculateRelativeTo_bottom() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, -300, 200, 100));
- Rect insets = new Rect();
- cutout.getSafeInsets(insets);
-
- assertEquals(new Rect(0, 0, 0, 100), insets);
+ assertEquals(new Rect(0, 0, 0, 100), cutout.getSafeInsets());
}
@Test
public void calculateRelativeTo_right() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-400, -200, 100, 100));
- Rect insets = new Rect();
- cutout.getSafeInsets(insets);
-
- assertEquals(new Rect(0, 0, 50, 0), insets);
+ assertEquals(new Rect(0, 0, 50, 0), cutout.getSafeInsets());
}
@Test
public void calculateRelativeTo_bounds() throws Exception {
DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-1000, -2000, 100, 200));
-
- Rect boundingRect = new Rect();
- cutout.getBoundingRect(boundingRect);
- assertEquals(new Rect(1050, 2000, 1075, 2100), boundingRect);
-
- ArrayList<Point> boundingPolygon = new ArrayList<>();
- cutout.getBoundingPolygon(boundingPolygon);
-
- assertEquals(Arrays.asList(
- new Point(1075, 2000),
- new Point(1050, 2000),
- new Point(1075, 2100),
- new Point(1050, 2100)), boundingPolygon);
+ assertEquals(new Rect(1050, 2000, 1075, 2100), cutout.getBoundingRect());
}
@Test
public void fromBoundingPolygon() throws Exception {
assertEquals(
- new DisplayCutout(
- new Rect(0, 0, 0, 0), // fromBoundingPolygon won't calculate safe insets.
- new Rect(50, 0, 75, 100),
- Arrays.asList(
- new Point(75, 0),
- new Point(50, 0),
- new Point(75, 100),
- new Point(50, 100))),
+ new Rect(50, 0, 75, 100),
DisplayCutout.fromBoundingPolygon(
Arrays.asList(
new Point(75, 0),
new Point(50, 0),
new Point(75, 100),
- new Point(50, 100))));
+ new Point(50, 100))).getBounds().getBounds());
}
@Test
@@ -324,24 +258,12 @@
}
private static DisplayCutout createCutoutTop() {
- return new DisplayCutout(
- new Rect(0, 100, 0, 0),
- new Rect(50, 0, 75, 100),
- Arrays.asList(
- new Point(75, 0),
- new Point(50, 0),
- new Point(75, 100),
- new Point(50, 100)));
+ return createCutoutWithInsets(0, 100, 0, 0);
}
private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) {
return new DisplayCutout(
new Rect(left, top, right, bottom),
- new Rect(50, 0, 75, 100),
- Arrays.asList(
- new Point(75, 0),
- new Point(50, 0),
- new Point(75, 100),
- new Point(50, 100)));
+ new Region(50, 0, 75, 100));
}
}
diff --git a/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
index 23d0251..fba8eae 100644
--- a/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
+++ b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
@@ -17,6 +17,7 @@
package android.view;
import android.content.Context;
+import android.support.test.filters.LargeTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
@@ -36,6 +37,7 @@
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.Espresso.onView;
+@LargeTest
public class ScaleGestureDetectorTest extends ActivityInstrumentationTestCase2<ScaleGesture> {
private ScaleGesture mScaleGestureActivity;
diff --git a/core/tests/coretests/src/android/view/ViewAttachTest.java b/core/tests/coretests/src/android/view/ViewAttachTest.java
index 44fcd13..aa8f8d8 100644
--- a/core/tests/coretests/src/android/view/ViewAttachTest.java
+++ b/core/tests/coretests/src/android/view/ViewAttachTest.java
@@ -20,6 +20,7 @@
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.view.View;
@@ -28,6 +29,7 @@
import com.android.frameworks.coretests.R;
+@LargeTest
public class ViewAttachTest extends
ActivityInstrumentationTestCase2<ViewAttachTestActivity> {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 2f32d13..4de8155 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
@@ -42,7 +43,7 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
-
+@LargeTest
@RunWith(AndroidJUnit4.class)
public class AccessibilityCacheTest {
private static final int WINDOW_ID_1 = 0xBEEF;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index c1b2309..318d122 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -22,6 +22,7 @@
import android.os.Bundle;
import android.os.RemoteException;
+import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import libcore.util.EmptyArray;
@@ -36,6 +37,7 @@
/**
* Tests for AccessibilityInteractionClient
*/
+@LargeTest
@RunWith(AndroidJUnit4.class)
public class AccessibilityInteractionClientTest {
private static final int MOCK_CONNECTION_ID = 0xabcd;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 79e6316..b135025 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.fail;
+import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -29,6 +30,7 @@
import java.util.ArrayList;
+@LargeTest
@RunWith(AndroidJUnit4.class)
public class AccessibilityNodeInfoTest {
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
index e3c91e6..9edbf3e 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -26,6 +26,7 @@
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
@@ -34,6 +35,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+@LargeTest
@RunWith(AndroidJUnit4.class)
public class InputMethodInfoTest {
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
index 9347b27..8ed0d86 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
@@ -21,8 +21,10 @@
import com.android.internal.view.menu.MenuBuilder;
import android.content.pm.ActivityInfo;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase;
+@LargeTest
public class MenuLayoutLandscapeTest extends ActivityInstrumentationTestCase<MenuLayoutLandscape> {
private static final String LONG_TITLE = "Really really really really really really really really really really long title";
private static final String SHORT_TITLE = "Item";
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
index b053699..ccf1264 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
@@ -16,13 +16,12 @@
package android.view.menu;
-import android.util.KeyUtils;
-import com.android.internal.view.menu.IconMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
import android.content.pm.ActivityInfo;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase;
+import android.util.KeyUtils;
+@LargeTest
public class MenuLayoutPortraitTest extends ActivityInstrumentationTestCase<MenuLayoutPortrait> {
private static final String LONG_TITLE = "Really really really really really really really really really really long title";
private static final String SHORT_TITLE = "Item";
diff --git a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
index 513e40f..be85450 100644
--- a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
+++ b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
import android.view.View;
@@ -28,6 +29,7 @@
/**
* Test {@link DatePicker} focus changes.
*/
+@LargeTest
public class DatePickerFocusTest extends ActivityInstrumentationTestCase2<DatePickerActivity> {
private Activity mActivity;
diff --git a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
index 28a3b67..2add221 100644
--- a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
+++ b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
@@ -22,6 +22,7 @@
import android.graphics.PointF;
import android.graphics.RectF;
+import android.support.test.filters.LargeTest;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +32,7 @@
import java.util.Arrays;
import java.util.List;
+@LargeTest
@RunWith(JUnit4.class)
public final class SelectionActionModeHelperTest {
diff --git a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
index b591e5f..43986ee 100644
--- a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
+++ b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
@@ -18,6 +18,7 @@
import android.widget.focus.HorizontalFocusSearch;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase;
import android.test.suitebuilder.annotation.Suppress;
import android.widget.LinearLayout;
@@ -32,6 +33,7 @@
* various widths and vertical placements.
*/
// Suppress until bug http://b/issue?id=1416545 is fixed.
+@LargeTest
@Suppress
public class HorizontalFocusSearchTest extends ActivityInstrumentationTestCase<HorizontalFocusSearch> {
diff --git a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
index f05d83a..f01422e 100644
--- a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
+++ b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
@@ -18,6 +18,7 @@
import android.widget.focus.VerticalFocusSearch;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase;
import android.test.suitebuilder.annotation.Suppress;
import android.view.FocusFinder;
@@ -31,6 +32,7 @@
* various widths and horizontal placements.
*/
// Suppress until bug http://b/issue?id=1416545 is fixed
+@LargeTest
@Suppress
public class VerticalFocusSearchTest extends ActivityInstrumentationTestCase<VerticalFocusSearch> {
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
index 400fd7d..9a8e634 100644
--- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
@@ -16,12 +16,14 @@
package android.widget.listview.arrowscroll;
-import android.widget.listview.ListWithFirstScreenUnSelectable;
+import android.support.test.filters.LargeTest;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
import android.widget.ListView;
+import android.widget.listview.ListWithFirstScreenUnSelectable;
import android.widget.AdapterView;
+@LargeTest
public class ListWithFirstScreenUnSelectableTest
extends ActivityInstrumentationTestCase2<ListWithFirstScreenUnSelectable> {
private ListView mListView;
diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk
index c1e8c98..5bfde78 100644
--- a/core/tests/packagemanagertests/Android.mk
+++ b/core/tests/packagemanagertests/Android.mk
@@ -10,7 +10,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
- frameworks-base-testutils
+ frameworks-base-testutils \
+ mockito-target-minus-junit4
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCorePackageManagerTests
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8e7147c..7bb2859 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -236,6 +236,7 @@
<permission name="android.permission.CHANGE_CONFIGURATION"/>
<permission name="android.permission.DELETE_PACKAGES"/>
<permission name="android.permission.FORCE_STOP_PACKAGES"/>
+ <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_FINGERPRINT"/>
<permission name="android.permission.MANAGE_USB"/>
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
new file mode 100644
index 0000000..60416a7
--- /dev/null
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RawRes;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.NinePatchDrawable;
+
+import java.nio.ByteBuffer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.lang.NullPointerException;
+import java.lang.RuntimeException;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
+ * @hide
+ */
+public final class ImageDecoder {
+ /**
+ * Source of the encoded image data.
+ */
+ public static abstract class Source {
+ /* @hide */
+ Resources getResources() { return null; }
+
+ /* @hide */
+ void close() {}
+
+ /* @hide */
+ abstract ImageDecoder createImageDecoder();
+ };
+
+ private static class ByteArraySource extends Source {
+ ByteArraySource(byte[] data, int offset, int length) {
+ mData = data;
+ mOffset = offset;
+ mLength = length;
+ };
+ private final byte[] mData;
+ private final int mOffset;
+ private final int mLength;
+
+ @Override
+ public ImageDecoder createImageDecoder() {
+ return nCreate(mData, mOffset, mLength);
+ }
+ }
+
+ private static class ByteBufferSource extends Source {
+ ByteBufferSource(ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+ private final ByteBuffer mBuffer;
+
+ @Override
+ public ImageDecoder createImageDecoder() {
+ if (!mBuffer.isDirect() && mBuffer.hasArray()) {
+ int offset = mBuffer.arrayOffset() + mBuffer.position();
+ int length = mBuffer.limit() - mBuffer.position();
+ return nCreate(mBuffer.array(), offset, length);
+ }
+ return nCreate(mBuffer, mBuffer.position(), mBuffer.limit());
+ }
+ }
+
+ private static class ResourceSource extends Source {
+ ResourceSource(Resources res, int resId)
+ throws Resources.NotFoundException {
+ // Test that the resource can be found.
+ InputStream is = null;
+ try {
+ is = res.openRawResource(resId);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ mResources = res;
+ mResId = resId;
+ }
+
+ final Resources mResources;
+ final int mResId;
+ // This is just stored here in order to keep the underlying Asset
+ // alive. FIXME: Can I access the Asset (and keep it alive) without
+ // this object?
+ InputStream mInputStream;
+
+ @Override
+ public Resources getResources() { return mResources; }
+
+ @Override
+ public ImageDecoder createImageDecoder() {
+ // FIXME: Can I bypass creating the stream?
+ try {
+ mInputStream = mResources.openRawResource(mResId);
+ } catch (Resources.NotFoundException e) {
+ // This should never happen, since we already tested in the
+ // constructor.
+ }
+ if (!(mInputStream instanceof AssetManager.AssetInputStream)) {
+ // This should never happen.
+ throw new RuntimeException("Resource is not an asset?");
+ }
+ long asset = ((AssetManager.AssetInputStream) mInputStream).getNativeAsset();
+ return nCreate(asset);
+ }
+
+ @Override
+ public void close() {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ } finally {
+ mInputStream = null;
+ }
+ }
+ }
+
+ /**
+ * Contains information about the encoded image.
+ */
+ public static class ImageInfo {
+ public final int width;
+ public final int height;
+ // TODO?: Add more info? mimetype, ninepatch etc?
+
+ ImageInfo(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+ };
+
+ /**
+ * Used if the provided data is incomplete.
+ *
+ * There may be a partial image to display.
+ */
+ public class IncompleteException extends Exception {};
+
+ /**
+ * Used if the provided data is corrupt.
+ *
+ * There may be a partial image to display.
+ */
+ public class CorruptException extends Exception {};
+
+ /**
+ * Optional listener supplied to {@link #decodeDrawable} or
+ * {@link #decodeBitmap}.
+ */
+ public static interface OnHeaderDecodedListener {
+ /**
+ * Called when the header is decoded and the size is known.
+ *
+ * @param info Information about the encoded image.
+ * @param decoder allows changing the default settings of the decode.
+ */
+ public void onHeaderDecoded(ImageInfo info, ImageDecoder decoder);
+
+ };
+
+ /**
+ * Optional listener supplied to the ImageDecoder.
+ */
+ public static interface OnExceptionListener {
+ /**
+ * Called when there is a problem in the stream or in the data.
+ * FIXME: Or do not allow streams?
+ * FIXME: Report how much of the image has been decoded?
+ *
+ * @param e Exception containing information about the error.
+ * @return True to create and return a {@link Drawable}/
+ * {@link Bitmap} with partial data. False to return
+ * {@code null}. True is the default.
+ */
+ public boolean onException(Exception e);
+ };
+
+ // Fields
+ private long mNativePtr;
+ private final int mWidth;
+ private final int mHeight;
+
+ private int mDesiredWidth;
+ private int mDesiredHeight;
+ private int mAllocator = DEFAULT_ALLOCATOR;
+ private boolean mRequireUnpremultiplied = false;
+ private boolean mMutable = false;
+ private boolean mPreferRamOverQuality = false;
+ private boolean mAsAlphaMask = false;
+ private Rect mCropRect;
+
+ private PostProcess mPostProcess;
+ private OnExceptionListener mOnExceptionListener;
+
+
+ /**
+ * Private constructor called by JNI. {@link #recycle} must be
+ * called after decoding to delete native resources.
+ */
+ @SuppressWarnings("unused")
+ private ImageDecoder(long nativePtr, int width, int height) {
+ mNativePtr = nativePtr;
+ mWidth = width;
+ mHeight = height;
+ mDesiredWidth = width;
+ mDesiredHeight = height;
+ }
+
+ /**
+ * Create a new {@link Source} from an asset.
+ *
+ * @param res the {@link Resources} object containing the image data.
+ * @param resId resource ID of the image data.
+ * // FIXME: Can be an @DrawableRes?
+ * @return a new Source object, which can be passed to
+ * {@link #decodeDrawable} or {@link #decodeBitmap}.
+ * @throws Resources.NotFoundException if the asset does not exist.
+ */
+ public static Source createSource(@NonNull Resources res, @RawRes int resId)
+ throws Resources.NotFoundException {
+ return new ResourceSource(res, resId);
+ }
+
+ /**
+ * Create a new {@link Source} from a byte array.
+ * @param data byte array of compressed image data.
+ * @param offset offset into data for where the decoder should begin
+ * parsing.
+ * @param length number of bytes, beginning at offset, to parse.
+ * @throws NullPointerException if data is null.
+ * @throws ArrayIndexOutOfBoundsException if offset and length are
+ * not within data.
+ */
+ // TODO: Overloads that don't use offset, length
+ public static Source createSource(@NonNull byte[] data, int offset,
+ int length) throws ArrayIndexOutOfBoundsException {
+ if (data == null) {
+ throw new NullPointerException("null byte[] in createSource!");
+ }
+ if (offset < 0 || length < 0 || offset >= data.length ||
+ offset + length > data.length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "invalid offset/length!");
+ }
+ return new ByteArraySource(data, offset, length);
+ }
+
+ /**
+ * Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
+ *
+ * The returned {@link Source} effectively takes ownership of the
+ * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after
+ * this call.
+ *
+ * Decoding will start from {@link java.nio.ByteBuffer#position()}.
+ */
+ public static Source createSource(ByteBuffer buffer) {
+ return new ByteBufferSource(buffer);
+ }
+
+ /**
+ * Return the width and height of a given sample size.
+ *
+ * This takes an input that functions like
+ * {@link BitmapFactory.Options#inSampleSize}. It returns a width and
+ * height that can be acheived by sampling the encoded image. Other widths
+ * and heights may be supported, but will require an additional (internal)
+ * scaling step. Such internal scaling is *not* supported with
+ * {@link #requireUnpremultiplied}.
+ *
+ * @param sampleSize Sampling rate of the encoded image.
+ * @return Point {@link Point#x} and {@link Point#y} correspond to the
+ * width and height after sampling.
+ */
+ public Point getSampledSize(int sampleSize) {
+ if (sampleSize <= 0) {
+ throw new IllegalArgumentException("sampleSize must be positive! "
+ + "provided " + sampleSize);
+ }
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("ImageDecoder is recycled!");
+ }
+
+ return nGetSampledSize(mNativePtr, sampleSize);
+ }
+
+ // Modifiers
+ /**
+ * Resize the output to have the following size.
+ *
+ * @param width must be greater than 0.
+ * @param height must be greater than 0.
+ */
+ public void resize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Dimensions must be positive! "
+ + "provided (" + width + ", " + height + ")");
+ }
+
+ mDesiredWidth = width;
+ mDesiredHeight = height;
+ }
+
+ /**
+ * Resize based on a sample size.
+ *
+ * This has the same effect as passing the result of
+ * {@link #getSampledSize} to {@link #resize(int, int)}.
+ *
+ * @param sampleSize Sampling rate of the encoded image.
+ */
+ public void resize(int sampleSize) {
+ Point dimensions = this.getSampledSize(sampleSize);
+ this.resize(dimensions.x, dimensions.y);
+ }
+
+ // These need to stay in sync with ImageDecoder.cpp's Allocator enum.
+ /**
+ * Use the default allocation for the pixel memory.
+ *
+ * Will typically result in a {@link Bitmap.Config#HARDWARE}
+ * allocation, but may be software for small images. In addition, this will
+ * switch to software when HARDWARE is incompatible, e.g.
+ * {@link #setMutable}, {@link #setAsAlphaMask}.
+ */
+ public static final int DEFAULT_ALLOCATOR = 0;
+
+ /**
+ * Use a software allocation for the pixel memory.
+ *
+ * Useful for drawing to a software {@link Canvas} or for
+ * accessing the pixels on the final output.
+ */
+ public static final int SOFTWARE_ALLOCATOR = 1;
+
+ /**
+ * Use shared memory for the pixel memory.
+ *
+ * Useful for sharing across processes.
+ */
+ public static final int SHARED_MEMORY_ALLOCATOR = 2;
+
+ /**
+ * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
+ *
+ * This will throw an {@link java.lang.IllegalStateException} when combined
+ * with incompatible options, like {@link #setMutable} or
+ * {@link #setAsAlphaMask}.
+ */
+ public static final int HARDWARE_ALLOCATOR = 3;
+
+ /** @hide **/
+ @Retention(SOURCE)
+ @IntDef({ DEFAULT_ALLOCATOR, SOFTWARE_ALLOCATOR, SHARED_MEMORY_ALLOCATOR,
+ HARDWARE_ALLOCATOR })
+ public @interface Allocator {};
+
+ /**
+ * Choose the backing for the pixel memory.
+ *
+ * This is ignored for animated drawables.
+ *
+ * TODO: Allow accessing the backing from the Bitmap.
+ *
+ * @param allocator Type of allocator to use.
+ */
+ public void setAllocator(@Allocator int allocator) {
+ if (allocator < DEFAULT_ALLOCATOR || allocator > HARDWARE_ALLOCATOR) {
+ throw new IllegalArgumentException("invalid allocator " + allocator);
+ }
+ mAllocator = allocator;
+ }
+
+ /**
+ * Create a {@link Bitmap} with unpremultiplied pixels.
+ *
+ * By default, ImageDecoder will create a {@link Bitmap} with
+ * premultiplied pixels, which is required for drawing with the
+ * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
+ * this method will result in {@link #decodeBitmap} returning a
+ * {@link Bitmap} with unpremultiplied pixels. See
+ * {@link Bitmap#isPremultiplied}. Incompatible with
+ * {@link #decodeDrawable}; attempting to decode an unpremultiplied
+ * {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
+ */
+ public void requireUnpremultiplied() {
+ mRequireUnpremultiplied = true;
+ }
+
+ /**
+ * Modify the image after decoding and scaling.
+ *
+ * This allows adding effects prior to returning a {@link Drawable} or
+ * {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
+ * this is the only way to process the image after decoding.
+ *
+ * If set on a nine-patch image, the nine-patch data is ignored.
+ *
+ * For an animated image, the drawing commands drawn on the {@link Canvas}
+ * will be recorded immediately and then applied to each frame.
+ */
+ public void setPostProcess(PostProcess p) {
+ mPostProcess = p;
+ }
+
+ /**
+ * Set (replace) the {@link OnExceptionListener} on this object.
+ *
+ * Will be called if there is an error in the input. Without one, a
+ * partial {@link Bitmap} will be created.
+ */
+ public void setOnExceptionListener(OnExceptionListener l) {
+ mOnExceptionListener = l;
+ }
+
+ /**
+ * Crop the output to {@code subset} of the (possibly) scaled image.
+ *
+ * {@code subset} must be contained within the size set by {@link #resize}
+ * or the bounds of the image if resize was not called. Otherwise an
+ * {@link IllegalStateException} will be thrown.
+ *
+ * NOT intended as a replacement for
+ * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
+ * but merely crops the output.
+ */
+ public void crop(Rect subset) {
+ mCropRect = subset;
+ }
+
+ /**
+ * Create a mutable {@link Bitmap}.
+ *
+ * By default, a {@link Bitmap} created will be immutable, but that can be
+ * changed with this call.
+ *
+ * Incompatible with {@link #HARDWARE_ALLOCATOR}, because
+ * {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. Attempting to
+ * combine them will throw an {@link java.lang.IllegalStateException}.
+ *
+ * Incompatible with {@link #decodeDrawable}, which would require
+ * retrieving the Bitmap from the returned Drawable in order to modify.
+ * Attempting to decode a mutable {@link Drawable} will throw an
+ * {@link java.lang.IllegalStateException}
+ */
+ public void setMutable() {
+ mMutable = true;
+ }
+
+ /**
+ * Potentially save RAM at the expense of quality.
+ *
+ * This may result in a {@link Bitmap} with a denser {@link Bitmap.Config},
+ * depending on the image. For example, for an opaque {@link Bitmap}, this
+ * may result in a {@link Bitmap.Config} with no alpha information.
+ */
+ public void setPreferRamOverQuality() {
+ mPreferRamOverQuality = true;
+ }
+
+ /**
+ * Potentially treat the output as an alpha mask.
+ *
+ * If the image is encoded in a format with only one channel, treat that
+ * channel as alpha. Otherwise this call has no effect.
+ *
+ * Incompatible with {@link #HARDWARE_ALLOCATOR}. Trying to combine them
+ * will throw an {@link java.lang.IllegalStateException}.
+ */
+ public void setAsAlphaMask() {
+ mAsAlphaMask = true;
+ }
+
+ /**
+ * Clean up resources.
+ *
+ * ImageDecoder has a private constructor, and will always be recycled
+ * by decodeDrawable or decodeBitmap which creates it, so there is no
+ * need for a finalizer.
+ */
+ private void recycle() {
+ if (mNativePtr == 0) {
+ return;
+ }
+ nRecycle(mNativePtr);
+ mNativePtr = 0;
+ }
+
+ private void checkState() {
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("Cannot reuse ImageDecoder.Source!");
+ }
+
+ checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
+
+ if (mAllocator == HARDWARE_ALLOCATOR) {
+ if (mMutable) {
+ throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
+ }
+ if (mAsAlphaMask) {
+ throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!");
+ }
+ }
+
+ if (mPostProcess != null && mRequireUnpremultiplied) {
+ throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
+ }
+ }
+
+ private static void checkSubset(int width, int height, Rect r) {
+ if (r == null) {
+ return;
+ }
+ if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) {
+ throw new IllegalStateException("Subset " + r + " not contained by "
+ + "scaled image bounds: (" + width + " x " + height + ")");
+ }
+ }
+
+ /**
+ * Create a {@link Drawable}.
+ */
+ public static Drawable decodeDrawable(Source src, OnHeaderDecodedListener listener) {
+ ImageDecoder decoder = src.createImageDecoder();
+ if (decoder == null) {
+ return null;
+ }
+
+ if (listener != null) {
+ ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight);
+ listener.onHeaderDecoded(info, decoder);
+ }
+
+ decoder.checkState();
+
+ if (decoder.mRequireUnpremultiplied) {
+ // Though this could be supported (ignored) for opaque images, it
+ // seems better to always report this error.
+ throw new IllegalStateException("Cannot decode a Drawable with" +
+ " unpremultiplied pixels!");
+ }
+
+ if (decoder.mMutable) {
+ throw new IllegalStateException("Cannot decode a mutable Drawable!");
+ }
+
+ try {
+ Bitmap bm = nDecodeBitmap(decoder.mNativePtr,
+ decoder.mOnExceptionListener,
+ decoder.mPostProcess,
+ decoder.mDesiredWidth, decoder.mDesiredHeight,
+ decoder.mCropRect,
+ false, // decoder.mMutable
+ decoder.mAllocator,
+ false, // decoder.mRequireUnpremultiplied
+ decoder.mPreferRamOverQuality,
+ decoder.mAsAlphaMask
+ );
+ if (bm == null) {
+ return null;
+ }
+
+ Resources res = src.getResources();
+ if (res == null) {
+ bm.setDensity(Bitmap.DENSITY_NONE);
+ }
+
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ Rect opticalInsets = new Rect();
+ bm.getOpticalInsets(opticalInsets);
+ Rect padding = new Rect();
+ nGetPadding(decoder.mNativePtr, padding);
+ return new NinePatchDrawable(res, bm, np, padding,
+ opticalInsets, null);
+ }
+
+ // TODO: Handle animation.
+ return new BitmapDrawable(res, bm);
+ } finally {
+ decoder.recycle();
+ src.close();
+ }
+ }
+
+ /**
+ * Create a {@link Bitmap}.
+ */
+ public static Bitmap decodeBitmap(Source src, OnHeaderDecodedListener listener) {
+ ImageDecoder decoder = src.createImageDecoder();
+ if (decoder == null) {
+ return null;
+ }
+
+ if (listener != null) {
+ ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight);
+ listener.onHeaderDecoded(info, decoder);
+ }
+
+ decoder.checkState();
+
+ try {
+ return nDecodeBitmap(decoder.mNativePtr,
+ decoder.mOnExceptionListener,
+ decoder.mPostProcess,
+ decoder.mDesiredWidth, decoder.mDesiredHeight,
+ decoder.mCropRect,
+ decoder.mMutable,
+ decoder.mAllocator,
+ decoder.mRequireUnpremultiplied,
+ decoder.mPreferRamOverQuality,
+ decoder.mAsAlphaMask);
+ } finally {
+ decoder.recycle();
+ src.close();
+ }
+ }
+
+ private static native ImageDecoder nCreate(long asset);
+ private static native ImageDecoder nCreate(ByteBuffer buffer,
+ int position,
+ int limit);
+ private static native ImageDecoder nCreate(byte[] data, int offset,
+ int length);
+ private static native Bitmap nDecodeBitmap(long nativePtr,
+ OnExceptionListener listener,
+ PostProcess postProcess,
+ int width, int height,
+ Rect cropRect, boolean mutable,
+ int allocator, boolean requireUnpremul,
+ boolean preferRamOverQuality, boolean asAlphaMask);
+ private static native Point nGetSampledSize(long nativePtr,
+ int sampleSize);
+ private static native void nGetPadding(long nativePtr, Rect outRect);
+ private static native void nRecycle(long nativePtr);
+}
diff --git a/graphics/java/android/graphics/PostProcess.java b/graphics/java/android/graphics/PostProcess.java
new file mode 100644
index 0000000..c5a31e8
--- /dev/null
+++ b/graphics/java/android/graphics/PostProcess.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.drawable.Drawable;
+
+
+/**
+ * Helper interface for adding custom processing to an image.
+ *
+ * The image being processed may be a {@link Drawable}, {@link Bitmap} or frame
+ * of an animated image produced by {@link ImageDecoder}. This is called before
+ * the requested object is returned.
+ *
+ * This custom processing also applies to image types that are otherwise
+ * immutable, such as {@link Bitmap.Config#HARDWARE}.
+ *
+ * On an animated image, the callback will only be called once, but the drawing
+ * commands will be applied to each frame, as if the {@code Canvas} had been
+ * returned by {@link Picture#beginRecording}.
+ *
+ * Supplied to ImageDecoder via {@link ImageDecoder#setPostProcess}.
+ * @hide
+ */
+public interface PostProcess {
+ /**
+ * Do any processing after (for example) decoding.
+ *
+ * Drawing to the {@link Canvas} will behave as if the initial processing
+ * (e.g. decoding) already exists in the Canvas. An implementation can draw
+ * effects on top of this, or it can even draw behind it using
+ * {@link PorterDuff.Mode#DST_OVER}. A common effect is to add transparency
+ * to the corners to achieve rounded corners. That can be done with the
+ * following code:
+ *
+ * <code>
+ * Path path = new Path();
+ * path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
+ * path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
+ * Paint paint = new Paint();
+ * paint.setAntiAlias(true);
+ * paint.setColor(Color.TRANSPARENT);
+ * paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+ * canvas.drawPath(path, paint);
+ * return PixelFormat.TRANSLUCENT;
+ * </code>
+ *
+ *
+ * @param canvas The {@link Canvas} to draw to.
+ * @param width Width of {@code canvas}. Anything drawn outside of this
+ * will be ignored.
+ * @param height Height of {@code canvas}. Anything drawn outside of this
+ * will be ignored.
+ * @return Opacity of the result after drawing.
+ * {@link PixelFormat#UNKNOWN} means that the implementation did not
+ * change whether the image has alpha. Return this unless you added
+ * transparency (e.g. with the code above, in which case you should
+ * return {@code PixelFormat.TRANSLUCENT}) or you forced the image to
+ * be opaque (e.g. by drawing everywhere with an opaque color and
+ * {@code PorterDuff.Mode.DST_OVER}, in which case you should return
+ * {@code PixelFormat.OPAQUE}).
+ * {@link PixelFormat#TRANSLUCENT} means that the implementation added
+ * transparency. This is safe to return even if the image already had
+ * transparency. This is also safe to return if the result is opaque,
+ * though it may draw more slowly.
+ * {@link PixelFormat#OPAQUE} means that the implementation forced the
+ * image to be opaque. This is safe to return even if the image was
+ * already opaque.
+ * {@link PixelFormat#TRANSPARENT} (or any other integer) is not
+ * allowed, and will result in throwing an
+ * {@link java.lang.IllegalArgumentException}.
+ */
+ @PixelFormat.Opacity
+ public int postProcess(@NonNull Canvas canvas, int width, int height);
+}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index eca52cc..7c7417d 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -35,6 +35,7 @@
boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
boolean attestKey(in String alias, in byte[] challenge, out KeymasterCertificateChain chain);
+ boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
// APIs used by CertInstaller and DevicePolicyManager
String installCaCertificate(in byte[] caCertificate);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 3fe75cf..5b95c81 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6880,6 +6880,9 @@
return UNKNOWN_ERROR;
}
+ // The number of resources overlaid that were not explicitly marked overlayable.
+ size_t forcedOverlayCount = 0u;
+
KeyedVector<uint8_t, IdmapTypeMap> map;
// overlaid packages are assumed to contain only one package group
@@ -6919,6 +6922,7 @@
continue;
}
+ uint32_t typeSpecFlags = 0u;
const String16 overlayType(resName.type, resName.typeLen);
const String16 overlayName(resName.name, resName.nameLen);
uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
@@ -6926,14 +6930,23 @@
overlayType.string(),
overlayType.size(),
overlayPackage.string(),
- overlayPackage.size());
+ overlayPackage.size(),
+ &typeSpecFlags);
if (overlayResID == 0) {
+ // No such target resource was found.
if (typeMap.entryMap.isEmpty()) {
typeMap.entryOffset++;
}
continue;
}
+ // Now that we know this is being overlaid, check if it can be, and emit a warning if
+ // it can't.
+ if ((dtohl(typeConfigs->typeSpecFlags[entryIndex]) &
+ ResTable_typeSpec::SPEC_OVERLAYABLE) == 0) {
+ forcedOverlayCount++;
+ }
+
if (typeMap.overlayTypeId == -1) {
typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
}
@@ -7012,6 +7025,10 @@
typeData += entryCount * 2;
}
+ if (forcedOverlayCount > 0) {
+ ALOGW("idmap: overlaid %zu resources not marked overlayable", forcedOverlayCount);
+ }
+
return NO_ERROR;
}
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 20d0178..8cf4de9 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1339,9 +1339,13 @@
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
- enum {
+ enum : uint32_t {
// Additional flag indicating an entry is public.
- SPEC_PUBLIC = 0x40000000
+ SPEC_PUBLIC = 0x40000000u,
+
+ // Additional flag indicating an entry is overlayable at runtime.
+ // Added in Android-P.
+ SPEC_OVERLAYABLE = 0x80000000u,
};
};
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 0c17328..18ef75e 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_de_fr.apk b/libs/androidfw/tests/data/basic/basic_de_fr.apk
index e45258c..767dff6 100644
--- a/libs/androidfw/tests/data/basic/basic_de_fr.apk
+++ b/libs/androidfw/tests/data/basic/basic_de_fr.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
index 4ae1a7c..58953f5 100644
--- a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
index a240d4c..103f656 100644
--- a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
index fd3d9b2..61369d5 100644
--- a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
index d619800..5682ed4 100755
--- a/libs/androidfw/tests/data/basic/build
+++ b/libs/androidfw/tests/data/basic/build
@@ -19,11 +19,15 @@
PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
-aapt package \
- -M AndroidManifest.xml \
- -S res \
- -A assets \
+aapt2 compile --dir res -o compiled.flata
+aapt2 link \
-I $PATH_TO_FRAMEWORK_RES \
- --split hdpi --split xhdpi --split xxhdpi --split fr,de \
- -F basic.apk \
- -f
+ --manifest AndroidManifest.xml \
+ -A assets \
+ --split basic_hdpi-v4.apk:hdpi \
+ --split basic_xhdpi-v4.apk:xhdpi \
+ --split basic_xxhdpi-v4.apk:xxhdpi \
+ --split basic_de_fr.apk:de,fr \
+ -o basic.apk \
+ compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index 638c983..6c47459 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -60,4 +60,9 @@
<item>2</item>
<item>3</item>
</integer-array>
+
+ <overlayable>
+ <item type="string" name="test2" />
+ <item type="array" name="integerArray1" />
+ </overlayable>
</resources>
diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build
index 112f373..716b1bd 100755
--- a/libs/androidfw/tests/data/overlay/build
+++ b/libs/androidfw/tests/data/overlay/build
@@ -17,4 +17,6 @@
set -e
-aapt package -M AndroidManifest.xml -S res -F overlay.apk -f
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -o overlay.apk compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index 40bf17c..33f9611 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index a8262c8..974b2a4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -271,7 +271,7 @@
* @param now The current time, used to tell whether daylight savings is active.
* @return A CharSequence suitable for display as the offset label of {@code tz}.
*/
- private static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale,
+ public static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale,
TimeZone tz, Date now) {
final SpannableStringBuilder builder = new SpannableStringBuilder();
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 3f826cc..6025d68 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -16,21 +16,21 @@
package com.android.settingslib.location;
-import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Process;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
import android.util.IconDrawableFactory;
import android.util.Log;
-
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
/**
@@ -38,11 +38,13 @@
*/
public class RecentLocationApps {
private static final String TAG = RecentLocationApps.class.getSimpleName();
- private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
+ @VisibleForTesting
+ static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000;
- private static final int[] LOCATION_OPS = new int[] {
+ @VisibleForTesting
+ static final int[] LOCATION_OPS = new int[] {
AppOpsManager.OP_MONITOR_LOCATION,
AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
};
@@ -59,6 +61,7 @@
/**
* Fills a list of applications which queried location recently within specified time.
+ * Apps are sorted by recency. Apps with more recent location requests are in the front.
*/
public List<Request> getAppList() {
// Retrieve a location usage list from AppOps
@@ -91,7 +94,18 @@
requests.add(request);
}
}
+ return requests;
+ }
+ public List<Request> getAppListSorted() {
+ List<Request> requests = getAppList();
+ // Sort the list of Requests by recency. Most recent request first.
+ Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() {
+ @Override
+ public int compare(Request request1, Request request2) {
+ return Long.compare(request1.requestFinishTime, request2.requestFinishTime);
+ }
+ }));
return requests;
}
@@ -108,10 +122,12 @@
List<AppOpsManager.OpEntry> entries = ops.getOps();
boolean highBattery = false;
boolean normalBattery = false;
+ long locationRequestFinishTime = 0L;
// Earliest time for a location request to end and still be shown in list.
long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
for (AppOpsManager.OpEntry entry : entries) {
if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
+ locationRequestFinishTime = entry.getTime() + entry.getDuration();
switch (entry.getOp()) {
case AppOpsManager.OP_MONITOR_LOCATION:
normalBattery = true;
@@ -133,15 +149,13 @@
}
// The package is fresh enough, continue.
-
int uid = ops.getUid();
int userId = UserHandle.getUserId(uid);
Request request = null;
try {
- IPackageManager ipm = AppGlobals.getPackageManager();
- ApplicationInfo appInfo =
- ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId);
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userId);
if (appInfo == null) {
Log.w(TAG, "Null application info retrieved for package " + packageName
+ ", userId " + userId);
@@ -158,12 +172,10 @@
badgedAppLabel = null;
}
request = new Request(packageName, userHandle, icon, appLabel, highBattery,
- badgedAppLabel);
- } catch (RemoteException e) {
- Log.w(TAG, "Error while retrieving application info for package " + packageName
- + ", userId " + userId, e);
+ badgedAppLabel, locationRequestFinishTime);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
}
-
return request;
}
@@ -174,15 +186,18 @@
public final CharSequence label;
public final boolean isHighBattery;
public final CharSequence contentDescription;
+ public final long requestFinishTime;
private Request(String packageName, UserHandle userHandle, Drawable icon,
- CharSequence label, boolean isHighBattery, CharSequence contentDescription) {
+ CharSequence label, boolean isHighBattery, CharSequence contentDescription,
+ long requestFinishTime) {
this.packageName = packageName;
this.userHandle = userHandle;
this.icon = icon;
this.label = label;
this.isHighBattery = isHighBattery;
this.contentDescription = contentDescription;
+ this.requestFinishTime = requestFinishTime;
}
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
new file mode 100644
index 0000000..226166b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
@@ -0,0 +1,162 @@
+package com.android.settingslib.location;
+
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.when;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(
+ manifest = TestConfig.MANIFEST_PATH,
+ sdk = TestConfig.SDK_VERSION)
+public class RecentLocationAppsTest {
+
+ private static final int TEST_UID = 1234;
+ private static final long NOW = System.currentTimeMillis();
+ // App running duration in milliseconds
+ private static final int DURATION = 10;
+ private static final long ONE_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(1);
+ private static final long FOURTEEN_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(14);
+ private static final long TWENTY_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(20);
+ private static final String[] TEST_PACKAGE_NAMES =
+ {"package_1MinAgo", "package_14MinAgo", "package_20MinAgo"};
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private AppOpsManager mAppOpsManager;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private UserManager mUserManager;
+ private int mTestUserId;
+ private RecentLocationApps mRecentLocationApps;
+
+
+
+ @Before
+ public void setUp() throws NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mPackageManager.getApplicationLabel(isA(ApplicationInfo.class)))
+ .thenReturn("testApplicationLabel");
+ when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class)))
+ .thenReturn("testUserBadgedLabel");
+ mTestUserId = UserHandle.getUserId(TEST_UID);
+ when(mUserManager.getUserProfiles())
+ .thenReturn(Collections.singletonList(new UserHandle(mTestUserId)));
+
+ long[] testRequestTime = {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO};
+ List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
+ when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
+ mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
+
+ mRecentLocationApps = new RecentLocationApps(mContext);
+ }
+
+ @Test
+ public void testGetAppList_shouldFilterRecentApps() {
+ List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
+ // Only two of the apps have requested location within 15 min.
+ assertThat(requests).hasSize(2);
+ // Make sure apps are ordered by recency
+ assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
+ assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION);
+ assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
+ assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION);
+ }
+
+ @Test
+ public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException {
+ // Add android OS to the list of apps.
+ PackageOps androidSystemPackageOps =
+ createPackageOps(
+ RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME,
+ Process.SYSTEM_UID,
+ AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
+ ONE_MIN_AGO,
+ DURATION);
+ long[] testRequestTime =
+ {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO, ONE_MIN_AGO};
+ List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
+ appOps.add(androidSystemPackageOps);
+ when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
+ mockTestApplicationInfos(
+ Process.SYSTEM_UID, RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME);
+
+ List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
+ // Android OS shouldn't show up in the list of apps.
+ assertThat(requests).hasSize(2);
+ // Make sure apps are ordered by recency
+ assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
+ assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION);
+ assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
+ assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION);
+ }
+
+ private void mockTestApplicationInfos(int userId, String... packageNameList)
+ throws NameNotFoundException {
+ for (String packageName : packageNameList) {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.packageName = packageName;
+ when(mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userId)).thenReturn(appInfo);
+ }
+ }
+
+ private List<PackageOps> createTestPackageOpsList(String[] packageNameList, long[] time) {
+ List<PackageOps> packageOpsList = new ArrayList<>();
+ for (int i = 0; i < packageNameList.length ; i++) {
+ PackageOps packageOps = createPackageOps(
+ packageNameList[i],
+ TEST_UID,
+ AppOpsManager.OP_MONITOR_LOCATION,
+ time[i],
+ DURATION);
+ packageOpsList.add(packageOps);
+ }
+ return packageOpsList;
+ }
+
+ private PackageOps createPackageOps(
+ String packageName, int uid, int op, long time, int duration) {
+ return new PackageOps(
+ packageName,
+ uid,
+ Collections.singletonList(createOpEntryWithTime(op, time, duration)));
+ }
+
+ private OpEntry createOpEntryWithTime(int op, long time, int duration) {
+ return new OpEntry(op, AppOpsManager.MODE_ALLOWED, time, 0L, duration, 0, "");
+ }
+}
diff --git a/packages/SystemUI/res/drawable/ic_cast.xml b/packages/SystemUI/res/drawable/ic_cast.xml
new file mode 100644
index 0000000..b86dfea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_cast.xml
@@ -0,0 +1,31 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M1 18v2c0 .55 .45 1 1 1h2c0-1.66-1.34-3-3-3zm0-2.94c-.01 .51 .32 .93 .82 1.02
+2.08 .36 3.74 2 4.1 4.08 .09 .48 .5 .84 .99 .84 .61 0 1.09-.54 1-1.14a6.996
+6.996 0 0 0-5.8-5.78c-.59-.09-1.09 .38 -1.11 .98 zm0-4.03c-.01 .52 .34 .96 .85
+1.01 4.26 .43 7.68 3.82 8.1 8.08 .05 .5 .48 .88 .99 .88 .59 0 1.06-.51
+1-1.1-.52-5.21-4.66-9.34-9.87-9.85-.57-.05-1.05 .4 -1.07 .98 zM21 3H3c-1.1 0-2
+.9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker.xml b/packages/SystemUI/res/drawable/ic_speaker.xml
new file mode 100644
index 0000000..1ea293c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
+ <path
+ android:pathData="M17,2L7,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,1.99 2,1.99L17,22c1.1,0 2,-0.9 2,-2L19,4c0,-1.1 -0.9,-2 -2,-2zM12,4c1.1,0 2,0.9 2,2s-0.9,2 -2,2c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2zM12,20c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_group.xml b/packages/SystemUI/res/drawable/ic_speaker_group.xml
new file mode 100644
index 0000000..d6867d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_group.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
+ <path
+ android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"
+ android:fillColor="#FFFFFFFF"/>
+ <path
+ android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"
+ android:fillColor="#FFFFFFFF"/>
+ <path
+ android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_tv.xml b/packages/SystemUI/res/drawable/ic_tv.xml
new file mode 100644
index 0000000..cc2ae91
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_tv.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
+ <path
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12z"
+ android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/output_chooser.xml b/packages/SystemUI/res/layout/output_chooser.xml
index 22c3bcf..3d0ab35 100644
--- a/packages/SystemUI/res/layout/output_chooser.xml
+++ b/packages/SystemUI/res/layout/output_chooser.xml
@@ -19,6 +19,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:id="@+id/output_chooser"
+ android:minWidth="320dp"
+ android:minHeight="320dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp" >
@@ -39,12 +41,6 @@
android:gravity="center"
android:orientation="vertical">
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:tint="?android:attr/textColorSecondary" />
-
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 78e621e..fd205dd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1269,6 +1269,14 @@
<string name="volume_dialog_accessibility_shown_message">%s volume controls shown. Swipe up to dismiss.</string>
<string name="volume_dialog_accessibility_dismissed_message">Volume controls hidden</string>
+ <string name="output_title">Media output</string>
+ <string name="output_calls_title">Phone call output</string>
+ <string name="output_none_found">No devices found</string>
+ <string name="output_none_found_service_off">No devices found. Try turning on <xliff:g id="service" example="Bluetooth">%1$s</xliff:g></string>
+ <string name="output_service_bt">Bluetooth</string>
+ <string name="output_service_wifi">Wi-Fi</string>
+ <string name="output_service_bt_wifi">Bluetooth and Wi-Fi</string>
+
<!-- Name of special SystemUI debug settings -->
<string name="system_ui_tuner">System UI Tuner</string>
diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
index edd1748..6aa465c 100644
--- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -24,6 +24,7 @@
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Point;
+import android.graphics.Region;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -36,7 +37,8 @@
import android.view.WindowInsets;
import android.view.WindowManager;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Emulates a display cutout by drawing its shape in an overlay as supplied by
@@ -85,6 +87,7 @@
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
+ lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
lp.setTitle("EmulatedDisplayCutout");
lp.gravity = Gravity.TOP;
return lp;
@@ -102,9 +105,8 @@
};
private static class CutoutView extends View {
- private Paint mPaint = new Paint();
- private Path mPath = new Path();
- private ArrayList<Point> mBoundingPolygon = new ArrayList<>();
+ private final Paint mPaint = new Paint();
+ private final Path mBounds = new Path();
CutoutView(Context context) {
super(context);
@@ -112,28 +114,22 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- insets.getDisplayCutout().getBoundingPolygon(mBoundingPolygon);
+ if (insets.getDisplayCutout() != null) {
+ insets.getDisplayCutout().getBounds().getBoundaryPath(mBounds);
+ } else {
+ mBounds.reset();
+ }
invalidate();
- return insets.consumeCutout();
+ return insets.consumeDisplayCutout();
}
@Override
protected void onDraw(Canvas canvas) {
- if (!mBoundingPolygon.isEmpty()) {
+ if (!mBounds.isEmpty()) {
mPaint.setColor(Color.DKGRAY);
mPaint.setStyle(Paint.Style.FILL);
- mPath.reset();
- for (int i = 0; i < mBoundingPolygon.size(); i++) {
- Point point = mBoundingPolygon.get(i);
- if (i == 0) {
- mPath.moveTo(point.x, point.y);
- } else {
- mPath.lineTo(point.x, point.y);
- }
- }
- mPath.close();
- canvas.drawPath(mPath, mPaint);
+ canvas.drawPath(mBounds, mPaint);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 50c1ede..ee41001 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -53,6 +53,8 @@
final boolean isAmbientMode;
switch (newState) {
case DOZE_AOD:
+ case DOZE_AOD_PAUSING:
+ case DOZE_AOD_PAUSED:
case DOZE_REQUEST_PULSE:
case DOZE_PULSING:
case DOZE_PULSE_DONE:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 75321fd..1cbb440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -98,7 +98,7 @@
setClipToActualHeight(false);
setClipChildren(false);
setClipToPadding(false);
- mShelfIcons.setShowAllIcons(false);
+ mShelfIcons.setIsStaticLayout(false);
mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
NotificationPanelView.DOZE_ANIMATION_DURATION);
mShelfState = new ShelfState();
@@ -681,7 +681,8 @@
if (isLayoutRtl()) {
start = getWidth() - start - mCollapsedIcons.getWidth();
}
- int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
+ int width = (int) NotificationUtils.interpolate(
+ start + mCollapsedIcons.getFinalTranslationX(),
mShelfIcons.getWidth(),
openedAmount);
mShelfIcons.setActualLayoutWidth(width);
@@ -691,6 +692,9 @@
// we have to ensure that adding the low priority notification won't lead to an
// overflow
collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
+ } else {
+ // Partial overflow padding will fill enough space to add extra dots
+ collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
}
float padding = NotificationUtils.interpolate(collapsedPadding,
mShelfIcons.getPaddingEnd(),
@@ -700,7 +704,6 @@
mShelfIcons.getPaddingStart(), openedAmount);
mShelfIcons.setActualPaddingStart(paddingStart);
mShelfIcons.setOpenedAmount(openedAmount);
- mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
}
public void setMaxLayoutHeight(int maxLayoutHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 316bd5b..7f4deb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -61,14 +61,10 @@
public void setWorkModeEnabled(boolean enableWorkMode) {
synchronized (mProfiles) {
for (UserInfo ui : mProfiles) {
- if (enableWorkMode) {
- if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
- StatusBarManager statusBarManager = (StatusBarManager) mContext
- .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
- statusBarManager.collapsePanels();
- }
- } else {
- mUserManager.setQuietModeEnabled(ui.id, true);
+ if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
+ StatusBarManager statusBarManager = (StatusBarManager) mContext
+ .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+ statusBarManager.collapsePanels();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index a1b49c1..91cae0af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -100,8 +100,10 @@
}.setDuration(200).setDelay(50);
public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
+ public static final int MAX_STATIC_ICONS = 4;
+ private static final int MAX_DOTS = 3;
- private boolean mShowAllIcons = true;
+ private boolean mIsStaticLayout = true;
private final HashMap<View, IconState> mIconStates = new HashMap<>();
private int mDotPadding;
private int mStaticDotRadius;
@@ -115,11 +117,13 @@
private int mSpeedBumpIndex = -1;
private int mIconSize;
private float mOpenedAmount = 0.0f;
- private float mVisualOverflowAdaption;
private boolean mDisallowNextAnimation;
private boolean mAnimationsEnabled = true;
private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
private int mDarkOffsetX;
+ // Keep track of the last visible icon so collapsed container can report on its location
+ private IconState mLastVisibleIconState;
+
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -163,7 +167,7 @@
mIconSize = child.getWidth();
}
}
- if (mShowAllIcons) {
+ if (mIsStaticLayout) {
resetViewStates();
calculateIconTranslations();
applyIconStates();
@@ -287,7 +291,8 @@
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount;
+ int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
+ mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd();
float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
@@ -320,23 +325,6 @@
visualOverflowStart += (translationX - overflowStart) / mIconSize
* (mStaticDotRadius * 2 + mDotPadding);
}
- if (mShowAllIcons) {
- // We want to perfectly position the overflow in the static state, such that
- // it's perfectly centered instead of measuring it from the end.
- mVisualOverflowAdaption = 0;
- if (firstOverflowIndex != -1) {
- View firstOverflowView = getChildAt(i);
- IconState overflowState = mIconStates.get(firstOverflowView);
- float totalAmount = layoutEnd - overflowState.xTranslation;
- float newPosition = overflowState.xTranslation + totalAmount / 2
- - totalDotLength / 2
- - mIconSize * 0.5f + mStaticDotRadius;
- mVisualOverflowAdaption = newPosition - visualOverflowStart;
- visualOverflowStart = newPosition;
- }
- } else {
- visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
- }
}
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
@@ -348,20 +336,24 @@
IconState iconState = mIconStates.get(view);
int dotWidth = mStaticDotRadius * 2 + mDotPadding;
iconState.xTranslation = translationX;
- if (numDots <= 3) {
+ if (numDots <= MAX_DOTS) {
if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
iconState.visibleState = StatusBarIconView.STATE_ICON;
numDots--;
} else {
iconState.visibleState = StatusBarIconView.STATE_DOT;
}
- translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
+ translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
* iconState.iconAppearAmount;
+ mLastVisibleIconState = iconState;
} else {
iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
}
numDots++;
}
+ } else if (childCount > 0) {
+ View lastChild = getChildAt(childCount - 1);
+ mLastVisibleIconState = mIconStates.get(lastChild);
}
boolean center = mDark;
if (center && translationX < getLayoutEnd()) {
@@ -415,13 +407,13 @@
}
/**
- * Sets whether the layout should always show all icons.
+ * Sets whether the layout should always show the same number of icons.
* If this is true, the icon positions will be updated on layout.
* If this if false, the layout is managed from the outside and layouting won't trigger a
* repositioning of the icons.
*/
- public void setShowAllIcons(boolean showAllIcons) {
- mShowAllIcons = showAllIcons;
+ public void setIsStaticLayout(boolean isStaticLayout) {
+ mIsStaticLayout = isStaticLayout;
}
public void setActualLayoutWidth(int actualLayoutWidth) {
@@ -452,6 +444,14 @@
return mActualLayoutWidth;
}
+ public int getFinalTranslationX() {
+ if (mLastVisibleIconState == null) {
+ return 0;
+ }
+
+ return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT));
+ }
+
public void setChangingViewPositions(boolean changingViewPositions) {
mChangingViewPositions = changingViewPositions;
}
@@ -479,19 +479,43 @@
mOpenedAmount = expandAmount;
}
- public float getVisualOverflowAdaption() {
- return mVisualOverflowAdaption;
- }
-
- public void setVisualOverflowAdaption(float visualOverflowAdaption) {
- mVisualOverflowAdaption = visualOverflowAdaption;
- }
-
public boolean hasOverflow() {
+ if (mIsStaticLayout) {
+ return getChildCount() > MAX_STATIC_ICONS;
+ }
+
float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
}
+ /**
+ * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
+ * extra padding will have to be accounted for
+ *
+ * This method has no meaning for non-static containers
+ */
+ public boolean hasPartialOverflow() {
+ if (mIsStaticLayout) {
+ int count = getChildCount();
+ return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get padding that can account for extra dots up to the max. The only valid values for
+ * this method are for 1 or 2 dots.
+ * @return only extraDotPadding or extraDotPadding * 2
+ */
+ public int getPartialOverflowExtraPadding() {
+ if (!hasPartialOverflow()) {
+ return 0;
+ }
+
+ return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding);
+ }
+
public int getIconSize() {
return mIconSize;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 8abc3f4..14329b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -255,8 +255,13 @@
mKeyguardFadeoutAnimation.cancel();
}
- // Do not let the device sleep until we're done with all animations
- holdWakeLock();
+ // The device might sleep if it's entering AOD, we need to make sure that
+ // the animation plays properly until the last frame.
+ // It's important to avoid holding the wakelock unless necessary because
+ // WakeLock#aqcuire will trigger an IPC and will cause jank.
+ if (mState == ScrimState.AOD) {
+ holdWakeLock();
+ }
// AOD wallpapers should fade away after a while
if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index fa82e33..f8843a9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -16,6 +16,10 @@
package com.android.systemui.volume;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
+import static android.support.v7.media.MediaRouter.UNSELECT_REASON_DISCONNECTED;
+
import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
import android.bluetooth.BluetoothClass;
@@ -27,7 +31,15 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
import android.util.Log;
import android.util.Pair;
@@ -38,8 +50,13 @@
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BluetoothController;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
public class OutputChooserDialog extends SystemUIDialog
implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
@@ -47,15 +64,33 @@
private static final String TAG = Util.logTag(OutputChooserDialog.class);
private static final int MAX_DEVICES = 10;
+ private static final long UPDATE_DELAY_MS = 300L;
+ static final int MSG_UPDATE_ITEMS = 1;
+
private final Context mContext;
private final BluetoothController mController;
+ private final WifiManager mWifiManager;
private OutputChooserLayout mView;
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mRouterCallback;
+ private long mLastUpdateTime;
+ private final MediaRouteSelector mRouteSelector;
+ private Drawable mDefaultIcon;
+ private Drawable mTvIcon;
+ private Drawable mSpeakerIcon;
+ private Drawable mSpeakerGroupIcon;
public OutputChooserDialog(Context context) {
super(context);
mContext = context;
mController = Dependency.get(BluetoothController.class);
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mRouter = MediaRouter.getInstance(context);
+ mRouterCallback = new MediaRouterCallback();
+ mRouteSelector = new MediaRouteSelector.Builder()
+ .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ .build();
final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mReceiver, filter);
@@ -67,10 +102,21 @@
setContentView(R.layout.output_chooser);
setCanceledOnTouchOutside(true);
setOnDismissListener(this::onDismiss);
+ setTitle(R.string.output_title);
+
mView = findViewById(R.id.output_chooser);
mView.setCallback(this);
- updateItems();
- mController.addCallback(mCallback);
+
+ mDefaultIcon = mContext.getDrawable(R.drawable.ic_cast);
+ mTvIcon = mContext.getDrawable(R.drawable.ic_tv);
+ mSpeakerIcon = mContext.getDrawable(R.drawable.ic_speaker);
+ mSpeakerGroupIcon = mContext.getDrawable(R.drawable.ic_speaker_group);
+
+ final boolean wifiOff = !mWifiManager.isWifiEnabled();
+ final boolean btOff = !mController.isBluetoothEnabled();
+ if (wifiOff || btOff) {
+ mView.setEmptyState(getDisabledServicesMessage(wifiOff, btOff));
+ }
}
protected void cleanUp() {}
@@ -82,43 +128,97 @@
}
@Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mRouter.addCallback(mRouteSelector, mRouterCallback,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ mController.addCallback(mCallback);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mRouter.removeCallback(mRouterCallback);
+ mController.removeCallback(mCallback);
+ super.onDetachedFromWindow();
+ }
+
+ @Override
public void onDismiss(DialogInterface unused) {
mContext.unregisterReceiver(mReceiver);
- mController.removeCallback(mCallback);
cleanUp();
}
@Override
public void onDetailItemClick(OutputChooserLayout.Item item) {
if (item == null || item.tag == null) return;
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- if (device != null && device.getMaxConnectionState()
- == BluetoothProfile.STATE_DISCONNECTED) {
- mController.connect(device);
+ if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device != null && device.getMaxConnectionState()
+ == BluetoothProfile.STATE_DISCONNECTED) {
+ mController.connect(device);
+ }
+ } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+ final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
+ if (route.isEnabled()) {
+ route.select();
+ }
}
}
@Override
public void onDetailItemDisconnect(OutputChooserLayout.Item item) {
if (item == null || item.tag == null) return;
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- if (device != null) {
- mController.disconnect(device);
+ if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device != null) {
+ mController.disconnect(device);
+ }
+ } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+ mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
}
}
private void updateItems() {
- if (mView == null) return;
- if (mController.isBluetoothEnabled()) {
- mView.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
- R.string.quick_settings_bluetooth_detail_empty_text);
- mView.setItemsVisible(true);
- } else {
- mView.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
- R.string.bt_is_off);
- mView.setItemsVisible(false);
+ if (SystemClock.uptimeMillis() - mLastUpdateTime < UPDATE_DELAY_MS) {
+ mHandler.removeMessages(MSG_UPDATE_ITEMS);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ITEMS),
+ mLastUpdateTime + UPDATE_DELAY_MS);
+ return;
}
+ mLastUpdateTime = SystemClock.uptimeMillis();
+ if (mView == null) return;
ArrayList<OutputChooserLayout.Item> items = new ArrayList<>();
+
+ // Add bluetooth devices
+ addBluetoothDevices(items);
+
+ // Add remote displays
+ addRemoteDisplayRoutes(items);
+
+ Collections.sort(items, ItemComparator.sInstance);
+
+ if (items.size() == 0) {
+ String emptyMessage = mContext.getString(R.string.output_none_found);
+ final boolean wifiOff = !mWifiManager.isWifiEnabled();
+ final boolean btOff = !mController.isBluetoothEnabled();
+ if (wifiOff || btOff) {
+ emptyMessage = getDisabledServicesMessage(wifiOff, btOff);
+ }
+ mView.setEmptyState(emptyMessage);
+ }
+
+ mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+ }
+
+ private String getDisabledServicesMessage(boolean wifiOff, boolean btOff) {
+ return mContext.getString(R.string.output_none_found_service_off,
+ wifiOff && btOff ? mContext.getString(R.string.output_service_bt_wifi)
+ : wifiOff ? mContext.getString(R.string.output_service_wifi)
+ : mContext.getString(R.string.output_service_bt));
+ }
+
+ private void addBluetoothDevices(List<OutputChooserLayout.Item> items) {
final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
int connectedDevices = 0;
@@ -134,6 +234,7 @@
item.iconResId = R.drawable.ic_qs_bluetooth_on;
item.line1 = device.getName();
item.tag = device;
+ item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
int state = device.getMaxConnectionState();
if (state == BluetoothProfile.STATE_CONNECTED) {
item.iconResId = R.drawable.ic_qs_bluetooth_connected;
@@ -163,7 +264,87 @@
}
}
}
- mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+ }
+
+ private void addRemoteDisplayRoutes(List<OutputChooserLayout.Item> items) {
+ List<MediaRouter.RouteInfo> routes = mRouter.getRoutes();
+ for(MediaRouter.RouteInfo route : routes) {
+ if (route.isDefaultOrBluetooth() || !route.isEnabled()
+ || !route.matchesSelector(mRouteSelector)) {
+ continue;
+ }
+ final OutputChooserLayout.Item item = new OutputChooserLayout.Item();
+ item.icon = getIconDrawable(route);
+ item.line1 = route.getName();
+ item.tag = route;
+ item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
+ if (route.getConnectionState() == CONNECTION_STATE_CONNECTING) {
+ mContext.getString(R.string.quick_settings_connecting);
+ } else {
+ item.line2 = route.getDescription();
+ }
+
+ if (route.getConnectionState() == CONNECTION_STATE_CONNECTED) {
+ item.canDisconnect = true;
+ }
+ items.add(item);
+ }
+ }
+
+ private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
+ Uri iconUri = route.getIconUri();
+ if (iconUri != null) {
+ try {
+ InputStream is = getContext().getContentResolver().openInputStream(iconUri);
+ Drawable drawable = Drawable.createFromStream(is, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to load " + iconUri, e);
+ // Falls back.
+ }
+ }
+ return getDefaultIconDrawable(route);
+ }
+
+ private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
+ // If the type of the receiver device is specified, use it.
+ switch (route.getDeviceType()) {
+ case MediaRouter.RouteInfo.DEVICE_TYPE_TV:
+ return mTvIcon;
+ case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
+ return mSpeakerIcon;
+ }
+
+ // Otherwise, make the best guess based on other route information.
+ if (route instanceof MediaRouter.RouteGroup) {
+ // Only speakers can be grouped for now.
+ return mSpeakerGroupIcon;
+ }
+ return mDefaultIcon;
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.Callback {
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems();
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems();
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+ dismiss();
+ }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -188,4 +369,33 @@
updateItems();
}
};
+
+ static final class ItemComparator implements Comparator<OutputChooserLayout.Item> {
+ public static final ItemComparator sInstance = new ItemComparator();
+
+ @Override
+ public int compare(OutputChooserLayout.Item lhs, OutputChooserLayout.Item rhs) {
+ // Connected item(s) first
+ if (lhs.canDisconnect != rhs.canDisconnect) {
+ return Boolean.compare(rhs.canDisconnect, lhs.canDisconnect);
+ }
+ // Bluetooth items before media routes
+ if (lhs.deviceType != rhs.deviceType) {
+ return Integer.compare(lhs.deviceType, rhs.deviceType);
+ }
+ // then by name
+ return lhs.line1.toString().compareToIgnoreCase(rhs.line1.toString());
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_ITEMS:
+ updateItems();
+ break;
+ }
+ }
+ };
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
index e8be4fd..22ced60 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
@@ -55,7 +55,6 @@
private AutoSizingList mItemList;
private View mEmpty;
private TextView mEmptyText;
- private ImageView mEmptyIcon;
private Item[] mItems;
@@ -76,7 +75,6 @@
mEmpty = findViewById(android.R.id.empty);
mEmpty.setVisibility(GONE);
mEmptyText = mEmpty.findViewById(android.R.id.title);
- mEmptyIcon = mEmpty.findViewById(android.R.id.icon);
}
@Override
@@ -93,9 +91,8 @@
}
}
- public void setEmptyState(int icon, int text) {
+ public void setEmptyState(String text) {
mEmpty.post(() -> {
- mEmptyIcon.setImageResource(icon);
mEmptyText.setText(text);
});
}
@@ -241,6 +238,8 @@
}
public static class Item {
+ public static int DEVICE_TYPE_BT = 1;
+ public static int DEVICE_TYPE_MEDIA_ROUTER = 2;
public int iconResId;
public Drawable icon;
public Drawable overlay;
@@ -249,6 +248,7 @@
public Object tag;
public boolean canDisconnect;
public int icon2 = -1;
+ public int deviceType = 0;
}
public interface Callback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 9b7efe9..6d2691c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
@@ -275,7 +276,7 @@
}
@Test
- public void testHoldsWakeLock() {
+ public void testHoldsWakeLock_whenAOD() {
mScrimController.transitionTo(ScrimState.AOD);
verify(mWakeLock).acquire();
verify(mWakeLock, never()).release();
@@ -284,6 +285,13 @@
}
@Test
+ public void testDoesNotHoldWakeLock_whenUnlocking() {
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.finishAnimationsImmediately();
+ verifyZeroInteractions(mWakeLock);
+ }
+
+ @Test
public void testCallbackInvokedOnSameStateTransition() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.finishAnimationsImmediately();
diff --git a/packages/VpnDialogs/res/values-ne/strings.xml b/packages/VpnDialogs/res/values-ne/strings.xml
index c19ae52..5019a06 100644
--- a/packages/VpnDialogs/res/values-ne/strings.xml
+++ b/packages/VpnDialogs/res/values-ne/strings.xml
@@ -25,8 +25,8 @@
<string name="data_received" msgid="4062776929376067820">"प्राप्त भयो:"</string>
<string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> बाइटहरू / <xliff:g id="NUMBER_1">%2$s</xliff:g> प्याकेटहरू"</string>
<string name="always_on_disconnected_title" msgid="1906740176262776166">"सधैँ-सक्रिय रहने VPN सेवामा जडान गर्न सकिँदैन"</string>
- <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> लाई सधैँ जडान भइरहनेगरि सेट अप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। तपाईंको फोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> मा पुन: जडान नहुँदासम्म यसले कुनै सार्वजनिक नेटवर्क प्रयोग गर्नेछ।"</string>
- <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> लाई सधैँ पनि जडान भइरहनेगरि सेट अप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। VPN पुन: जडान नहुँदासम्म तपाईंसँग कुनै पनि इन्टरनेट रहनेछैन।"</string>
+ <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> लाई सधैँ जडान भइरहनेगरि सेटअप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। तपाईंको फोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> मा पुन: जडान नहुँदासम्म यसले कुनै सार्वजनिक नेटवर्क प्रयोग गर्नेछ।"</string>
+ <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> लाई सधैँ पनि जडान भइरहनेगरि सेटअप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। VPN पुन: जडान नहुँदासम्म तपाईंसँग कुनै पनि इन्टरनेट रहनेछैन।"</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
<string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN सम्बन्धी सेटिङहरू परिवर्तन गर्नुहोस्"</string>
<string name="configure" msgid="4905518375574791375">"कन्फिगर गर्नुहोस्"</string>
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
similarity index 98%
rename from services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
rename to services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 01679dd..3d7d6b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -71,7 +71,7 @@
* This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
* It is responsible for behavior common to both types of clients.
*/
-abstract class AccessibilityClientConnection extends IAccessibilityServiceConnection.Stub
+abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
FingerprintGestureDispatcher.FingerprintGestureClient {
private static final boolean DEBUG = false;
@@ -238,7 +238,7 @@
int flags);
}
- public AccessibilityClientConnection(Context context, ComponentName componentName,
+ public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
@@ -339,6 +339,11 @@
}
}
+ int getRelevantEventTypes() {
+ return (mUsesAccessibilityCache ? AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK : 0)
+ | mEventTypes;
+ }
+
@Override
public void setServiceInfo(AccessibilityServiceInfo info) {
final long identity = Binder.clearCallingIdentity();
@@ -954,13 +959,15 @@
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
+ final int eventType = event.getEventType();
+
final boolean serviceWantsEvent = wantsEventLocked(event);
- if (!serviceWantsEvent && !mUsesAccessibilityCache &&
- ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & event.getEventType()) == 0)) {
+ final boolean requiredForCacheConsistency = mUsesAccessibilityCache
+ && ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
+ if (!serviceWantsEvent && !requiredForCacheConsistency) {
return;
}
- final int eventType = event.getEventType();
// Make a copy since during dispatch it is possible the event to
// be modified to remove its source if the receiving service does
// not have permission to access the window content.
@@ -1226,6 +1233,10 @@
return windowId;
}
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
private final class InvocationHandler extends Handler {
public static final int MSG_ON_GESTURE = 1;
public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 270a762..d83f6ae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -20,6 +20,8 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -84,7 +86,6 @@
import android.view.View;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
@@ -114,6 +115,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -131,7 +133,7 @@
* on the device. Events are dispatched to {@link AccessibilityService}s.
*/
public class AccessibilityManagerService extends IAccessibilityManager.Stub
- implements AccessibilityClientConnection.SystemSupport {
+ implements AbstractAccessibilityServiceConnection.SystemSupport {
private static final boolean DEBUG = false;
@@ -455,7 +457,7 @@
}
@Override
- public long addClient(IAccessibilityManagerClient client, int userId) {
+ public long addClient(IAccessibilityManagerClient callback, int userId) {
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
@@ -467,15 +469,17 @@
// the system UI or the system we add it to the global state that
// is shared across users.
UserState userState = getUserStateLocked(resolvedUserId);
+ Client client = new Client(callback, Binder.getCallingUid(), userState);
if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
- mGlobalClients.register(client);
+ mGlobalClients.register(callback, client);
if (DEBUG) {
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
return IntPair.of(
- userState.getClientState(), userState.mLastSentRelevantEventTypes);
+ userState.getClientState(),
+ client.mLastSentRelevantEventTypes);
} else {
- userState.mUserClients.register(client);
+ userState.mUserClients.register(callback, client);
// If this client is not for the current user we do not
// return a state since it is not for the foreground user.
// We will send the state to the client on a user switch.
@@ -485,7 +489,7 @@
}
return IntPair.of(
(resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0,
- userState.mLastSentRelevantEventTypes);
+ client.mLastSentRelevantEventTypes);
}
}
}
@@ -951,7 +955,7 @@
* Has no effect if no item has accessibility focus, if the item with accessibility
* focus does not expose the specified action, or if the action fails.
*
- * @param actionId The id of the action to perform.
+ * @param action The action to perform.
*
* @return {@code true} if the action was performed. {@code false} if it was not.
*/
@@ -1329,33 +1333,67 @@
}
private void updateRelevantEventsLocked(UserState userState) {
- int relevantEventTypes = AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK;
- for (AccessibilityServiceConnection service : userState.mBoundServices) {
- relevantEventTypes |= service.mEventTypes;
- }
- relevantEventTypes |= mUiAutomationManager.getRequestedEventMaskLocked();
- int finalRelevantEventTypes = relevantEventTypes;
+ mMainHandler.post(() -> {
+ broadcastToClients(userState, ignoreRemoteException(client -> {
+ int relevantEventTypes = computeRelevantEventTypes(userState, client);
- if (userState.mLastSentRelevantEventTypes != finalRelevantEventTypes) {
- userState.mLastSentRelevantEventTypes = finalRelevantEventTypes;
- mMainHandler.obtainMessage(MainHandler.MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS,
- userState.mUserId, finalRelevantEventTypes);
- mMainHandler.post(() -> {
- broadcastToClients(userState, (client) -> {
- try {
- client.setRelevantEventTypes(finalRelevantEventTypes);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
- });
+ if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+ client.mLastSentRelevantEventTypes = relevantEventTypes;
+ client.mCallback.setRelevantEventTypes(relevantEventTypes);
+ }
+ }));
+ });
+ }
+
+ private int computeRelevantEventTypes(UserState userState, Client client) {
+ int relevantEventTypes = 0;
+
+ int numBoundServices = userState.mBoundServices.size();
+ for (int i = 0; i < numBoundServices; i++) {
+ AccessibilityServiceConnection service =
+ userState.mBoundServices.get(i);
+ relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
+ ? service.getRelevantEventTypes()
+ : 0;
}
+ relevantEventTypes |= isClientInPackageWhitelist(
+ mUiAutomationManager.getServiceInfo(), client)
+ ? mUiAutomationManager.getRelevantEventTypes()
+ : 0;
+ return relevantEventTypes;
+ }
+
+ private static boolean isClientInPackageWhitelist(
+ @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
+ if (serviceInfo == null) return false;
+
+ String[] clientPackages = client.mPackageNames;
+ boolean result = ArrayUtils.isEmpty(serviceInfo.packageNames);
+ if (!result && clientPackages != null) {
+ for (String packageName : clientPackages) {
+ if (ArrayUtils.contains(serviceInfo.packageNames, packageName)) {
+ result = true;
+ break;
+ }
+ }
+ }
+ if (!result) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Dropping events: "
+ + Arrays.toString(clientPackages) + " -> "
+ + serviceInfo.getComponentName().flattenToShortString()
+ + " due to not being in package whitelist "
+ + Arrays.toString(serviceInfo.packageNames));
+ }
+ }
+
+ return result;
}
private void broadcastToClients(
- UserState userState, Consumer<IAccessibilityManagerClient> clientAction) {
- mGlobalClients.broadcast(clientAction);
- userState.mUserClients.broadcast(clientAction);
+ UserState userState, Consumer<Client> clientAction) {
+ mGlobalClients.broadcastForEachCookie(clientAction);
+ userState.mUserClients.broadcastForEachCookie(clientAction);
}
private void unbindAllServicesLocked(UserState userState) {
@@ -2156,6 +2194,7 @@
* permission to write secure settings, since someone with that permission can enable
* accessibility services themselves.
*/
+ @Override
public void performAccessibilityShortcut() {
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
&& (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -2445,13 +2484,8 @@
synchronized (mLock) {
userState = getUserStateLocked(userId);
}
- broadcastToClients(userState, (client) -> {
- try {
- client.setRelevantEventTypes(relevantEventTypes);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
+ broadcastToClients(userState, ignoreRemoteException(
+ client -> client.mCallback.setRelevantEventTypes(relevantEventTypes)));
} break;
case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: {
@@ -2500,30 +2534,14 @@
private void sendStateToClients(int clientState,
RemoteCallbackList<IAccessibilityManagerClient> clients) {
- clients.broadcast((client) -> {
- try {
- client.setState(clientState);
- } catch (RemoteException re) {
- /* ignore */
- }
- });
+ clients.broadcast(ignoreRemoteException(
+ client -> client.setState(clientState)));
}
private void notifyClientsOfServicesStateChange(
RemoteCallbackList<IAccessibilityManagerClient> clients) {
- try {
- final int userClientCount = clients.beginBroadcast();
- for (int i = 0; i < userClientCount; i++) {
- IAccessibilityManagerClient client = clients.getBroadcastItem(i);
- try {
- client.notifyServicesStateChanged();
- } catch (RemoteException re) {
- /* ignore */
- }
- }
- } finally {
- clients.finishBroadcast();
- }
+ clients.broadcast(ignoreRemoteException(
+ client -> client.notifyServicesStateChanged()));
}
}
@@ -3335,20 +3353,20 @@
}
public boolean canGetAccessibilityNodeInfoLocked(
- AccessibilityClientConnection service, int windowId) {
+ AbstractAccessibilityServiceConnection service, int windowId) {
return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId);
}
- public boolean canRetrieveWindowsLocked(AccessibilityClientConnection service) {
+ public boolean canRetrieveWindowsLocked(AbstractAccessibilityServiceConnection service) {
return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows;
}
- public boolean canRetrieveWindowContentLocked(AccessibilityClientConnection service) {
+ public boolean canRetrieveWindowContentLocked(AbstractAccessibilityServiceConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
}
- public boolean canControlMagnification(AccessibilityClientConnection service) {
+ public boolean canControlMagnification(AbstractAccessibilityServiceConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
}
@@ -3445,7 +3463,7 @@
final int windowCount = mWindows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindows.get(i);
- if (window.inPictureInPicture()) {
+ if (window.isInPictureInPictureMode()) {
return window;
}
}
@@ -3476,13 +3494,26 @@
}
}
+ /** Represents an {@link AccessibilityManager} */
+ class Client {
+ final IAccessibilityManagerClient mCallback;
+ final String[] mPackageNames;
+ int mLastSentRelevantEventTypes;
+
+ private Client(IAccessibilityManagerClient callback, int clientUid, UserState userState) {
+ mCallback = callback;
+ mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+ mLastSentRelevantEventTypes = computeRelevantEventTypes(userState, this);
+ }
+ }
+
public class UserState {
public final int mUserId;
// Non-transient state.
public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
- new RemoteCallbackList<>();
+ new RemoteCallbackList<>();
public final SparseArray<RemoteAccessibilityConnection> mInteractionConnections =
new SparseArray<>();
@@ -3494,8 +3525,6 @@
public final CopyOnWriteArrayList<AccessibilityServiceConnection> mBoundServices =
new CopyOnWriteArrayList<>();
- public int mLastSentRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-
public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
new HashMap<>();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 9cafa1e..5f6efb6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -19,9 +19,7 @@
import static android.provider.Settings.Secure.SHOW_MODE_AUTO;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
-import android.accessibilityservice.IAccessibilityServiceConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -39,7 +37,6 @@
import com.android.server.wm.WindowManagerInternal;
import java.lang.ref.WeakReference;
-import java.util.List;
import java.util.Set;
/**
@@ -50,7 +47,7 @@
* passed to the service it represents as soon it is bound. It also serves as the
* connection for the service.
*/
-class AccessibilityServiceConnection extends AccessibilityClientConnection {
+class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
private static final String LOG_TAG = "AccessibilityServiceConnection";
/*
Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 56a9534..ed3b3e7 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -18,6 +18,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.Nullable;
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
@@ -46,7 +47,7 @@
private AccessibilityServiceInfo mUiAutomationServiceInfo;
- private AccessibilityClientConnection.SystemSupport mSystemSupport;
+ private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
private int mUiAutomationFlags;
@@ -78,7 +79,7 @@
Context context, AccessibilityServiceInfo accessibilityServiceInfo,
int id, Handler mainHandler, Object lock,
AccessibilityManagerService.SecurityPolicy securityPolicy,
- AccessibilityClientConnection.SystemSupport systemSupport,
+ AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
GlobalActionPerformer globalActionPerfomer, int flags) {
accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
@@ -157,6 +158,17 @@
return mUiAutomationService.mEventTypes;
}
+ int getRelevantEventTypes() {
+ if (mUiAutomationService == null) return 0;
+ return mUiAutomationService.getRelevantEventTypes();
+ }
+
+ @Nullable
+ AccessibilityServiceInfo getServiceInfo() {
+ if (mUiAutomationService == null) return null;
+ return mUiAutomationService.getServiceInfo();
+ }
+
void dumpUiAutomationService(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (mUiAutomationService != null) {
mUiAutomationService.dump(fd, pw, args);
@@ -176,7 +188,7 @@
mSystemSupport.onClientChange(false);
}
- private class UiAutomationService extends AccessibilityClientConnection {
+ private class UiAutomationService extends AbstractAccessibilityServiceConnection {
private final Handler mMainHandler;
UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo,
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index f185443..fbdb183 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -483,6 +483,7 @@
description.currentDestinationString = currentDestinationString;
description.dataManagementIntent = dataManagementIntent;
description.dataManagementLabel = dataManagementLabel;
+ Slog.d(TAG, "Transport " + name + " updated its attributes");
}
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index a764808..d3ab125 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -988,12 +988,6 @@
sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
mUidFdTagger.tag(sockFd, callingUid);
- if (port != 0) {
- Log.v(TAG, "Binding to port " + port);
- Os.bind(sockFd, INADDR_ANY, port);
- } else {
- port = bindToRandomPort(sockFd);
- }
// This code is common to both the unspecified and specified port cases
Os.setsockoptInt(
sockFd,
@@ -1001,6 +995,14 @@
OsConstants.UDP_ENCAP,
OsConstants.UDP_ENCAP_ESPINUDP);
+ mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
+ if (port != 0) {
+ Log.v(TAG, "Binding to port " + port);
+ Os.bind(sockFd, INADDR_ANY, port);
+ } else {
+ port = bindToRandomPort(sockFd);
+ }
+
userRecord.mEncapSocketRecords.put(
resourceId,
new RefcountedResource<EncapSocketRecord>(
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0ffc779..31aea63 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1406,7 +1406,7 @@
mLocalUnlockedUsers.put(userId, true);
}
if (userId < 1) return;
- syncSharedAccounts(userId);
+ mHandler.post(() -> syncSharedAccounts(userId));
}
private void syncSharedAccounts(int userId) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2ca4fdb..7dfde56 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14146,8 +14146,7 @@
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord proc = mLruProcesses.get(i);
if (proc.notCachedSinceIdle) {
- if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP_SLEEPING
- && proc.setProcState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ if (proc.setProcState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
&& proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
if (doKilling && proc.initialIdlePss != 0
&& proc.lastPss > ((proc.initialIdlePss*3)/2)) {
@@ -21692,7 +21691,7 @@
int procState;
boolean foregroundActivities = false;
mTmpBroadcastQueue.clear();
- if (app == TOP_APP) {
+ if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
@@ -21728,6 +21727,13 @@
procState = ActivityManager.PROCESS_STATE_SERVICE;
if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making exec-service: " + app);
//Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
+ } else if (app == TOP_APP) {
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ app.adjType = "top-sleeping";
+ foregroundActivities = true;
+ procState = PROCESS_STATE_CUR_TOP;
+ if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making top: " + app);
} else {
// As far as we know the process is empty. We may change our mind later.
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
@@ -23176,7 +23182,7 @@
if (app.curProcState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
isInteraction = true;
app.fgInteractionTime = 0;
- } else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ } else if (app.curProcState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
if (app.fgInteractionTime == 0) {
app.fgInteractionTime = nowElapsed;
isInteraction = false;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c9c26ef1..ab5d64c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -364,9 +364,6 @@
case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
procState = "FGS ";
break;
- case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
- procState = "TPSL";
- break;
case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
procState = "IMPF";
break;
@@ -385,6 +382,9 @@
case ActivityManager.PROCESS_STATE_RECEIVER:
procState = "RCVR";
break;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ procState = "TPSL";
+ break;
case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
procState = "HVY ";
break;
@@ -485,13 +485,13 @@
PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP
PROC_MEM_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_RECEIVER
+ PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_HOME
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
@@ -507,14 +507,14 @@
PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+ PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -529,14 +529,14 @@
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
- PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+ PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -549,15 +549,15 @@
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
@@ -573,14 +573,14 @@
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
- PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
- PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+ PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+ PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 7f88663..26edf62 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -79,7 +79,8 @@
private final Context mContext;
- private final ContextHubInfo[] mContextHubInfo;
+ private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+ private final List<ContextHubInfo> mContextHubInfoList;
private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
new RemoteCallbackList<>();
@@ -141,8 +142,9 @@
if (mContextHubProxy == null) {
mTransactionManager = null;
mClientManager = null;
- mDefaultClientMap = Collections.EMPTY_MAP;
- mContextHubInfo = new ContextHubInfo[0];
+ mDefaultClientMap = Collections.emptyMap();
+ mContextHubIdToInfoMap = Collections.emptyMap();
+ mContextHubInfoList = Collections.emptyList();
return;
}
@@ -157,20 +159,16 @@
Log.e(TAG, "RemoteException while getting Context Hub info", e);
hubList = Collections.emptyList();
}
- mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+ mContextHubIdToInfoMap = Collections.unmodifiableMap(
+ ContextHubServiceUtil.createContextHubInfoMap(hubList));
+ mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
- for (ContextHubInfo contextHubInfo : mContextHubInfo) {
- int contextHubId = contextHubInfo.getId();
-
+ for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
IContextHubClient client = mClientManager.registerClient(
createDefaultClientCallback(contextHubId), contextHubId);
defaultClientMap.put(contextHubId, client);
- }
- mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
- for (ContextHubInfo contextHubInfo : mContextHubInfo) {
- int contextHubId = contextHubInfo.getId();
try {
mContextHubProxy.registerCallback(
contextHubId, new ContextHubServiceCallback(contextHubId));
@@ -178,18 +176,12 @@
Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
+ contextHubId + ")", e);
}
- }
- // Do a query to initialize the service cache list of nanoapps
- // TODO(b/69270990): Remove this when old API is deprecated
- for (ContextHubInfo contextHubInfo : mContextHubInfo) {
- queryNanoAppsInternal(contextHubInfo.getId());
+ // Do a query to initialize the service cache list of nanoapps
+ // TODO(b/69270990): Remove this when old API is deprecated
+ queryNanoAppsInternal(contextHubId);
}
-
- for (int i = 0; i < mContextHubInfo.length; i++) {
- Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
- + ", name: " + mContextHubInfo[i].getName());
- }
+ mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
}
/**
@@ -267,23 +259,29 @@
@Override
public int[] getContextHubHandles() throws RemoteException {
checkPermissions();
- int[] returnArray = new int[mContextHubInfo.length];
-
- for (int i = 0; i < returnArray.length; ++i) {
- returnArray[i] = i;
- }
- return returnArray;
+ return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet());
}
@Override
- public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException {
+ public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
checkPermissions();
- if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
- Log.e(TAG, "Invalid context hub handle " + contextHubId);
- return null; // null means fail
+ if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) {
+ Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo");
+ return null;
}
- return mContextHubInfo[contextHubId];
+ return mContextHubIdToInfoMap.get(contextHubHandle);
+ }
+
+ /**
+ * Returns a List of ContextHubInfo object describing the available hubs.
+ *
+ * @return the List of ContextHubInfo objects
+ */
+ @Override
+ public List<ContextHubInfo> getContextHubs() throws RemoteException {
+ checkPermissions();
+ return mContextHubInfoList;
}
/**
@@ -347,28 +345,27 @@
}
@Override
- public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException {
+ public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException {
checkPermissions();
if (mContextHubProxy == null) {
return -1;
}
-
- if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
- Log.e(TAG, "Invalid contextHubhandle " + contextHubId);
+ if (!isValidContextHubId(contextHubHandle)) {
+ Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in loadNanoApp");
return -1;
}
- if (app == null) {
- Log.e(TAG, "Invalid null app");
+ if (nanoApp == null) {
+ Log.e(TAG, "NanoApp cannot be null in loadNanoApp");
return -1;
}
// Create an internal IContextHubTransactionCallback for the old API clients
- NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary());
+ NanoAppBinary nanoAppBinary = new NanoAppBinary(nanoApp.getAppBinary());
IContextHubTransactionCallback onCompleteCallback =
- createLoadTransactionCallback(contextHubId, nanoAppBinary);
+ createLoadTransactionCallback(contextHubHandle, nanoAppBinary);
ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction(
- contextHubId, nanoAppBinary, onCompleteCallback);
+ contextHubHandle, nanoAppBinary, onCompleteCallback);
mTransactionManager.addTransaction(transaction);
return 0;
@@ -384,7 +381,7 @@
NanoAppInstanceInfo info =
mNanoAppStateManager.getNanoAppInstanceInfo(nanoAppHandle);
if (info == null) {
- Log.e(TAG, "Cannot find nanoapp with handle " + nanoAppHandle);
+ Log.e(TAG, "Invalid nanoapp handle " + nanoAppHandle + " in unloadNanoApp");
return -1;
}
@@ -407,7 +404,8 @@
}
@Override
- public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) throws RemoteException {
+ public int[] findNanoAppOnHub(
+ int contextHubHandle, NanoAppFilter filter) throws RemoteException {
checkPermissions();
ArrayList<Integer> foundInstances = new ArrayList<>();
@@ -450,29 +448,29 @@
}
@Override
- public int sendMessage(
- int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException {
+ public int sendMessage(int contextHubHandle, int nanoAppHandle, ContextHubMessage msg)
+ throws RemoteException {
checkPermissions();
if (mContextHubProxy == null) {
return -1;
}
if (msg == null) {
- Log.e(TAG, "ContextHubMessage cannot be null");
+ Log.e(TAG, "ContextHubMessage cannot be null in sendMessage");
return -1;
}
if (msg.getData() == null) {
- Log.e(TAG, "ContextHubMessage message body cannot be null");
+ Log.e(TAG, "ContextHubMessage message body cannot be null in sendMessage");
return -1;
}
- if (!mDefaultClientMap.containsKey(hubHandle)) {
- Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
+ if (!isValidContextHubId(contextHubHandle)) {
+ Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in sendMessage");
return -1;
}
boolean success = false;
if (nanoAppHandle == OS_APP_INSTANCE) {
if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
- success = (queryNanoAppsInternal(hubHandle) == Result.OK);
+ success = (queryNanoAppsInternal(contextHubHandle) == Result.OK);
} else {
Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
}
@@ -482,7 +480,7 @@
NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
info.getAppId(), msg.getMsgType(), msg.getData());
- IContextHubClient client = mDefaultClientMap.get(hubHandle);
+ IContextHubClient client = mDefaultClientMap.get(contextHubHandle);
success = (client.sendMessageToNanoApp(message) ==
ContextHubTransaction.TRANSACTION_SUCCESS);
} else {
@@ -595,13 +593,7 @@
* @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
*/
private boolean isValidContextHubId(int contextHubId) {
- for (ContextHubInfo hubInfo : mContextHubInfo) {
- if (hubInfo.getId() == contextHubId) {
- return true;
- }
- }
-
- return false;
+ return mContextHubIdToInfoMap.containsKey(contextHubId);
}
/**
@@ -762,8 +754,8 @@
pw.println("");
// dump ContextHubInfo
pw.println("=================== CONTEXT HUBS ====================");
- for (int i = 0; i < mContextHubInfo.length; i++) {
- pw.println("Handle " + i + " : " + mContextHubInfo[i].toString());
+ for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
+ pw.println(hubInfo);
}
pw.println("");
pw.println("=================== NANOAPPS ====================");
@@ -779,7 +771,8 @@
ContextHubServiceUtil.checkPermissions(mContext);
}
- private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
+ private int onMessageReceiptOldApi(
+ int msgType, int contextHubHandle, int appInstance, byte[] data) {
if (data == null) {
return -1;
}
@@ -787,7 +780,8 @@
int msgVersion = 0;
int callbacksCount = mCallbacksList.beginBroadcast();
Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " +
- hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
+ contextHubHandle + ", appInstance " + appInstance + ", callBackCount "
+ + callbacksCount);
if (callbacksCount < 1) {
Log.v(TAG, "No message callbacks registered.");
@@ -798,7 +792,7 @@
for (int i = 0; i < callbacksCount; ++i) {
IContextHubCallback callback = mCallbacksList.getBroadcastItem(i);
try {
- callback.onMessageReceipt(hubHandle, appInstance, msg);
+ callback.onMessageReceipt(contextHubHandle, appInstance, msg);
} catch (RemoteException e) {
Log.i(TAG, "Exception (" + e + ") calling remote callback (" + callback + ").");
continue;
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index 6faeb72..7a57dd3 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -30,6 +30,9 @@
import android.hardware.location.NanoAppState;
import android.util.Log;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
@@ -43,19 +46,20 @@
+ HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
/**
- * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
+ * Creates a ConcurrentHashMap of the Context Hub ID to the ContextHubInfo object given an
+ * ArrayList of HIDL ContextHub objects.
*
* @param hubList the ContextHub ArrayList
- * @return the ContextHubInfo array
+ * @return the HashMap object
*/
/* package */
- static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
- ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
- for (int i = 0; i < hubList.size(); i++) {
- contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
+ static HashMap<Integer, ContextHubInfo> createContextHubInfoMap(List<ContextHub> hubList) {
+ HashMap<Integer, ContextHubInfo> contextHubIdToInfoMap = new HashMap<>();
+ for (ContextHub contextHub : hubList) {
+ contextHubIdToInfoMap.put(contextHub.hubId, new ContextHubInfo(contextHub));
}
- return contextHubInfoList;
+ return contextHubIdToInfoMap;
}
/**
@@ -90,6 +94,22 @@
}
/**
+ * Creates a primitive integer array given a Collection<Integer>.
+ * @param collection the collection to iterate
+ * @return the primitive integer array
+ */
+ static int[] createPrimitiveIntArray(Collection<Integer> collection) {
+ int[] primitiveArray = new int[collection.size()];
+
+ int i = 0;
+ for (int contextHubId : collection) {
+ primitiveArray[i++] = contextHubId;
+ }
+
+ return primitiveArray;
+ }
+
+ /**
* Generates the Context Hub HAL's NanoAppBinary object from the client-facing
* android.hardware.location.NanoAppBinary object.
*
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
new file mode 100644
index 0000000..9a4d051
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.security.keystore.AndroidKeyStoreProvider;
+
+import java.security.KeyStoreException;
+import java.security.NoSuchProviderException;
+
+public interface AndroidKeyStoreFactory {
+ KeyStoreProxy getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException;
+
+ class Impl implements AndroidKeyStoreFactory {
+ @Override
+ public KeyStoreProxy getKeyStoreForUid(int uid)
+ throws KeyStoreException, NoSuchProviderException {
+ return new KeyStoreProxyImpl(AndroidKeyStoreProvider.getKeyStoreForUid(uid));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
index 7c9b395..8103177 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
@@ -40,4 +40,7 @@
/** @see KeyStore#setEntry(String, KeyStore.Entry, KeyStore.ProtectionParameter) */
void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)
throws KeyStoreException;
+
+ /** @see KeyStore#deleteEntry(String) */
+ void deleteEntry(String alias) throws KeyStoreException;
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
index ceee381..59132da 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
@@ -52,4 +52,9 @@
throws KeyStoreException {
mKeyStore.setEntry(alias, entry, protParam);
}
+
+ @Override
+ public void deleteEntry(String alias) throws KeyStoreException {
+ mKeyStore.deleteEntry(alias);
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 37aeb3a..25428e7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -20,10 +20,13 @@
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
+import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
@@ -39,6 +42,7 @@
*/
public class KeySyncUtils {
+ private static final String PUBLIC_KEY_FACTORY_ALGORITHM = "EC";
private static final String RECOVERY_KEY_ALGORITHM = "AES";
private static final int RECOVERY_KEY_SIZE_BITS = 256;
@@ -237,6 +241,21 @@
}
/**
+ * Deserializes a X509 public key.
+ *
+ * @param key The bytes of the key.
+ * @return The key.
+ * @throws NoSuchAlgorithmException if the public key algorithm is unavailable.
+ * @throws InvalidKeySpecException if the bytes of the key are not a valid key.
+ */
+ public static PublicKey deserializePublicKey(byte[] key)
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ KeyFactory keyFactory = KeyFactory.getInstance(PUBLIC_KEY_FACTORY_ALGORITHM);
+ X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(key);
+ return keyFactory.generatePublic(publicKeySpec);
+ }
+
+ /**
* Returns the concatenation of all the given {@code arrays}.
*/
@VisibleForTesting
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
new file mode 100644
index 0000000..0f17294
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * In memory storage for listeners to be notified when new recovery snapshot is available.
+ * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent}
+ * class.
+ *
+ * @hide
+ */
+public class ListenersStorage {
+ private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>();
+
+ private static final ListenersStorage mInstance = new ListenersStorage();
+ public static ListenersStorage getInstance() {
+ return mInstance;
+ }
+
+ /**
+ * Sets new listener for the recovery agent, identified by {@code uid}
+ *
+ * @param recoveryAgentUid uid
+ * @param intent PendingIntent which will be triggered than new snapshot is available.
+ */
+ public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) {
+ mAgentIntents.put(recoveryAgentUid, intent);
+ }
+
+ /**
+ * Notifies recovery agent, that new snapshot is available.
+ * Does nothing if a listener was not registered.
+ *
+ * @param recoveryAgentUid uid.
+ */
+ public void recoverySnapshotAvailable(int recoveryAgentUid) {
+ PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
+ if (intent != null) {
+ try {
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ // Ignore - sending intent is not allowed.
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 074c596..24f3f65 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -18,16 +18,14 @@
import android.app.KeyguardManager;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Environment;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
-import java.io.File;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -64,8 +62,6 @@
private static final String KEY_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
- private static final String SHARED_PREFS_KEY_GENERATION_ID = "generationId";
- private static final String SHARED_PREFS_PATH = "/system/recoverablekeystore/platform_keys.xml";
private static final String KEY_ALIAS_PREFIX =
"com.android.server.locksettings.recoverablekeystore/platform/";
private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
@@ -74,7 +70,7 @@
private final Context mContext;
private final KeyStoreProxy mKeyStore;
- private final SharedPreferences mSharedPreferences;
+ private final RecoverableKeyStoreDb mDatabase;
private final int mUserId;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
@@ -92,17 +88,14 @@
*
* @hide
*/
- public static PlatformKeyManager getInstance(Context context, int userId)
+ public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId)
throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
context = context.getApplicationContext();
- File sharedPreferencesFile = new File(
- Environment.getDataDirectory().getAbsoluteFile(), SHARED_PREFS_PATH);
- sharedPreferencesFile.mkdirs();
PlatformKeyManager keyManager = new PlatformKeyManager(
userId,
context,
new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
- context.getSharedPreferences(sharedPreferencesFile, Context.MODE_PRIVATE));
+ database);
keyManager.init();
return keyManager;
}
@@ -112,11 +105,11 @@
int userId,
Context context,
KeyStoreProxy keyStore,
- SharedPreferences sharedPreferences) {
+ RecoverableKeyStoreDb database) {
mUserId = userId;
mKeyStore = keyStore;
mContext = context;
- mSharedPreferences = sharedPreferences;
+ mDatabase = database;
}
/**
@@ -127,7 +120,11 @@
* @hide
*/
public int getGenerationId() {
- return mSharedPreferences.getInt(getGenerationIdKey(), 1);
+ int generationId = mDatabase.getPlatformKeyGenerationId(mUserId);
+ if (generationId == -1) {
+ return 1;
+ }
+ return generationId;
}
/**
@@ -150,9 +147,9 @@
* @hide
*/
public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
- int generationId = getGenerationId();
- generateAndLoadKey(generationId + 1);
- setGenerationId(generationId + 1);
+ int nextId = getGenerationId() + 1;
+ generateAndLoadKey(nextId);
+ setGenerationId(nextId);
}
/**
@@ -252,14 +249,7 @@
* Sets the current generation ID to {@code generationId}.
*/
private void setGenerationId(int generationId) {
- mSharedPreferences.edit().putInt(getGenerationIdKey(), generationId).commit();
- }
-
- /**
- * Returns the current user's generation ID key in the shared preferences.
- */
- private String getGenerationIdKey() {
- return SHARED_PREFS_KEY_GENERATION_ID + "/" + mUserId;
+ mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
}
/**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
index 54deec2..d50a736 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,16 +16,18 @@
package com.android.server.locksettings.recoverablekeystore;
-import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.util.Log;
-import java.io.IOException;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
import java.security.InvalidKeyException;
+import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
+import java.security.NoSuchProviderException;
+import java.util.Locale;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -42,39 +44,39 @@
*/
public class RecoverableKeyGenerator {
private static final String TAG = "RecoverableKeyGenerator";
+
+ private static final int RESULT_CANNOT_INSERT_ROW = -1;
private static final String KEY_GENERATOR_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
/**
* A new {@link RecoverableKeyGenerator} instance.
*
- * @param platformKey Secret key used to wrap generated keys before persisting to disk.
- * @param recoverableKeyStorage Class that manages persisting wrapped keys to disk.
* @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is
* unavailable. Should never happen.
*
* @hide
*/
- public static RecoverableKeyGenerator newInstance(
- PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage)
+ public static RecoverableKeyGenerator newInstance(RecoverableKeyStoreDb database)
throws NoSuchAlgorithmException {
// NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
// material, so that it can be synced to disk in encrypted form.
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
- return new RecoverableKeyGenerator(keyGenerator, platformKey, recoverableKeyStorage);
+ return new RecoverableKeyGenerator(
+ keyGenerator, database, new AndroidKeyStoreFactory.Impl());
}
private final KeyGenerator mKeyGenerator;
- private final RecoverableKeyStorage mRecoverableKeyStorage;
- private final PlatformEncryptionKey mPlatformKey;
+ private final RecoverableKeyStoreDb mDatabase;
+ private final AndroidKeyStoreFactory mAndroidKeyStoreFactory;
private RecoverableKeyGenerator(
KeyGenerator keyGenerator,
- PlatformEncryptionKey platformKey,
- RecoverableKeyStorage recoverableKeyStorage) {
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ AndroidKeyStoreFactory androidKeyStoreFactory) {
mKeyGenerator = keyGenerator;
- mRecoverableKeyStorage = recoverableKeyStorage;
- mPlatformKey = platformKey;
+ mAndroidKeyStoreFactory = androidKeyStoreFactory;
+ mDatabase = recoverableKeyStoreDb;
}
/**
@@ -84,50 +86,70 @@
* persisted to disk so that it can be synced remotely, and then recovered on another device.
* The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
*
- * <p>The key handle returned to the caller is a reference to the AndroidKeyStore key,
- * meaning that the caller is never able to access the raw, unencrypted key.
- *
+ * @param platformKey The user's platform key, with which to wrap the generated key.
+ * @param uid The uid of the application that will own the key.
* @param alias The alias by which the key will be known in AndroidKeyStore.
+ * @throws RecoverableKeyStorageException if there is some error persisting the key either to
+ * the AndroidKeyStore or the database.
+ * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
* @throws InvalidKeyException if the platform key cannot be used to wrap keys.
- * @throws IOException if there was an issue writing the wrapped key to the wrapped key store.
- * @throws UnrecoverableEntryException if could not retrieve key after putting it in
- * AndroidKeyStore. This should not happen.
- * @return A handle to the AndroidKeyStore key.
*
* @hide
*/
- public SecretKey generateAndStoreKey(String alias) throws KeyStoreException,
- InvalidKeyException, IOException, UnrecoverableEntryException {
+ public void generateAndStoreKey(PlatformEncryptionKey platformKey, int uid, String alias)
+ throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
mKeyGenerator.init(KEY_SIZE_BITS);
SecretKey key = mKeyGenerator.generateKey();
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- alias,
- key,
- new KeyProtection.Builder(
- KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build());
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(mPlatformKey, key);
+ KeyStoreProxy keyStore;
try {
+ keyStore = mAndroidKeyStoreFactory.getKeyStoreForUid(uid);
+ } catch (NoSuchProviderException e) {
+ throw new RecoverableKeyStorageException(
+ "Impossible: AndroidKeyStore provider did not exist", e);
+ } catch (KeyStoreException e) {
+ throw new RecoverableKeyStorageException(
+ "Could not load AndroidKeyStore for " + uid, e);
+ }
+
+ try {
+ keyStore.setEntry(
+ alias,
+ new KeyStore.SecretKeyEntry(key),
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ } catch (KeyStoreException e) {
+ throw new RecoverableKeyStorageException(
+ "Failed to load (%d, %s) into AndroidKeyStore", e);
+ }
+
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
+ try {
// Keep raw key material in memory for minimum possible time.
key.destroy();
} catch (DestroyFailedException e) {
Log.w(TAG, "Could not destroy SecretKey.");
}
+ long result = mDatabase.insertKey(uid, alias, wrappedKey);
- mRecoverableKeyStorage.persistToDisk(alias, wrappedKey);
+ if (result == RESULT_CANNOT_INSERT_ROW) {
+ // Attempt to clean up
+ try {
+ keyStore.deleteEntry(alias);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, String.format(Locale.US,
+ "Could not delete recoverable key (%d, %s) from "
+ + "AndroidKeyStore after error writing to database.", uid, alias),
+ e);
+ }
- try {
- // Reload from the keystore, so that the caller is only provided with the handle of the
- // key, not the raw key material.
- return mRecoverableKeyStorage.loadFromAndroidKeyStore(alias);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(
- "Impossible: NoSuchAlgorithmException when attempting to retrieve a key "
- + "that has only just been stored in AndroidKeyStore.", e);
+ throw new RecoverableKeyStorageException(
+ String.format(
+ Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
deleted file mode 100644
index 6a189ef..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Stores wrapped keys to disk, so they can be synced on the next screen unlock event.
- *
- * @hide
- */
-public interface RecoverableKeyStorage {
-
- /**
- * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
- *
- * @throws IOException if an error occurred writing to disk.
- *
- * @hide
- */
- void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException;
-
- /**
- * Imports {@code key} into AndroidKeyStore, keyed by the application's uid and
- * the {@code alias}.
- *
- * @param alias The alias of the key.
- * @param key The key.
- * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
- * Cipher modes, whether for encrpyt/decrypt or signing, etc.)
- * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
- *
- * @hide
- */
- void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection) throws
- KeyStoreException;
-
- /**
- * Loads a key handle from AndroidKeyStore.
- *
- * @param alias Alias of the key to load.
- * @return The key handle.
- * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
- *
- * @hide
- */
- SecretKey loadFromAndroidKeyStore(String alias) throws KeyStoreException,
- NoSuchAlgorithmException,
- UnrecoverableEntryException;
-
- /**
- * Removes the entry with the given {@code alias} from AndroidKeyStore.
- *
- * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
- *
- * @hide
- */
- void removeFromAndroidKeyStore(String alias) throws KeyStoreException;
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
new file mode 100644
index 0000000..f9d28f1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+/**
+ * Error thrown when there was a problem writing or reading recoverable key information to or from
+ * storage.
+ *
+ * <p>Storage is typically
+ * {@link com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb} or
+ * AndroidKeyStore.
+ */
+public class RecoverableKeyStorageException extends Exception {
+ public RecoverableKeyStorageException(String message) {
+ super(message);
+ }
+
+ public RecoverableKeyStorageException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
deleted file mode 100644
index d4dede1..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.AndroidKeyStoreProvider;
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link RecoverableKeyStorage} for a specific application.
- *
- * <p>Persists wrapped keys to disk, and loads raw keys into AndroidKeyStore.
- *
- * @hide
- */
-public class RecoverableKeyStorageImpl implements RecoverableKeyStorage {
- private final KeyStore mKeyStore;
-
- /**
- * A new instance, storing recoverable keys for the given {@code userId}.
- *
- * @throws KeyStoreException if unable to load AndroidKeyStore.
- * @throws NoSuchProviderException if AndroidKeyStore is not in this version of Android. Should
- * never occur.
- *
- * @hide
- */
- public static RecoverableKeyStorageImpl newInstance(int userId) throws KeyStoreException,
- NoSuchProviderException {
- KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(userId);
- return new RecoverableKeyStorageImpl(keyStore);
- }
-
- private RecoverableKeyStorageImpl(KeyStore keyStore) {
- mKeyStore = keyStore;
- }
-
- /**
- * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
- *
- * @throws IOException if an error occurred writing to disk.
- *
- * @hide
- */
- @Override
- public void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException {
- // TODO(robertberry) Add implementation.
- throw new UnsupportedOperationException();
- }
-
- /**
- * Imports {@code key} into the application's AndroidKeyStore, keyed by {@code alias}.
- *
- * @param alias The alias of the key.
- * @param key The key.
- * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
- * Cipher modes, whether for encrpyt/decrypt or signing, etc.)
- * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection)
- throws KeyStoreException {
- mKeyStore.setEntry(alias, new KeyStore.SecretKeyEntry(key), keyProtection);
- }
-
- /**
- * Loads a key handle from the application's AndroidKeyStore.
- *
- * @param alias Alias of the key to load.
- * @return The key handle.
- * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public SecretKey loadFromAndroidKeyStore(String alias)
- throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
- return ((SecretKey) mKeyStore.getKey(alias, /*password=*/ null));
- }
-
- /**
- * Removes the entry with the given {@code alias} from the application's AndroidKeyStore.
- *
- * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
- *
- * @hide
- */
- @Override
- public void removeFromAndroidKeyStore(String alias) throws KeyStoreException {
- mKeyStore.deleteEntry(alias);
- }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index f87f7fa..48f4626 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -31,7 +31,13 @@
import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -46,7 +52,10 @@
private static final String TAG = "RecoverableKeyStoreManager";
private static RecoverableKeyStoreManager mInstance;
- private Context mContext;
+
+ private final Context mContext;
+ private final RecoverableKeyStoreDb mDatabase;
+ private final RecoverySessionStorage mRecoverySessionStorage;
/**
* Returns a new or existing instance.
@@ -55,14 +64,23 @@
*/
public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
if (mInstance == null) {
- mInstance = new RecoverableKeyStoreManager(mContext);
+ RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext);
+ mInstance = new RecoverableKeyStoreManager(
+ mContext.getApplicationContext(),
+ db,
+ new RecoverySessionStorage());
}
return mInstance;
}
@VisibleForTesting
- RecoverableKeyStoreManager(Context context) {
+ RecoverableKeyStoreManager(
+ Context context,
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ RecoverySessionStorage recoverySessionStorage) {
mContext = context;
+ mDatabase = recoverableKeyStoreDb;
+ mRecoverySessionStorage = recoverySessionStorage;
}
public int initRecoveryService(
@@ -196,7 +214,13 @@
/**
* Initializes recovery session.
*
- * @return recovery claim
+ * @param sessionId A unique ID to identify the recovery session.
+ * @param verifierPublicKey X509-encoded public key.
+ * @param vaultParams Additional params associated with vault.
+ * @param vaultChallenge Challenge issued by vault service.
+ * @param secrets Lock-screen hashes. Should have a single element. TODO: why is this a list?
+ * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
+ *
* @hide
*/
public @NonNull byte[] startRecoverySession(
@@ -208,7 +232,40 @@
int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
- throw new UnsupportedOperationException();
+
+ if (secrets.size() != 1) {
+ // TODO: support multiple secrets
+ throw new RemoteException("Only a single KeyStoreRecoveryMetadata is supported");
+ }
+
+ byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+ byte[] kfHash = secrets.get(0).getSecret();
+ mRecoverySessionStorage.add(
+ userId, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant));
+
+ try {
+ byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
+ PublicKey publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
+ return KeySyncUtils.encryptRecoveryClaim(
+ publicKey,
+ vaultParams,
+ vaultChallenge,
+ thmKfHash,
+ keyClaimant);
+ } catch (NoSuchAlgorithmException e) {
+ // Should never happen: all the algorithms used are required by AOSP implementations.
+ throw new RemoteException(
+ "Missing required algorithm",
+ e,
+ /*enableSuppression=*/ true,
+ /*writeableStackTrace=*/ true);
+ } catch (InvalidKeySpecException | InvalidKeyException e) {
+ throw new RemoteException(
+ "Not a valid X509 key",
+ e,
+ /*enableSuppression=*/ true,
+ /*writeableStackTrace=*/ true);
+ }
}
public void recoverKeys(
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 742cb45..d8a2d31 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -17,23 +17,159 @@
package com.android.server.locksettings.recoverablekeystore;
import android.annotation.Nullable;
-
+import com.android.internal.annotations.VisibleForTesting;
+import java.math.BigInteger;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
-
+import java.security.SecureRandom;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
/**
- * TODO(b/69056040) Add implementation of SecureBox. This is a placeholder so KeySyncUtils compiles.
+ * Implementation of the SecureBox v2 crypto functions.
+ *
+ * <p>Securebox v2 provides a simple interface to perform encryptions by using any of the following
+ * credential types:
+ *
+ * <ul>
+ * <li>A public key owned by the recipient,
+ * <li>A secret shared between the sender and the recipient, or
+ * <li>Both a recipient's public key and a shared secret.
+ * </ul>
*
* @hide
*/
public class SecureBox {
+
+ private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
+ private static final byte[] HKDF_SALT =
+ concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
+ private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
+ "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
+ "SHARED HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] CONSTANT_01 = {(byte) 0x01};
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ private static final byte EC_PUBLIC_KEY_PREFIX = (byte) 0x04;
+
+ private static final String CIPHER_ALG = "AES";
+ private static final String EC_ALG = "EC";
+ private static final String EC_P256_COMMON_NAME = "secp256r1";
+ private static final String EC_P256_OPENSSL_NAME = "prime256v1";
+ private static final String ENC_ALG = "AES/GCM/NoPadding";
+ private static final String KA_ALG = "ECDH";
+ private static final String MAC_ALG = "HmacSHA256";
+
+ private static final int EC_COORDINATE_LEN_BYTES = 32;
+ private static final int EC_PUBLIC_KEY_LEN_BYTES = 2 * EC_COORDINATE_LEN_BYTES + 1;
+ private static final int GCM_NONCE_LEN_BYTES = 12;
+ private static final int GCM_KEY_LEN_BYTES = 16;
+ private static final int GCM_TAG_LEN_BYTES = 16;
+
+ private static final BigInteger BIG_INT_02 = BigInteger.valueOf(2);
+
+ private enum AesGcmOperation {
+ ENCRYPT,
+ DECRYPT
+ }
+
+ // Parameters for the NIST P-256 curve y^2 = x^3 + ax + b (mod p)
+ private static final BigInteger EC_PARAM_P =
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);
+ private static final BigInteger EC_PARAM_A = EC_PARAM_P.subtract(new BigInteger("3"));
+ private static final BigInteger EC_PARAM_B =
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
+
+ @VisibleForTesting static final ECParameterSpec EC_PARAM_SPEC;
+
+ static {
+ EllipticCurve curveSpec =
+ new EllipticCurve(new ECFieldFp(EC_PARAM_P), EC_PARAM_A, EC_PARAM_B);
+ ECPoint generator =
+ new ECPoint(
+ new BigInteger(
+ "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
+ 16),
+ new BigInteger(
+ "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5",
+ 16));
+ BigInteger generatorOrder =
+ new BigInteger(
+ "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);
+ EC_PARAM_SPEC = new ECParameterSpec(curveSpec, generator, generatorOrder, /* cofactor */ 1);
+ }
+
+ private SecureBox() {}
+
/**
- * TODO(b/69056040) Add implementation of encrypt.
+ * Randomly generates a public-key pair that can be used for the functions {@link #encrypt} and
+ * {@link #decrypt}.
*
+ * @return the randomly generated public-key pair
+ * @throws NoSuchAlgorithmException if the underlying crypto algorithm is not supported
+ * @hide
+ */
+ public static KeyPair genKeyPair() throws NoSuchAlgorithmException {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_ALG);
+ try {
+ // Try using the OpenSSL provider first
+ keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME));
+ return keyPairGenerator.generateKeyPair();
+ } catch (InvalidAlgorithmParameterException ex) {
+ // Try another name for NIST P-256
+ }
+ try {
+ keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME));
+ return keyPairGenerator.generateKeyPair();
+ } catch (InvalidAlgorithmParameterException ex) {
+ throw new NoSuchAlgorithmException("Unable to find the NIST P-256 curve", ex);
+ }
+ }
+
+ /**
+ * Encrypts {@code payload} by using {@code theirPublicKey} and/or {@code sharedSecret}. At
+ * least one of {@code theirPublicKey} and {@code sharedSecret} must be non-null, and an empty
+ * {@code sharedSecret} is equivalent to null.
+ *
+ * <p>Note that {@code header} will be authenticated (but not encrypted) together with {@code
+ * payload}, and the same {@code header} has to be provided for {@link #decrypt}.
+ *
+ * @param theirPublicKey the recipient's public key, or null if the payload is to be encrypted
+ * only with the shared secret
+ * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+ * payload is to be encrypted only with the recipient's public key
+ * @param header the data that will be authenticated with {@code payload} but not encrypted, or
+ * null if the data is empty
+ * @param payload the data to be encrypted, or null if the data is empty
+ * @return the encrypted payload
+ * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+ * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
* @hide
*/
public static byte[] encrypt(
@@ -42,12 +178,59 @@
@Nullable byte[] header,
@Nullable byte[] payload)
throws NoSuchAlgorithmException, InvalidKeyException {
- throw new UnsupportedOperationException("Needs to be implemented.");
+ sharedSecret = emptyByteArrayIfNull(sharedSecret);
+ if (theirPublicKey == null && sharedSecret.length == 0) {
+ throw new IllegalArgumentException("Both the public key and shared secret are empty");
+ }
+ header = emptyByteArrayIfNull(header);
+ payload = emptyByteArrayIfNull(payload);
+
+ KeyPair senderKeyPair;
+ byte[] dhSecret;
+ byte[] hkdfInfo;
+ if (theirPublicKey == null) {
+ senderKeyPair = null;
+ dhSecret = EMPTY_BYTE_ARRAY;
+ hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+ } else {
+ senderKeyPair = genKeyPair();
+ dhSecret = dhComputeSecret(senderKeyPair.getPrivate(), theirPublicKey);
+ hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+ }
+
+ byte[] randNonce = genRandomNonce();
+ byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+ SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+ byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
+ if (senderKeyPair == null) {
+ return concat(VERSION, randNonce, ciphertext);
+ } else {
+ return concat(
+ VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
+ }
}
/**
- * TODO(b/69056040) Add implementation of decrypt.
+ * Decrypts {@code encryptedPayload} by using {@code ourPrivateKey} and/or {@code sharedSecret}.
+ * At least one of {@code ourPrivateKey} and {@code sharedSecret} must be non-null, and an empty
+ * {@code sharedSecret} is equivalent to null.
*
+ * <p>Note that {@code header} should be the same data used for {@link #encrypt}, which is
+ * authenticated (but not encrypted) together with {@code payload}; otherwise, an {@code
+ * AEADBadTagException} will be thrown.
+ *
+ * @param ourPrivateKey the recipient's private key, or null if the payload was encrypted only
+ * with the shared secret
+ * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+ * payload was encrypted only with the recipient's public key
+ * @param header the data that was authenticated with the original payload but not encrypted, or
+ * null if the data is empty
+ * @param encryptedPayload the data to be decrypted
+ * @return the original payload that was encrypted
+ * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+ * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
+ * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload}
+ * cannot be validated
* @hide
*/
public static byte[] decrypt(
@@ -56,6 +239,224 @@
@Nullable byte[] header,
byte[] encryptedPayload)
throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
- throw new UnsupportedOperationException("Needs to be implemented.");
+ sharedSecret = emptyByteArrayIfNull(sharedSecret);
+ if (ourPrivateKey == null && sharedSecret.length == 0) {
+ throw new IllegalArgumentException("Both the private key and shared secret are empty");
+ }
+ header = emptyByteArrayIfNull(header);
+ encryptedPayload = emptyByteArrayIfNull(encryptedPayload);
+
+ ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload);
+ byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length);
+ if (!Arrays.equals(version, VERSION)) {
+ throw new IllegalArgumentException("The payload was not encrypted by SecureBox v2");
+ }
+
+ byte[] senderPublicKeyBytes;
+ byte[] dhSecret;
+ byte[] hkdfInfo;
+ if (ourPrivateKey == null) {
+ dhSecret = EMPTY_BYTE_ARRAY;
+ hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+ } else {
+ senderPublicKeyBytes = readEncryptedPayload(ciphertextBuffer, EC_PUBLIC_KEY_LEN_BYTES);
+ dhSecret = dhComputeSecret(ourPrivateKey, decodePublicKey(senderPublicKeyBytes));
+ hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+ }
+
+ byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
+ byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
+ byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+ SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+ return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
+ }
+
+ private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) {
+ byte[] output = new byte[length];
+ try {
+ buffer.get(output);
+ } catch (BufferUnderflowException ex) {
+ throw new IllegalArgumentException("The encrypted payload is too short");
+ }
+ return output;
+ }
+
+ private static byte[] dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ KeyAgreement agreement = KeyAgreement.getInstance(KA_ALG);
+ try {
+ agreement.init(ourPrivateKey);
+ } catch (RuntimeException ex) {
+ // Rethrow the RuntimeException as InvalidKeyException
+ throw new InvalidKeyException(ex);
+ }
+ agreement.doPhase(theirPublicKey, /*lastPhase=*/ true);
+ return agreement.generateSecret();
+ }
+
+ /** Derives a 128-bit AES key. */
+ private static SecretKey hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info)
+ throws NoSuchAlgorithmException {
+ Mac mac = Mac.getInstance(MAC_ALG);
+ try {
+ mac.init(new SecretKeySpec(salt, MAC_ALG));
+ } catch (InvalidKeyException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ byte[] pseudorandomKey = mac.doFinal(secret);
+
+ try {
+ mac.init(new SecretKeySpec(pseudorandomKey, MAC_ALG));
+ } catch (InvalidKeyException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ mac.update(info);
+ // Hashing just one block will yield 256 bits, which is enough to construct the AES key
+ byte[] hkdfOutput = mac.doFinal(CONSTANT_01);
+
+ return new SecretKeySpec(Arrays.copyOf(hkdfOutput, GCM_KEY_LEN_BYTES), CIPHER_ALG);
+ }
+
+ private static byte[] aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ try {
+ return aesGcmInternal(AesGcmOperation.ENCRYPT, key, nonce, plaintext, aad);
+ } catch (AEADBadTagException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static byte[] aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+ return aesGcmInternal(AesGcmOperation.DECRYPT, key, nonce, ciphertext, aad);
+ }
+
+ private static byte[] aesGcmInternal(
+ AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(ENC_ALG);
+ } catch (NoSuchPaddingException ex) {
+ // This should never happen because AES-GCM doesn't use padding
+ throw new RuntimeException(ex);
+ }
+ GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BYTES * 8, nonce);
+ try {
+ if (operation == AesGcmOperation.DECRYPT) {
+ cipher.init(Cipher.DECRYPT_MODE, key, spec);
+ } else {
+ cipher.init(Cipher.ENCRYPT_MODE, key, spec);
+ }
+ } catch (InvalidAlgorithmParameterException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ try {
+ cipher.updateAAD(aad);
+ return cipher.doFinal(text);
+ } catch (AEADBadTagException ex) {
+ // Catch and rethrow AEADBadTagException first because it's a subclass of
+ // BadPaddingException
+ throw ex;
+ } catch (IllegalBlockSizeException | BadPaddingException ex) {
+ // This should never happen because AES-GCM can handle inputs of any length without
+ // padding
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @VisibleForTesting
+ static byte[] encodePublicKey(PublicKey publicKey) {
+ ECPoint point = ((ECPublicKey) publicKey).getW();
+ byte[] x = point.getAffineX().toByteArray();
+ byte[] y = point.getAffineY().toByteArray();
+
+ byte[] output = new byte[EC_PUBLIC_KEY_LEN_BYTES];
+ // The order of arraycopy() is important, because the coordinates may have a one-byte
+ // leading 0 for the sign bit of two's complement form
+ System.arraycopy(y, 0, output, EC_PUBLIC_KEY_LEN_BYTES - y.length, y.length);
+ System.arraycopy(x, 0, output, 1 + EC_COORDINATE_LEN_BYTES - x.length, x.length);
+ output[0] = EC_PUBLIC_KEY_PREFIX;
+ return output;
+ }
+
+ @VisibleForTesting
+ static PublicKey decodePublicKey(byte[] keyBytes)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ BigInteger x =
+ new BigInteger(
+ /*signum=*/ 1,
+ Arrays.copyOfRange(keyBytes, 1, 1 + EC_COORDINATE_LEN_BYTES));
+ BigInteger y =
+ new BigInteger(
+ /*signum=*/ 1,
+ Arrays.copyOfRange(
+ keyBytes, 1 + EC_COORDINATE_LEN_BYTES, EC_PUBLIC_KEY_LEN_BYTES));
+
+ // Checks if the point is indeed on the P-256 curve for security considerations
+ validateEcPoint(x, y);
+
+ KeyFactory keyFactory = KeyFactory.getInstance(EC_ALG);
+ try {
+ return keyFactory.generatePublic(new ECPublicKeySpec(new ECPoint(x, y), EC_PARAM_SPEC));
+ } catch (InvalidKeySpecException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static void validateEcPoint(BigInteger x, BigInteger y) throws InvalidKeyException {
+ if (x.compareTo(EC_PARAM_P) >= 0
+ || y.compareTo(EC_PARAM_P) >= 0
+ || x.signum() == -1
+ || y.signum() == -1) {
+ throw new InvalidKeyException("Point lies outside of the expected curve");
+ }
+
+ // Points on the curve satisfy y^2 = x^3 + ax + b (mod p)
+ BigInteger lhs = y.modPow(BIG_INT_02, EC_PARAM_P);
+ BigInteger rhs =
+ x.modPow(BIG_INT_02, EC_PARAM_P) // x^2
+ .add(EC_PARAM_A) // x^2 + a
+ .mod(EC_PARAM_P) // This will speed up the next multiplication
+ .multiply(x) // (x^2 + a) * x = x^3 + ax
+ .add(EC_PARAM_B) // x^3 + ax + b
+ .mod(EC_PARAM_P);
+ if (!lhs.equals(rhs)) {
+ throw new InvalidKeyException("Point lies outside of the expected curve");
+ }
+ }
+
+ private static byte[] genRandomNonce() throws NoSuchAlgorithmException {
+ byte[] nonce = new byte[GCM_NONCE_LEN_BYTES];
+ new SecureRandom().nextBytes(nonce);
+ return nonce;
+ }
+
+ @VisibleForTesting
+ static byte[] concat(byte[]... inputs) {
+ int length = 0;
+ for (int i = 0; i < inputs.length; i++) {
+ if (inputs[i] == null) {
+ inputs[i] = EMPTY_BYTE_ARRAY;
+ }
+ length += inputs[i].length;
+ }
+
+ byte[] output = new byte[length];
+ int outputPos = 0;
+ for (byte[] input : inputs) {
+ System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length);
+ outputPos += input.length;
+ }
+ return output;
+ }
+
+ private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
+ return input == null ? EMPTY_BYTE_ARRAY : input;
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 7986533..3644d36 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -25,6 +25,7 @@
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
import java.util.HashMap;
import java.util.Locale;
@@ -176,6 +177,52 @@
}
/**
+ * Sets the {@code generationId} of the platform key for the account owned by {@code userId}.
+ *
+ * @return The primary key ID of the relation.
+ */
+ public long setPlatformKeyGenerationId(int userId, int generationId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
+ values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
+ return db.replace(
+ UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
+ }
+
+ /**
+ * Returns the generation ID associated with the platform key of the user with {@code userId}.
+ */
+ public int getPlatformKeyGenerationId(int userId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+ String[] projection = {
+ UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID};
+ String selection =
+ UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+ String[] selectionArguments = {
+ Integer.toString(userId)};
+
+ try (
+ Cursor cursor = db.query(
+ UserMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ if (cursor.getCount() == 0) {
+ return -1;
+ }
+ cursor.moveToFirst();
+ return cursor.getInt(
+ cursor.getColumnIndexOrThrow(
+ UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID));
+ }
+ }
+
+ /**
* Closes all open connections to the database.
*/
public void close() {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index c54d0a6..b6c168f 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -58,4 +58,22 @@
*/
static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";
}
+
+ /**
+ * Recoverable KeyStore metadata for a specific user profile.
+ */
+ static class UserMetadataEntry implements BaseColumns {
+ static final String TABLE_NAME = "user_metadata";
+
+ /**
+ * User ID of the profile.
+ */
+ static final String COLUMN_NAME_USER_ID = "user_id";
+
+ /**
+ * Every time a new platform key is generated for a user, this increments. The platform key
+ * is used to wrap recoverable keys on disk.
+ */
+ static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index e3783c4..6868203 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -5,6 +5,7 @@
import android.database.sqlite.SQLiteOpenHelper;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
/**
* Helper for creating the recoverable key database.
@@ -13,31 +14,44 @@
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "recoverablekeystore.db";
- private static final String SQL_CREATE_ENTRIES =
+ private static final String SQL_CREATE_KEYS_ENTRY =
"CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
+ KeysEntry._ID + " INTEGER PRIMARY KEY,"
- + KeysEntry.COLUMN_NAME_UID + " INTEGER UNIQUE,"
- + KeysEntry.COLUMN_NAME_ALIAS + " TEXT UNIQUE,"
+ + KeysEntry.COLUMN_NAME_UID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
+ KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
+ KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
+ KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
- + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)";
+ + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
+ + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
+ + KeysEntry.COLUMN_NAME_ALIAS + "))";
- private static final String SQL_DELETE_ENTRIES =
+ private static final String SQL_CREATE_USER_METADATA_ENTRY =
+ "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
+ + UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
+ + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
+
+ private static final String SQL_DELETE_KEYS_ENTRY =
"DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
+ private static final String SQL_DELETE_USER_METADATA_ENTRY =
+ "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;
+
RecoverableKeyStoreDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
- db.execSQL(SQL_CREATE_ENTRIES);
+ db.execSQL(SQL_CREATE_KEYS_ENTRY);
+ db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL(SQL_DELETE_ENTRIES);
+ db.execSQL(SQL_DELETE_KEYS_ENTRY);
+ db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
onCreate(db);
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
new file mode 100644
index 0000000..bc56ae1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.security.auth.Destroyable;
+
+/**
+ * Stores pending recovery sessions in memory. We do not write these to disk, as it contains hashes
+ * of the user's lock screen.
+ *
+ * @hide
+ */
+public class RecoverySessionStorage implements Destroyable {
+
+ private final SparseArray<ArrayList<Entry>> mSessionsByUid = new SparseArray<>();
+
+ /**
+ * Returns the session for the given user with the given id.
+ *
+ * @param uid The uid of the recovery agent who created the session.
+ * @param sessionId The unique identifier for the session.
+ * @return The session info.
+ *
+ * @hide
+ */
+ @Nullable
+ public Entry get(int uid, String sessionId) {
+ ArrayList<Entry> userEntries = mSessionsByUid.get(uid);
+ if (userEntries == null) {
+ return null;
+ }
+ for (Entry entry : userEntries) {
+ if (sessionId.equals(entry.mSessionId)) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a pending session for the given user.
+ *
+ * @param uid The uid of the recovery agent who created the session.
+ * @param entry The session info.
+ *
+ * @hide
+ */
+ public void add(int uid, Entry entry) {
+ if (mSessionsByUid.get(uid) == null) {
+ mSessionsByUid.put(uid, new ArrayList<>());
+ }
+ mSessionsByUid.get(uid).add(entry);
+ }
+
+ /**
+ * Removes all sessions associated with the given recovery agent uid.
+ *
+ * @param uid The uid of the recovery agent whose sessions to remove.
+ *
+ * @hide
+ */
+ public void remove(int uid) {
+ ArrayList<Entry> entries = mSessionsByUid.get(uid);
+ if (entries == null) {
+ return;
+ }
+ for (Entry entry : entries) {
+ entry.destroy();
+ }
+ mSessionsByUid.remove(uid);
+ }
+
+ /**
+ * Returns the total count of pending sessions.
+ *
+ * @hide
+ */
+ public int size() {
+ int size = 0;
+ int numberOfUsers = mSessionsByUid.size();
+ for (int i = 0; i < numberOfUsers; i++) {
+ ArrayList<Entry> entries = mSessionsByUid.valueAt(i);
+ size += entries.size();
+ }
+ return size;
+ }
+
+ /**
+ * Wipes the memory of any sensitive information (i.e., lock screen hashes and key claimants).
+ *
+ * @hide
+ */
+ @Override
+ public void destroy() {
+ int numberOfUids = mSessionsByUid.size();
+ for (int i = 0; i < numberOfUids; i++) {
+ ArrayList<Entry> entries = mSessionsByUid.valueAt(i);
+ for (Entry entry : entries) {
+ entry.destroy();
+ }
+ }
+ }
+
+ /**
+ * Information about a recovery session.
+ *
+ * @hide
+ */
+ public static class Entry implements Destroyable {
+ private final byte[] mLskfHash;
+ private final byte[] mKeyClaimant;
+ private final String mSessionId;
+
+ /**
+ * @hide
+ */
+ public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant) {
+ this.mLskfHash = lskfHash;
+ this.mSessionId = sessionId;
+ this.mKeyClaimant = keyClaimant;
+ }
+
+ /**
+ * Returns the hash of the lock screen associated with the recovery attempt.
+ *
+ * @hide
+ */
+ public byte[] getLskfHash() {
+ return mLskfHash;
+ }
+
+ /**
+ * Returns the key generated for this recovery attempt (used to decrypt data returned by
+ * the server).
+ *
+ * @hide
+ */
+ public byte[] getKeyClaimant() {
+ return mKeyClaimant;
+ }
+
+ /**
+ * Overwrites the memory for the lskf hash and key claimant.
+ *
+ * @hide
+ */
+ @Override
+ public void destroy() {
+ Arrays.fill(mLskfHash, (byte) 0);
+ Arrays.fill(mKeyClaimant, (byte) 0);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 03cd4f1..768eb8f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -27,6 +27,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
@@ -38,6 +39,7 @@
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -386,7 +388,7 @@
/**
* Start an {@link IntentSender} when user is unlocked after disabling quiet mode.
*
- * @see {@link #trySetQuietModeDisabled(int, IntentSender)}
+ * @see {@link #trySetQuietModeEnabled(String, boolean, int, IntentSender)}
*/
private class DisableQuietModeUserUnlockedCallback extends IProgressListener.Stub {
private final IntentSender mTarget;
@@ -784,48 +786,114 @@
}
@Override
- public void setQuietModeEnabled(int userHandle, boolean enableQuietMode, IntentSender target) {
- checkManageUsersPermission("silence profile");
- boolean changed = false;
- UserInfo profile, parent;
- synchronized (mPackagesLock) {
- synchronized (mUsersLock) {
- profile = getUserInfoLU(userHandle);
- parent = getProfileParentLU(userHandle);
+ public boolean trySetQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
+ int userHandle, @Nullable IntentSender target) {
+ Preconditions.checkNotNull(callingPackage);
+ if (enableQuietMode && target != null) {
+ throw new IllegalArgumentException(
+ "target should only be specified when we are disabling quiet mode.");
+ }
+
+ if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) {
+ throw new SecurityException("Not allowed to call trySetQuietModeEnabled, "
+ + "caller is foreground default launcher "
+ + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (enableQuietMode) {
+ setQuietModeEnabled(userHandle, true /* enableQuietMode */, target);
+ return true;
+ } else {
+ boolean needToShowConfirmCredential =
+ mLockPatternUtils.isSecure(userHandle)
+ && !StorageManager.isUserKeyUnlocked(userHandle);
+ if (needToShowConfirmCredential) {
+ showConfirmCredentialToDisableQuietMode(userHandle, target);
+ return false;
+ } else {
+ setQuietModeEnabled(userHandle, false /* enableQuietMode */, target);
+ return true;
+ }
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * An app can modify quiet mode if the caller meets one of the condition:
+ * <ul>
+ * <li>Has system UID or root UID</li>
+ * <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li>
+ * <li>Has {@link Manifest.permission#MANAGE_USERS}</li>
+ * </ul>
+ */
+ private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) {
+ if (hasManageUsersPermission()) {
+ return true;
+ }
+
+ final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission(
+ Manifest.permission.MODIFY_QUIET_MODE,
+ callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
+ if (hasModifyQuietModePermission) {
+ return true;
+ }
+
+ final ShortcutServiceInternal shortcutInternal =
+ LocalServices.getService(ShortcutServiceInternal.class);
+ if (shortcutInternal != null) {
+ boolean isForegroundLauncher =
+ shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid);
+ if (isForegroundLauncher) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void setQuietModeEnabled(
+ int userHandle, boolean enableQuietMode, IntentSender target) {
+ final UserInfo profile, parent;
+ final UserData profileUserData;
+ synchronized (mUsersLock) {
+ profile = getUserInfoLU(userHandle);
+ parent = getProfileParentLU(userHandle);
+
if (profile == null || !profile.isManagedProfile()) {
throw new IllegalArgumentException("User " + userHandle + " is not a profile");
}
- if (profile.isQuietModeEnabled() != enableQuietMode) {
- profile.flags ^= UserInfo.FLAG_QUIET_MODE;
- writeUserLP(getUserDataLU(profile.id));
- changed = true;
+ if (profile.isQuietModeEnabled() == enableQuietMode) {
+ Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
+ return;
}
+ profile.flags ^= UserInfo.FLAG_QUIET_MODE;
+ profileUserData = getUserDataLU(profile.id);
}
- if (changed) {
- long identity = Binder.clearCallingIdentity();
- try {
- if (enableQuietMode) {
- ActivityManager.getService().stopUser(userHandle, /* force */true, null);
- LocalServices.getService(ActivityManagerInternal.class)
- .killForegroundAppsForUser(userHandle);
- } else {
- IProgressListener callback = target != null
- ? new DisableQuietModeUserUnlockedCallback(target)
- : null;
- ActivityManager.getService().startUserInBackgroundWithListener(
- userHandle, callback);
- }
- } catch (RemoteException e) {
- Slog.e(LOG_TAG, "fail to start/stop user for quiet mode", e);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ synchronized (mPackagesLock) {
+ writeUserLP(profileUserData);
+ }
+ try {
+ if (enableQuietMode) {
+ ActivityManager.getService().stopUser(userHandle, /* force */true, null);
+ LocalServices.getService(ActivityManagerInternal.class)
+ .killForegroundAppsForUser(userHandle);
+ } else {
+ IProgressListener callback = target != null
+ ? new DisableQuietModeUserUnlockedCallback(target)
+ : null;
+ ActivityManager.getService().startUserInBackgroundWithListener(
+ userHandle, callback);
}
-
- broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(),
- enableQuietMode);
+ } catch (RemoteException e) {
+ // Should not happen, same process.
+ e.rethrowAsRuntimeException();
}
+ broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(),
+ enableQuietMode);
}
@Override
@@ -842,54 +910,42 @@
}
}
- @Override
- public boolean trySetQuietModeDisabled(
+ /**
+ * Show confirm credential screen to unlock user in order to turn off quiet mode.
+ */
+ private void showConfirmCredentialToDisableQuietMode(
@UserIdInt int userHandle, @Nullable IntentSender target) {
- checkManageUsersPermission("silence profile");
- if (StorageManager.isUserKeyUnlocked(userHandle)
- || !mLockPatternUtils.isSecure(userHandle)) {
- // if the user is already unlocked, no need to show a profile challenge
- setQuietModeEnabled(userHandle, false, target);
- return true;
+ // otherwise, we show a profile challenge to trigger decryption of the user
+ final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
+ Context.KEYGUARD_SERVICE);
+ // We should use userHandle not credentialOwnerUserId here, as even if it is unified
+ // lock, confirm screenlock page will know and show personal challenge, and unlock
+ // work profile when personal challenge is correct
+ final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
+ userHandle);
+ if (unlockIntent == null) {
+ return;
}
-
- long identity = Binder.clearCallingIdentity();
- try {
- // otherwise, we show a profile challenge to trigger decryption of the user
- final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
- Context.KEYGUARD_SERVICE);
- // We should use userHandle not credentialOwnerUserId here, as even if it is unified
- // lock, confirm screenlock page will know and show personal challenge, and unlock
- // work profile when personal challenge is correct
- final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
- userHandle);
- if (unlockIntent == null) {
- return false;
- }
- final Intent callBackIntent = new Intent(
- ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
- if (target != null) {
- callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
- }
- callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
- callBackIntent.setPackage(mContext.getPackageName());
- callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- mContext,
- 0,
- callBackIntent,
- PendingIntent.FLAG_CANCEL_CURRENT |
- PendingIntent.FLAG_ONE_SHOT |
- PendingIntent.FLAG_IMMUTABLE);
- // After unlocking the challenge, it will disable quiet mode and run the original
- // intentSender
- unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
- unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- mContext.startActivity(unlockIntent);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ final Intent callBackIntent = new Intent(
+ ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
+ if (target != null) {
+ callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
}
- return false;
+ callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
+ callBackIntent.setPackage(mContext.getPackageName());
+ callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ callBackIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT |
+ PendingIntent.FLAG_ONE_SHOT |
+ PendingIntent.FLAG_IMMUTABLE);
+ // After unlocking the challenge, it will disable quiet mode and run the original
+ // intentSender
+ unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
+ unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ mContext.startActivity(unlockIntent);
}
@Override
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 21c6889..de723c6 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -59,6 +59,8 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+
+import com.android.server.FgThread;
import com.android.server.wm.WindowManagerInternal;
import android.view.inputmethod.InputMethodManagerInternal;
@@ -825,9 +827,11 @@
@Override
public void onSwitchUser(int userHandle) {
- synchronized (mLock) {
- mComponentObserver.onUsersChanged();
- }
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ mComponentObserver.onUsersChanged();
+ }
+ });
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2bda80d..163b160 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
@@ -1068,8 +1069,11 @@
continue;
}
- // If the window is not touchable - ignore.
- if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+ // Ignore non-touchable windows, except the split-screen divider, which is
+ // occasionally non-touchable but still useful for identifying split-screen
+ // mode.
+ if (((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+ && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
continue;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 5b9e3a1..e55d4ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,6 +15,7 @@
*/
package com.android.server.devicepolicy;
+import android.annotation.UserIdInt;
import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
import android.os.PersistableBundle;
@@ -24,6 +25,8 @@
import com.android.internal.R;
import com.android.server.SystemService;
+import java.util.List;
+
/**
* Defines the required interface for IDevicePolicyManager implemenation.
*
@@ -68,7 +71,29 @@
return false;
}
+ @Override
+ public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
+ boolean parent) {
+ return false;
+ }
+
+ @Override
+ public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
+ boolean parent) {
+ return null;
+ }
+
+ @Override
+ public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
+ return false;
+ }
+
public boolean isUsingUnifiedPassword(ComponentName who) {
return true;
}
+
+ public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
+ byte[] cert, byte[] chain, boolean isUserSelectable) {
+ return false;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bead31f..387818b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -734,6 +734,7 @@
private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
private static final String ATTR_VALUE = "value";
+ private static final String TAG_PASSWORD_BLACKLIST = "password-blacklist";
private static final String TAG_PASSWORD_QUALITY = "password-quality";
private static final String TAG_POLICIES = "policies";
private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
@@ -866,6 +867,9 @@
// Default title of confirm credentials screen
String organizationName = null;
+ // The blacklist data is stored in a file whose name is stored in the XML
+ String passwordBlacklistFile = null;
+
ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
info = _info;
isParent = parent;
@@ -947,6 +951,11 @@
out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
}
}
+ if (passwordBlacklistFile != null) {
+ out.startTag(null, TAG_PASSWORD_BLACKLIST);
+ out.attribute(null, ATTR_VALUE, passwordBlacklistFile);
+ out.endTag(null, TAG_PASSWORD_BLACKLIST);
+ }
if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
@@ -1186,7 +1195,9 @@
} else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
minimumPasswordMetrics.nonLetter = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
- } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
+ } else if (TAG_PASSWORD_BLACKLIST.equals(tag)) {
+ passwordBlacklistFile = parser.getAttributeValue(null, ATTR_VALUE);
+ }else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
@@ -1441,6 +1452,8 @@
pw.println(minimumPasswordMetrics.symbols);
pw.print(prefix); pw.print("minimumPasswordNonLetter=");
pw.println(minimumPasswordMetrics.nonLetter);
+ pw.print(prefix); pw.print("passwordBlacklist=");
+ pw.println(passwordBlacklistFile != null);
pw.print(prefix); pw.print("maximumTimeToUnlock=");
pw.println(maximumTimeToUnlock);
pw.print(prefix); pw.print("strongAuthUnlockTimeout=");
@@ -1693,6 +1706,10 @@
return new LockPatternUtils(mContext);
}
+ PasswordBlacklist newPasswordBlacklist(File file) {
+ return new PasswordBlacklist(file);
+ }
+
boolean storageManagerIsFileBasedEncryptionEnabled() {
return StorageManager.isFileEncryptedNativeOnly();
}
@@ -2589,11 +2606,15 @@
}
}
- private JournaledFile makeJournaledFile(int userHandle) {
- final String base = userHandle == UserHandle.USER_SYSTEM
- ? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML
- : new File(mInjector.environmentGetUserSystemDirectory(userHandle),
- DEVICE_POLICIES_XML).getAbsolutePath();
+ private File getPolicyFileDirectory(@UserIdInt int userId) {
+ return userId == UserHandle.USER_SYSTEM
+ ? new File(mInjector.getDevicePolicyFilePathForSystemUser())
+ : mInjector.environmentGetUserSystemDirectory(userId);
+ }
+
+ private JournaledFile makeJournaledFile(@UserIdInt int userId) {
+ final String base = new File(getPolicyFileDirectory(userId), DEVICE_POLICIES_XML)
+ .getAbsolutePath();
if (VERBOSE_LOG) {
Log.v(LOG_TAG, "Opening " + base);
}
@@ -4064,6 +4085,136 @@
}
}
+ /* @return the password blacklist set by the admin or {@code null} if none. */
+ PasswordBlacklist getAdminPasswordBlacklistLocked(@NonNull ActiveAdmin admin) {
+ final int userId = UserHandle.getUserId(admin.getUid());
+ return admin.passwordBlacklistFile == null ? null : new PasswordBlacklist(
+ new File(getPolicyFileDirectory(userId), admin.passwordBlacklistFile));
+ }
+
+ private static final String PASSWORD_BLACKLIST_FILE_PREFIX = "password-blacklist-";
+ private static final String PASSWORD_BLACKLIST_FILE_SUFFIX = "";
+
+ @Override
+ public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
+ boolean parent) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "who is null");
+
+ synchronized (this) {
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+ final int userId = mInjector.userHandleGetCallingUserId();
+ PasswordBlacklist adminBlacklist = getAdminPasswordBlacklistLocked(admin);
+
+ if (blacklist == null || blacklist.isEmpty()) {
+ // Remove the adminBlacklist
+ admin.passwordBlacklistFile = null;
+ saveSettingsLocked(userId);
+ if (adminBlacklist != null) {
+ adminBlacklist.delete();
+ }
+ return true;
+ }
+
+ // Validate server side
+ Preconditions.checkNotNull(name, "name is null");
+ DevicePolicyManager.enforcePasswordBlacklistSize(blacklist);
+
+ // Blacklist is case insensitive so normalize to lower case
+ final int blacklistSize = blacklist.size();
+ for (int i = 0; i < blacklistSize; ++i) {
+ blacklist.set(i, blacklist.get(i).toLowerCase());
+ }
+
+ final boolean isNewBlacklist = adminBlacklist == null;
+ if (isNewBlacklist) {
+ // Create a new file for the blacklist. There could be multiple admins, each setting
+ // different blacklists, to restrict a user's credential, for example a managed
+ // profile can impose restrictions on its parent while the parent is already
+ // restricted by its own admin. A deterministic naming scheme would be fragile if
+ // new types of admin are introduced so we generate and save the file name instead.
+ // This isn't a temporary file but it reuses the name generation logic
+ final File file;
+ try {
+ file = File.createTempFile(PASSWORD_BLACKLIST_FILE_PREFIX,
+ PASSWORD_BLACKLIST_FILE_SUFFIX, getPolicyFileDirectory(userId));
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Failed to make a file for the blacklist", e);
+ return false;
+ }
+ adminBlacklist = mInjector.newPasswordBlacklist(file);
+ }
+
+ if (adminBlacklist.savePasswordBlacklist(name, blacklist)) {
+ if (isNewBlacklist) {
+ // The blacklist was saved so point the admin to the file
+ admin.passwordBlacklistFile = adminBlacklist.getFile().getName();
+ saveSettingsLocked(userId);
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
+ boolean parent) {
+ if (!mHasFeature) {
+ return null;
+ }
+ Preconditions.checkNotNull(who, "who is null");
+ enforceFullCrossUsersPermission(userId);
+ synchronized (this) {
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+ final PasswordBlacklist blacklist = getAdminPasswordBlacklistLocked(admin);
+ if (blacklist == null) {
+ return null;
+ }
+ return blacklist.getName();
+ }
+ }
+
+ @Override
+ public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
+ if (!mHasFeature) {
+ return false;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.TEST_BLACKLISTED_PASSWORD, null);
+ return isPasswordBlacklistedInternal(userId, password);
+ }
+
+ private boolean isPasswordBlacklistedInternal(@UserIdInt int userId, String password) {
+ Preconditions.checkNotNull(password, "Password is null");
+ enforceFullCrossUsersPermission(userId);
+
+ // Normalize to lower case for case insensitive blacklist match
+ final String lowerCasePassword = password.toLowerCase();
+
+ synchronized (this) {
+ final List<ActiveAdmin> admins =
+ getActiveAdminsForLockscreenPoliciesLocked(userId, /* parent */ false);
+ final int N = admins.size();
+ for (int i = 0; i < N; i++) {
+ final PasswordBlacklist blacklist
+ = getAdminPasswordBlacklistLocked(admins.get(i));
+ if (blacklist != null) {
+ if (blacklist.isPasswordBlacklisted(lowerCasePassword)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
@Override
public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
if (!mHasFeature) {
@@ -4420,6 +4571,11 @@
return false;
}
}
+
+ if (isPasswordBlacklistedInternal(userHandle, password)) {
+ Slog.w(LOG_TAG, "resetPassword: the password is blacklisted");
+ return false;
+ }
}
DevicePolicyData policy = getUserData(userHandle);
@@ -4998,6 +5154,33 @@
}
@Override
+ public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
+ byte[] cert, byte[] chain, boolean isUserSelectable) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
+
+ final int callingUid = mInjector.binderGetCallingUid();
+ final long id = mInjector.binderClearCallingIdentity();
+ try (final KeyChainConnection keyChainConnection =
+ KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid))) {
+ IKeyChainService keyChain = keyChainConnection.getService();
+ if (!keyChain.setKeyPairCertificate(alias, cert, chain)) {
+ return false;
+ }
+ keyChain.setUserSelectable(alias, isUserSelectable);
+ return true;
+ } catch (InterruptedException e) {
+ Log.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
+ Thread.currentThread().interrupt();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Failed setting keypair certificate", e);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ return false;
+ }
+
+ @Override
public void choosePrivateKeyAlias(final int uid, final Uri uri, final String alias,
final IBinder response) {
// Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java
new file mode 100644
index 0000000..6a9b53a
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Manages the blacklisted passwords.
+ *
+ * This caller must ensure synchronized access.
+ */
+public class PasswordBlacklist {
+ private static final String TAG = "PasswordBlacklist";
+
+ private final AtomicFile mFile;
+
+ /**
+ * Create an object to manage the password blacklist.
+ *
+ * This is a lightweight operation to prepare variables but not perform any IO.
+ */
+ public PasswordBlacklist(File file) {
+ mFile = new AtomicFile(file);
+ }
+
+ /**
+ * Atomically replace the blacklist.
+ *
+ * Pass {@code null} for an empty list.
+ */
+ public boolean savePasswordBlacklist(@NonNull String name, @NonNull List<String> blacklist) {
+ FileOutputStream fos = null;
+ try {
+ fos = mFile.startWrite();
+ final DataOutputStream out = buildStreamForWriting(fos);
+ final Header header = new Header(Header.VERSION_1, name, blacklist.size());
+ header.write(out);
+ final int blacklistSize = blacklist.size();
+ for (int i = 0; i < blacklistSize; ++i) {
+ out.writeUTF(blacklist.get(i));
+ }
+ out.flush();
+ mFile.finishWrite(fos);
+ return true;
+ } catch (IOException e) {
+ mFile.failWrite(fos);
+ return false;
+ }
+ }
+
+ /** @return the name of the blacklist or {@code null} if none set. */
+ public String getName() {
+ try (DataInputStream in = openForReading()) {
+ return Header.read(in).mName;
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to read blacklist file", e);
+ }
+ return null;
+ }
+
+ /** @return the number of blacklisted passwords. */
+ public int getSize() {
+ final int blacklistSize;
+ try (DataInputStream in = openForReading()) {
+ return Header.read(in).mSize;
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to read blacklist file", e);
+ }
+ return 0;
+ }
+
+ /** @return whether the password matches an blacklisted item. */
+ public boolean isPasswordBlacklisted(@NonNull String password) {
+ final int blacklistSize;
+ try (DataInputStream in = openForReading()) {
+ final Header header = Header.read(in);
+ for (int i = 0; i < header.mSize; ++i) {
+ if (in.readUTF().equals(password)) {
+ return true;
+ }
+ }
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to read blacklist file", e);
+ // Fail safe and block all passwords. Setting a new blacklist should resolve this
+ // problem which can be identified by examining the log.
+ return true;
+ }
+ return false;
+ }
+
+ /** Delete the blacklist completely from disk. */
+ public void delete() {
+ mFile.delete();
+ }
+
+ /** Get the file the blacklist is stored in. */
+ public File getFile() {
+ return mFile.getBaseFile();
+ }
+
+ private DataOutputStream buildStreamForWriting(FileOutputStream fos) {
+ return new DataOutputStream(new BufferedOutputStream(fos));
+ }
+
+ private DataInputStream openForReading() throws IOException {
+ return new DataInputStream(new BufferedInputStream(mFile.openRead()));
+ }
+
+ /**
+ * Helper to read and write the header of the blacklist file.
+ */
+ private static class Header {
+ static final int VERSION_1 = 1;
+
+ final int mVersion; // File format version
+ final String mName;
+ final int mSize;
+
+ Header(int version, String name, int size) {
+ mVersion = version;
+ mName = name;
+ mSize = size;
+ }
+
+ void write(DataOutputStream out) throws IOException {
+ out.writeInt(mVersion);
+ out.writeUTF(mName);
+ out.writeInt(mSize);
+ }
+
+ static Header read(DataInputStream in) throws IOException {
+ final int version = in.readInt();
+ final String name = in.readUTF();
+ final int size = in.readInt();
+ return new Header(version, name, size);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index c14f74c..0462b14 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -62,7 +62,7 @@
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
- @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+ @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock GlobalActionPerformer mMockGlobalActionPerformer;
@Mock KeyEventDispatcher mMockKeyEventDispatcher;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 45ecbfb..8853db2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -59,7 +59,7 @@
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
- @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+ @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock GlobalActionPerformer mMockGlobalActionPerformer;
@Mock IBinder mMockOwner;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index d168479..0650acb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -102,6 +102,11 @@
this.context = injector.context;
}
+ @Override
+ public boolean isPasswordBlacklisted(int userId, String password) {
+ return false;
+ }
+
public void notifyChangeToContentObserver(Uri uri, int userHandle) {
ContentObserver co = mMockInjector.mContentObservers.get(new Pair<>(uri, userHandle));
@@ -205,6 +210,11 @@
}
@Override
+ PasswordBlacklist newPasswordBlacklist(File file) {
+ return services.passwordBlacklist;
+ }
+
+ @Override
boolean storageManagerIsFileBasedEncryptionEnabled() {
return services.storageManager.isFileBasedEncryptionEnabled();
}
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 ca918c6..4779474 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3765,6 +3765,36 @@
assertTrue(dpm.clearResetPasswordToken(admin1));
}
+ public void testSetPasswordBlacklistCannotBeCalledByNonAdmin() throws Exception {
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPasswordBlacklist(admin1, null, null));
+ verifyZeroInteractions(getServices().passwordBlacklist);
+ }
+
+ public void testClearingPasswordBlacklistDoesNotCreateNewBlacklist() throws Exception {
+ setupProfileOwner();
+ dpm.setPasswordBlacklist(admin1, null, null);
+ verifyZeroInteractions(getServices().passwordBlacklist);
+ }
+
+ public void testSetPasswordBlacklistCreatesNewBlacklist() throws Exception {
+ final String name = "myblacklist";
+ final List<String> explicit = Arrays.asList("password", "letmein");
+ setupProfileOwner();
+ dpm.setPasswordBlacklist(admin1, name, explicit);
+ verify(getServices().passwordBlacklist).savePasswordBlacklist(name, explicit);
+ }
+
+ public void testSetPasswordBlacklistOnlyConvertsExplicitToLowerCase() throws Exception {
+ final List<String> mixedCase = Arrays.asList("password", "LETMEIN", "FooTBAll");
+ final List<String> lowerCase = Arrays.asList("password", "letmein", "football");
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ final String name = "Name of the Blacklist";
+ dpm.setPasswordBlacklist(admin1, name, mixedCase);
+ verify(getServices().passwordBlacklist).savePasswordBlacklist(name, lowerCase);
+ }
+
public void testIsActivePasswordSufficient() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
mContext.packageName = admin1.getPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 4ee5ba6..8cb0459 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -86,6 +86,7 @@
public final IBackupManager ibackupManager;
public final IAudioService iaudioService;
public final LockPatternUtils lockPatternUtils;
+ public final PasswordBlacklist passwordBlacklist;
public final StorageManagerForMock storageManager;
public final WifiManager wifiManager;
public final SettingsForMock settings;
@@ -120,6 +121,7 @@
ibackupManager = mock(IBackupManager.class);
iaudioService = mock(IAudioService.class);
lockPatternUtils = mock(LockPatternUtils.class);
+ passwordBlacklist = mock(PasswordBlacklist.class);
storageManager = mock(StorageManagerForMock.class);
wifiManager = mock(WifiManager.class);
settings = mock(SettingsForMock.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
new file mode 100644
index 0000000..1b3fc2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link PasswordBlacklist}.
+ *
+ * bit FrameworksServicesTests:com.android.server.devicepolicy.PasswordBlacklistTest
+ * runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
+ */
+@RunWith(AndroidJUnit4.class)
+public final class PasswordBlacklistTest {
+ private File mBlacklistFile;
+ private PasswordBlacklist mBlacklist;
+
+ @Before
+ public void setUp() throws IOException {
+ mBlacklistFile = File.createTempFile("pwdbl", null);
+ mBlacklist = new PasswordBlacklist(mBlacklistFile);
+ }
+
+ @After
+ public void tearDown() {
+ mBlacklist.delete();
+ }
+
+ @Test
+ public void matchIsExact() {
+ // Note: Case sensitivity is handled by the user of PasswordBlacklist by normalizing the
+ // values stored in and tested against it.
+ mBlacklist.savePasswordBlacklist("matchIsExact", Arrays.asList("password", "qWERty"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("password"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("qWERty"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("Password"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("qwert"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("letmein"));
+ }
+
+ @Test
+ public void matchIsNotRegex() {
+ mBlacklist.savePasswordBlacklist("matchIsNotRegex", Arrays.asList("a+b*"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("a+b*"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("aaaa"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("abbbb"));
+ assertFalse(mBlacklist.isPasswordBlacklisted("aaaa"));
+ }
+
+ @Test
+ public void matchFailsSafe() throws IOException {
+ try (FileOutputStream fos = new FileOutputStream(mBlacklistFile)) {
+ // Write a malformed blacklist file
+ fos.write(17);
+ }
+ assertTrue(mBlacklist.isPasswordBlacklisted("anything"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("at"));
+ assertTrue(mBlacklist.isPasswordBlacklisted("ALL"));
+ }
+
+ @Test
+ public void blacklistCanBeNamed() {
+ final String name = "identifier";
+ mBlacklist.savePasswordBlacklist(name, Arrays.asList("one", "two", "three"));
+ assertEquals(mBlacklist.getName(), name);
+ }
+
+ @Test
+ public void reportsTheCorrectNumberOfEntries() {
+ mBlacklist.savePasswordBlacklist("Count Entries", Arrays.asList("1", "2", "3", "4"));
+ assertEquals(mBlacklist.getSize(), 4);
+ }
+
+ @Test
+ public void reportsBlacklistFile() {
+ assertEquals(mBlacklistFile, mBlacklist.getFile());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index a997770..e20f664 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -36,6 +36,8 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -45,6 +47,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
import java.security.KeyStore;
import java.util.List;
@@ -52,9 +55,9 @@
@RunWith(AndroidJUnit4.class)
public class PlatformKeyManagerTest {
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
private static final int USER_ID_FIXTURE = 42;
- private static final String TEST_SHARED_PREFS_NAME = "PlatformKeyManagerTestPrefs";
@Mock private Context mContext;
@Mock private KeyStoreProxy mKeyStoreProxy;
@@ -63,18 +66,20 @@
@Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor;
@Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor;
- private SharedPreferences mSharedPreferences;
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
+
private PlatformKeyManager mPlatformKeyManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- Context testContext = InstrumentationRegistry.getTargetContext();
- mSharedPreferences = testContext.getSharedPreferences(
- TEST_SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
mPlatformKeyManager = new PlatformKeyManager(
- USER_ID_FIXTURE, mContext, mKeyStoreProxy, mSharedPreferences);
+ USER_ID_FIXTURE, mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
when(mContext.getSystemServiceName(any())).thenReturn("test");
@@ -83,7 +88,8 @@
@After
public void tearDown() {
- mSharedPreferences.edit().clear().commit();
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index 12dbdb3..3012931 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -16,62 +16,71 @@
package com.android.server.locksettings.recoverablekeystore;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import com.google.common.collect.ImmutableMap;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
import java.security.KeyStore;
+import java.util.Arrays;
+import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RecoverableKeyGeneratorTest {
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
private static final int TEST_GENERATION_ID = 3;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String KEY_ALGORITHM = "AES";
+ private static final String SUPPORTED_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+ private static final String UNSUPPORTED_CIPHER_ALGORITHM = "AES/CTR/NoPadding";
private static final String TEST_ALIAS = "karlin";
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
-
- @Mock
- RecoverableKeyStorage mRecoverableKeyStorage;
-
- @Captor ArgumentCaptor<KeyProtection> mKeyProtectionArgumentCaptor;
+ private static final int KEYSTORE_UID_SELF = -1;
+ private static final int GCM_TAG_LENGTH_BITS = 128;
+ private static final int GCM_NONCE_LENGTH_BYTES = 12;
private PlatformEncryptionKey mPlatformKey;
- private SecretKey mKeyHandle;
+ private PlatformDecryptionKey mDecryptKey;
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
private RecoverableKeyGenerator mRecoverableKeyGenerator;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
- mKeyHandle = generateKey();
- mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(
- mPlatformKey, mRecoverableKeyStorage);
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
- when(mRecoverableKeyStorage.loadFromAndroidKeyStore(any())).thenReturn(mKeyHandle);
+ AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey);
+ mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey);
+ mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb);
}
@After
@@ -79,67 +88,69 @@
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
keyStore.load(/*param=*/ null);
keyStore.deleteEntry(WRAPPING_KEY_ALIAS);
+
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
}
@Test
public void generateAndStoreKey_setsKeyInKeyStore() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
- verify(mRecoverableKeyStorage, times(1))
- .importIntoAndroidKeyStore(eq(TEST_ALIAS), any(), any());
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ assertTrue(keyStore.containsAlias(TEST_ALIAS));
}
@Test
- public void generateAndStoreKey_storesKeyEnabledForEncryptDecrypt() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt()
+ throws Exception {
+ mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertEquals(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
- keyProtection.getPurposes());
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
+ Arrays.fill(nonce, (byte) 0);
+ cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
}
@Test
- public void generateAndStoreKey_storesKeyEnabledForGCM() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception {
+ mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertArrayEquals(new String[] { KeyProperties.BLOCK_MODE_GCM },
- keyProtection.getBlockModes());
- }
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM);
- @Test
- public void generateAndStoreKey_storesKeyEnabledForNoPadding() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
- KeyProtection keyProtection = getKeyProtectionUsed();
- assertArrayEquals(new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
- keyProtection.getEncryptionPaddings());
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ fail("Should not be able to use key for " + UNSUPPORTED_CIPHER_ALGORITHM);
+ } catch (InvalidKeyException e) {
+ // expected
+ }
}
@Test
public void generateAndStoreKey_storesWrappedKey() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+ mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
- verify(mRecoverableKeyStorage, times(1)).persistToDisk(eq(TEST_ALIAS), any());
- }
+ KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+ SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+ WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+ SecretKey unwrappedKey = WrappedKey
+ .unwrapKeys(mDecryptKey, ImmutableMap.of(TEST_ALIAS, wrappedKey))
+ .get(TEST_ALIAS);
- @Test
- public void generateAndStoreKey_returnsKeyHandle() throws Exception {
- SecretKey secretKey = mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
- assertEquals(mKeyHandle, secretKey);
- }
-
- private KeyProtection getKeyProtectionUsed() throws Exception {
- verify(mRecoverableKeyStorage, times(1)).importIntoAndroidKeyStore(
- any(), any(), mKeyProtectionArgumentCaptor.capture());
- return mKeyProtectionArgumentCaptor.getValue();
- }
-
- private SecretKey generateKey() throws Exception {
- KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
- keyGenerator.init(/*keySize=*/ 256);
- return keyGenerator.generateKey();
+ // key and unwrappedKey should be equivalent. let's check!
+ byte[] plaintext = getUtf8Bytes("dtianpos");
+ Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] encrypted = cipher.doFinal(plaintext);
+ byte[] iv = cipher.getIV();
+ cipher.init(Cipher.DECRYPT_MODE, unwrappedKey, new GCMParameterSpec(128, iv));
+ byte[] decrypted = cipher.doFinal(encrypted);
+ assertArrayEquals(decrypted, plaintext);
}
private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
@@ -153,4 +164,8 @@
.build());
return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
}
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
deleted file mode 100644
index fb4e75e..0000000
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.KeyStoreException;
-import java.util.Random;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RecoverableKeyStorageImplTest {
- private static final String KEY_ALGORITHM = "AES";
- private static final int GCM_TAG_LENGTH_BYTES = 16;
- private static final int BITS_PER_BYTE = 8;
- private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
- private static final int GCM_NONCE_LENGTH_BYTES = 12;
- private static final String TEST_KEY_ALIAS = "RecoverableKeyStorageImplTestKey";
- private static final int KEYSTORE_UID_SELF = -1;
-
- private RecoverableKeyStorageImpl mRecoverableKeyStorage;
-
- @Before
- public void setUp() throws Exception {
- mRecoverableKeyStorage = RecoverableKeyStorageImpl.newInstance(
- /*userId=*/ KEYSTORE_UID_SELF);
- }
-
- @After
- public void tearDown() {
- try {
- mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
- } catch (KeyStoreException e) {
- // Do nothing.
- }
- }
-
- @Test
- public void loadFromAndroidKeyStore_loadsAKeyThatWasImported() throws Exception {
- SecretKey key = generateKey();
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- key,
- getKeyProperties());
-
- assertKeysAreEquivalent(
- key, mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
- }
-
- @Test
- public void importIntoAndroidKeyStore_importsWithKeyProperties() throws Exception {
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- generateKey(),
- getKeyProperties());
-
- SecretKey key = mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS);
-
- Mac mac = Mac.getInstance("HmacSHA256");
- try {
- // Fails because missing PURPOSE_SIGN or PURPOSE_VERIFY
- mac.init(key);
- fail("Was able to initialize Mac with an ENCRYPT/DECRYPT-only key.");
- } catch (InvalidKeyException e) {
- // expect exception
- }
- }
-
- @Test
- public void removeFromAndroidKeyStore_removesAnEntry() throws Exception {
- mRecoverableKeyStorage.importIntoAndroidKeyStore(
- TEST_KEY_ALIAS,
- generateKey(),
- getKeyProperties());
-
- mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
-
- assertNull(mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
- }
-
- private static KeyProtection getKeyProperties() {
- return new KeyProtection.Builder(
- KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build();
- }
-
- /**
- * Asserts that {@code b} key can decrypt data encrypted with {@code a} key. Otherwise throws.
- */
- private static void assertKeysAreEquivalent(SecretKey a, SecretKey b) throws Exception {
- byte[] plaintext = "doge".getBytes(StandardCharsets.UTF_8);
- byte[] nonce = generateGcmNonce();
-
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
- cipher.init(Cipher.ENCRYPT_MODE, a, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
- byte[] encrypted = cipher.doFinal(plaintext);
-
- cipher.init(Cipher.DECRYPT_MODE, b, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
- byte[] decrypted = cipher.doFinal(encrypted);
-
- assertArrayEquals(decrypted, plaintext);
- }
-
- /**
- * Returns a new random GCM nonce.
- */
- private static byte[] generateGcmNonce() {
- Random random = new Random();
- byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
- random.nextBytes(nonce);
- return nonce;
- }
-
- private static SecretKey generateKey() throws Exception {
- KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
- keyGenerator.init(/*keySize=*/ 256);
- return keyGenerator.generateKey();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
new file mode 100644
index 0000000..35b18b1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverableKeyStoreManagerTest {
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+
+ private static final String TEST_SESSION_ID = "karlin";
+ private static final byte[] TEST_PUBLIC_KEY = new byte[] {
+ (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
+ (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+ (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03,
+ (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e,
+ (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07,
+ (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc,
+ (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2,
+ (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55,
+ (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d,
+ (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1,
+ (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa};
+ private static final byte[] TEST_SALT = getUtf8Bytes("salt");
+ private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
+ private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
+ private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params");
+ private static final int TEST_USER_ID = 10009;
+ private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+
+ @Mock private Context mMockContext;
+
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
+ private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+ private RecoverySessionStorage mRecoverySessionStorage;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+ mRecoverySessionStorage = new RecoverySessionStorage();
+ mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
+ mMockContext,
+ mRecoverableKeyStoreDb,
+ mRecoverySessionStorage);
+ }
+
+ @After
+ public void tearDown() {
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
+ }
+
+ @Test
+ public void startRecoverySession_checksPermissionFirst() throws Exception {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ TEST_PUBLIC_KEY,
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(new KeyStoreRecoveryMetadata(
+ TYPE_LOCKSCREEN,
+ TYPE_PASSWORD,
+ KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+ TEST_SECRET)),
+ TEST_USER_ID);
+
+ verify(mMockContext, times(1)).enforceCallingOrSelfPermission(
+ eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE),
+ any());
+ }
+
+ @Test
+ public void startRecoverySession_storesTheSessionInfo() throws Exception {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ TEST_PUBLIC_KEY,
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(new KeyStoreRecoveryMetadata(
+ TYPE_LOCKSCREEN,
+ TYPE_PASSWORD,
+ KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+ TEST_SECRET)),
+ TEST_USER_ID);
+
+ assertEquals(1, mRecoverySessionStorage.size());
+ RecoverySessionStorage.Entry entry = mRecoverySessionStorage.get(
+ TEST_USER_ID, TEST_SESSION_ID);
+ assertArrayEquals(TEST_SECRET, entry.getLskfHash());
+ assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
+ }
+
+ @Test
+ public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception {
+ try {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ TEST_PUBLIC_KEY,
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(),
+ TEST_USER_ID);
+ } catch (RemoteException e) {
+ assertEquals("Only a single KeyStoreRecoveryMetadata is supported",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void startRecoverySession_throwsIfBadKey() throws Exception {
+ try {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ getUtf8Bytes("0"),
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(new KeyStoreRecoveryMetadata(
+ TYPE_LOCKSCREEN,
+ TYPE_PASSWORD,
+ KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+ TEST_SECRET)),
+ TEST_USER_ID);
+ } catch (RemoteException e) {
+ assertEquals("Not a valid X509 key",
+ e.getMessage());
+ }
+ }
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
new file mode 100644
index 0000000..72b69f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECPrivateKeySpec;
+import javax.crypto.AEADBadTagException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecureBoxTest {
+
+ private static final int EC_PUBLIC_KEY_LEN_BYTES = 65;
+ private static final int NUM_TEST_ITERATIONS = 100;
+ private static final int VERSION_LEN_BYTES = 2;
+
+ // The following fixtures were produced by the C implementation of SecureBox v2. We use these to
+ // cross-verify the two implementations.
+ private static final byte[] VAULT_PARAMS =
+ new byte[] {
+ (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98,
+ (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe,
+ (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01,
+ (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61,
+ (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f,
+ (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d,
+ (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a,
+ (byte) 0x8b, (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2,
+ (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
+ (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa, (byte) 0x31,
+ (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00
+ };
+ private static final byte[] VAULT_CHALLENGE = getBytes("Not a real vault challenge");
+ private static final byte[] THM_KF_HASH = getBytes("12345678901234567890123456789012");
+ private static final byte[] ENCRYPTED_RECOVERY_KEY =
+ new byte[] {
+ (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0xe3, (byte) 0xa8, (byte) 0xd0,
+ (byte) 0x32, (byte) 0x3c, (byte) 0xc7, (byte) 0xe5, (byte) 0xe8, (byte) 0xc1,
+ (byte) 0x73, (byte) 0x4c, (byte) 0x75, (byte) 0x20, (byte) 0x2e, (byte) 0xb7,
+ (byte) 0xba, (byte) 0xef, (byte) 0x3e, (byte) 0x3e, (byte) 0xa6, (byte) 0x93,
+ (byte) 0xe9, (byte) 0xde, (byte) 0xa7, (byte) 0x00, (byte) 0x09, (byte) 0xba,
+ (byte) 0xa8, (byte) 0x9c, (byte) 0xac, (byte) 0x72, (byte) 0xff, (byte) 0xf6,
+ (byte) 0x84, (byte) 0x16, (byte) 0xb0, (byte) 0xff, (byte) 0x47, (byte) 0x98,
+ (byte) 0x53, (byte) 0xc4, (byte) 0xa3, (byte) 0x4a, (byte) 0x54, (byte) 0x21,
+ (byte) 0x8e, (byte) 0x00, (byte) 0x4b, (byte) 0xfa, (byte) 0xce, (byte) 0xe3,
+ (byte) 0x79, (byte) 0x8e, (byte) 0x20, (byte) 0x7c, (byte) 0x9b, (byte) 0xc4,
+ (byte) 0x7c, (byte) 0xd5, (byte) 0x33, (byte) 0x70, (byte) 0x96, (byte) 0xdc,
+ (byte) 0xa0, (byte) 0x1f, (byte) 0x6e, (byte) 0xbb, (byte) 0x5d, (byte) 0x0c,
+ (byte) 0x64, (byte) 0x5f, (byte) 0xed, (byte) 0xbf, (byte) 0x79, (byte) 0x8a,
+ (byte) 0x0e, (byte) 0xd6, (byte) 0x4b, (byte) 0x93, (byte) 0xc9, (byte) 0xcd,
+ (byte) 0x25, (byte) 0x06, (byte) 0x73, (byte) 0x5e, (byte) 0xdb, (byte) 0xac,
+ (byte) 0xa8, (byte) 0xeb, (byte) 0x6e, (byte) 0x26, (byte) 0x77, (byte) 0x56,
+ (byte) 0xd1, (byte) 0x23, (byte) 0x48, (byte) 0xb6, (byte) 0x6a, (byte) 0x15,
+ (byte) 0xd4, (byte) 0x3e, (byte) 0x38, (byte) 0x7d, (byte) 0x6f, (byte) 0x6f,
+ (byte) 0x7c, (byte) 0x0b, (byte) 0x93, (byte) 0x4e, (byte) 0xb3, (byte) 0x21,
+ (byte) 0x44, (byte) 0x86, (byte) 0xf3, (byte) 0x2e
+ };
+ private static final byte[] KEY_CLAIMANT = getBytes("asdfasdfasdfasdf");
+ private static final byte[] RECOVERY_CLAIM =
+ new byte[] {
+ (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0x16, (byte) 0x75, (byte) 0x5b,
+ (byte) 0xa2, (byte) 0xdc, (byte) 0x2b, (byte) 0x58, (byte) 0xb9, (byte) 0x66,
+ (byte) 0xcb, (byte) 0x6f, (byte) 0xb1, (byte) 0xc1, (byte) 0xb0, (byte) 0x1d,
+ (byte) 0x82, (byte) 0x29, (byte) 0x97, (byte) 0xec, (byte) 0x65, (byte) 0x5e,
+ (byte) 0xef, (byte) 0x14, (byte) 0xc7, (byte) 0xf0, (byte) 0xf1, (byte) 0x83,
+ (byte) 0x15, (byte) 0x0b, (byte) 0xcb, (byte) 0x33, (byte) 0x2d, (byte) 0x05,
+ (byte) 0x20, (byte) 0xdc, (byte) 0xc7, (byte) 0x0d, (byte) 0xc8, (byte) 0xc0,
+ (byte) 0xc9, (byte) 0xa8, (byte) 0x67, (byte) 0xc8, (byte) 0x16, (byte) 0xfe,
+ (byte) 0xfb, (byte) 0xb0, (byte) 0x28, (byte) 0x8e, (byte) 0x4f, (byte) 0xd5,
+ (byte) 0x31, (byte) 0xa7, (byte) 0x94, (byte) 0x33, (byte) 0x23, (byte) 0x15,
+ (byte) 0x04, (byte) 0xbf, (byte) 0x13, (byte) 0x6a, (byte) 0x28, (byte) 0x8f,
+ (byte) 0xa6, (byte) 0xfc, (byte) 0x01, (byte) 0xd5, (byte) 0x69, (byte) 0x3d,
+ (byte) 0x96, (byte) 0x0c, (byte) 0x37, (byte) 0xb4, (byte) 0x1e, (byte) 0x13,
+ (byte) 0x40, (byte) 0xcc, (byte) 0x44, (byte) 0x19, (byte) 0xf2, (byte) 0xdb,
+ (byte) 0x49, (byte) 0x80, (byte) 0x9f, (byte) 0xef, (byte) 0xee, (byte) 0x41,
+ (byte) 0xe6, (byte) 0x3f, (byte) 0xa8, (byte) 0xea, (byte) 0x89, (byte) 0xfe,
+ (byte) 0x56, (byte) 0x20, (byte) 0xba, (byte) 0x90, (byte) 0x9a, (byte) 0xba,
+ (byte) 0x0e, (byte) 0x30, (byte) 0xa7, (byte) 0x2b, (byte) 0x0a, (byte) 0x12,
+ (byte) 0x0b, (byte) 0x03, (byte) 0xd1, (byte) 0x0c, (byte) 0x8e, (byte) 0x82,
+ (byte) 0x03, (byte) 0xa1, (byte) 0x7f, (byte) 0xc8, (byte) 0xd0, (byte) 0xa9,
+ (byte) 0x86, (byte) 0x55, (byte) 0x63, (byte) 0xdc, (byte) 0x70, (byte) 0x34,
+ (byte) 0x21, (byte) 0x2a, (byte) 0x41, (byte) 0x3f, (byte) 0xbb, (byte) 0x82,
+ (byte) 0x82, (byte) 0xf9, (byte) 0x2b, (byte) 0xd2, (byte) 0x33, (byte) 0x03,
+ (byte) 0x50, (byte) 0xd2, (byte) 0x27, (byte) 0xeb, (byte) 0x1a
+ };
+
+ private static final byte[] TEST_SHARED_SECRET = getBytes("TEST_SHARED_SECRET");
+ private static final byte[] TEST_HEADER = getBytes("TEST_HEADER");
+ private static final byte[] TEST_PAYLOAD = getBytes("TEST_PAYLOAD");
+
+ private static final PublicKey THM_PUBLIC_KEY;
+ private static final PrivateKey THM_PRIVATE_KEY;
+
+ static {
+ try {
+ THM_PUBLIC_KEY =
+ SecureBox.decodePublicKey(
+ new byte[] {
+ (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18,
+ (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4,
+ (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c,
+ (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a,
+ (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0,
+ (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0,
+ (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79,
+ (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55, (byte) 0xb0,
+ (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32,
+ (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc,
+ (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
+ (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa
+ });
+ THM_PRIVATE_KEY =
+ decodePrivateKey(
+ new byte[] {
+ (byte) 0x70, (byte) 0x01, (byte) 0xc7, (byte) 0x87, (byte) 0x32,
+ (byte) 0x2f, (byte) 0x1c, (byte) 0x9a, (byte) 0x6e, (byte) 0xb1,
+ (byte) 0x91, (byte) 0xca, (byte) 0x4e, (byte) 0xb5, (byte) 0x44,
+ (byte) 0xba, (byte) 0xc8, (byte) 0x68, (byte) 0xc6, (byte) 0x0a,
+ (byte) 0x76, (byte) 0xcb, (byte) 0xd3, (byte) 0x63, (byte) 0x67,
+ (byte) 0x7c, (byte) 0xb0, (byte) 0x11, (byte) 0x82, (byte) 0x65,
+ (byte) 0x77, (byte) 0x01
+ });
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Test
+ public void genKeyPair_alwaysReturnsANewKeyPair() throws Exception {
+ KeyPair keyPair1 = SecureBox.genKeyPair();
+ KeyPair keyPair2 = SecureBox.genKeyPair();
+ assertThat(keyPair1).isNotEqualTo(keyPair2);
+ }
+
+ @Test
+ public void decryptRecoveryClaim() throws Exception {
+ byte[] claimContent =
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ /*sharedSecret=*/ null,
+ SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
+ RECOVERY_CLAIM);
+ assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT));
+ }
+
+ @Test
+ public void decryptRecoveryKey_doesNotThrowForValidAuthenticationTag() throws Exception {
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ THM_KF_HASH,
+ SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
+ ENCRYPTED_RECOVERY_KEY);
+ }
+
+ @Test
+ public void encryptThenDecrypt() throws Exception {
+ byte[] state = TEST_PAYLOAD;
+ // Iterate multiple times to amplify any errors
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ state = SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+ }
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ state = SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+ }
+ assertThat(state).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullPublicPrivateKeys() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ /*theirPublicKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(
+ /*ourPrivateKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullSharedSecret() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, /*sharedSecret=*/ null, TEST_HEADER, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(THM_PRIVATE_KEY, /*sharedSecret=*/ null, TEST_HEADER, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullHeader() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, TEST_SHARED_SECRET, /*header=*/ null, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, /*header=*/ null, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullPayload() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, /*payload=*/ null);
+ byte[] decrypted =
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ /*encryptedPayload=*/ encrypted);
+ assertThat(decrypted.length).isEqualTo(0);
+ }
+
+ @Test
+ public void encrypt_nullPublicKeyAndSharedSecret() throws Exception {
+ IllegalArgumentException expected =
+ expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecureBox.encrypt(
+ /*theirPublicKey=*/ null,
+ /*sharedSecret=*/ null,
+ TEST_HEADER,
+ TEST_PAYLOAD));
+ assertThat(expected.getMessage()).contains("public key and shared secret");
+ }
+
+ @Test
+ public void decrypt_nullPrivateKeyAndSharedSecret() throws Exception {
+ IllegalArgumentException expected =
+ expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecureBox.decrypt(
+ /*ourPrivateKey=*/ null,
+ /*sharedSecret=*/ null,
+ TEST_HEADER,
+ TEST_PAYLOAD));
+ assertThat(expected.getMessage()).contains("private key and shared secret");
+ }
+
+ @Test
+ public void decrypt_nullEncryptedPayload() throws Exception {
+ IllegalArgumentException expected =
+ expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ /*encryptedPayload=*/ null));
+ assertThat(expected.getMessage()).contains("payload");
+ }
+
+ @Test
+ public void decrypt_badAuthenticationTag() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ encrypted[encrypted.length - 1] ^= (byte) 1;
+
+ assertThrows(
+ AEADBadTagException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+ }
+
+ @Test
+ public void encrypt_invalidPublicKey() throws Exception {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ PublicKey publicKey = keyGen.genKeyPair().getPublic();
+
+ assertThrows(
+ InvalidKeyException.class,
+ () -> SecureBox.encrypt(publicKey, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD));
+ }
+
+ @Test
+ public void decrypt_invalidPrivateKey() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ PrivateKey privateKey = keyGen.genKeyPair().getPrivate();
+
+ assertThrows(
+ InvalidKeyException.class,
+ () -> SecureBox.decrypt(privateKey, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+ }
+
+ @Test
+ public void decrypt_publicKeyOutsideCurve() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ // Flip the least significant bit of the encoded public key
+ encrypted[VERSION_LEN_BYTES + EC_PUBLIC_KEY_LEN_BYTES - 1] ^= (byte) 1;
+
+ InvalidKeyException expected =
+ expectThrows(
+ InvalidKeyException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ encrypted));
+ assertThat(expected.getMessage()).contains("expected curve");
+ }
+
+ @Test
+ public void encodeThenDecodePublicKey() throws Exception {
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ PublicKey originalKey = SecureBox.genKeyPair().getPublic();
+ byte[] encodedKey = SecureBox.encodePublicKey(originalKey);
+ PublicKey decodedKey = SecureBox.decodePublicKey(encodedKey);
+ assertThat(originalKey).isEqualTo(decodedKey);
+ }
+ }
+
+ private static byte[] getBytes(String str) {
+ return str.getBytes(StandardCharsets.UTF_8);
+ }
+
+ private static PrivateKey decodePrivateKey(byte[] keyBytes) throws Exception {
+ assertThat(keyBytes.length).isEqualTo(32);
+ BigInteger priv = new BigInteger(/*signum=*/ 1, keyBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ return keyFactory.generatePrivate(new ECPrivateKeySpec(priv, SecureBox.EC_PARAM_SPEC));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 3d5b958..d5ad959 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -30,7 +30,6 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import java.io.File;
@@ -65,7 +64,7 @@
WrappedKey oldWrappedKey = new WrappedKey(
getUtf8Bytes("nonce1"),
getUtf8Bytes("keymaterial1"),
- /*platformKeyGenerationId=*/1);
+ /*platformKeyGenerationId=*/ 1);
mRecoverableKeyStoreDb.insertKey(
userId, alias, oldWrappedKey);
byte[] nonce = getUtf8Bytes("nonce2");
@@ -83,6 +82,29 @@
}
@Test
+ public void insertKey_allowsTwoUidsToHaveSameAlias() {
+ String alias = "pcoulton";
+ WrappedKey key1 = new WrappedKey(
+ getUtf8Bytes("nonce1"),
+ getUtf8Bytes("key1"),
+ /*platformKeyGenerationId=*/ 1);
+ WrappedKey key2 = new WrappedKey(
+ getUtf8Bytes("nonce2"),
+ getUtf8Bytes("key2"),
+ /*platformKeyGenerationId=*/ 1);
+
+ mRecoverableKeyStoreDb.insertKey(/*uid=*/ 1, alias, key1);
+ mRecoverableKeyStoreDb.insertKey(/*uid=*/ 2, alias, key2);
+
+ assertArrayEquals(
+ getUtf8Bytes("nonce1"),
+ mRecoverableKeyStoreDb.getKey(1, alias).getNonce());
+ assertArrayEquals(
+ getUtf8Bytes("nonce2"),
+ mRecoverableKeyStoreDb.getKey(2, alias).getNonce());
+ }
+
+ @Test
public void getKey_returnsNullIfNoKey() {
WrappedKey key = mRecoverableKeyStoreDb.getKey(
/*userId=*/ 1, /*alias=*/ "hello");
@@ -157,6 +179,29 @@
assertTrue(keys.isEmpty());
}
+ @Test
+ public void getPlatformKeyGenerationId_returnsGenerationId() {
+ int userId = 42;
+ int generationId = 24;
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId);
+
+ assertEquals(generationId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+ }
+
+ @Test
+ public void getPlatformKeyGenerationId_returnsMinusOneIfNoEntry() {
+ assertEquals(-1, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(42));
+ }
+
+ @Test
+ public void setPlatformKeyGenerationId_replacesOldEntry() {
+ int userId = 42;
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 1);
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 2);
+
+ assertEquals(2, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+ }
+
private static byte[] getUtf8Bytes(String s) {
return s.getBytes(StandardCharsets.UTF_8);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
new file mode 100644
index 0000000..6aeff28
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverySessionStorageTest {
+
+ private static final String TEST_SESSION_ID = "peter";
+ private static final int TEST_USER_ID = 696;
+ private static final byte[] TEST_LSKF_HASH = getUtf8Bytes("lskf");
+ private static final byte[] TEST_KEY_CLAIMANT = getUtf8Bytes("0000111122223333");
+
+ @Test
+ public void size_isZeroForEmpty() {
+ assertEquals(0, new RecoverySessionStorage().size());
+ }
+
+ @Test
+ public void size_incrementsAfterAdd() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+
+ assertEquals(1, storage.size());
+ }
+
+ @Test
+ public void size_decrementsAfterRemove() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+ storage.remove(TEST_USER_ID);
+
+ assertEquals(0, storage.size());
+ }
+
+ @Test
+ public void remove_overwritesLskfHashMemory() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+ storage.add(TEST_USER_ID, entry);
+
+ storage.remove(TEST_USER_ID);
+
+ assertZeroedOut(entry.getLskfHash());
+ }
+
+ @Test
+ public void remove_overwritesKeyClaimantMemory() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+ storage.add(TEST_USER_ID, entry);
+
+ storage.remove(TEST_USER_ID);
+
+ assertZeroedOut(entry.getKeyClaimant());
+ }
+
+ @Test
+ public void destroy_overwritesLskfHashMemory() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+ storage.add(TEST_USER_ID, entry);
+
+ storage.destroy();
+
+ assertZeroedOut(entry.getLskfHash());
+ }
+
+ @Test
+ public void destroy_overwritesKeyClaimantMemory() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+ storage.add(TEST_USER_ID, entry);
+
+ storage.destroy();
+
+ assertZeroedOut(entry.getKeyClaimant());
+ }
+
+ private static void assertZeroedOut(byte[] bytes) {
+ for (byte b : bytes) {
+ if (b != (byte) 0) {
+ fail("Bytes were not all zeroed out.");
+ }
+ }
+ }
+
+ private static byte[] lskfHashFixture() {
+ return Arrays.copyOf(TEST_LSKF_HASH, TEST_LSKF_HASH.length);
+ }
+
+ private static byte[] keyClaimantFixture() {
+ return Arrays.copyOf(TEST_KEY_CLAIMANT, TEST_KEY_CLAIMANT.length);
+ }
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 337fd50..0959df2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -451,9 +451,9 @@
private DisplayCutout createDisplayCutoutFromRect(int left, int top, int right, int bottom) {
return DisplayCutout.fromBoundingPolygon(Arrays.asList(
new Point(left, top),
- new Point (left, bottom),
- new Point (right, bottom),
- new Point (left, bottom)
+ new Point(left, bottom),
+ new Point(right, bottom),
+ new Point(right, top)
));
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c0685f9..44f5551 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -67,6 +67,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
@@ -389,11 +390,13 @@
}
public void switchUser(int userHandle) {
- synchronized (this) {
- mCurUser = userHandle;
- mCurUserUnlocked = false;
- switchImplementationIfNeededLocked(false);
- }
+ FgThread.getHandler().post(() -> {
+ synchronized (this) {
+ mCurUser = userHandle;
+ mCurUserUnlocked = false;
+ switchImplementationIfNeededLocked(false);
+ }
+ });
}
void switchImplementationIfNeeded(boolean force) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 93c0c51..9e77992 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1176,12 +1176,14 @@
}
/**
- * Returns the NAI. Return null if NAI is not available.
- *
+ * Returns the Network Access Identifier (NAI). Return null if NAI is not available.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
- /** {@hide}*/
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public String getNai() {
- return getNai(getSlotIndex());
+ return getNaiBySubscriberId(getSubId());
}
/**
@@ -1192,11 +1194,18 @@
/** {@hide}*/
public String getNai(int slotIndex) {
int[] subId = SubscriptionManager.getSubId(slotIndex);
+ if (subId == null) {
+ return null;
+ }
+ return getNaiBySubscriberId(subId[0]);
+ }
+
+ private String getNaiBySubscriberId(int subId) {
try {
IPhoneSubInfo info = getSubscriberInfo();
if (info == null)
return null;
- String nai = info.getNaiForSubscriber(subId[0], mContext.getOpPackageName());
+ String nai = info.getNaiForSubscriber(subId, mContext.getOpPackageName());
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Rlog.v(TAG, "Nai = " + nai);
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 2507cfee..973df31 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -168,12 +168,10 @@
public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
private final Context mContext;
- private final IEuiccController mController;
/** @hide */
public EuiccManager(Context context) {
mContext = context;
- mController = IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
}
/**
@@ -189,7 +187,7 @@
public boolean isEnabled() {
// In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
// restrictions.
- return mController != null;
+ return getIEuiccController() != null;
}
/**
@@ -206,7 +204,7 @@
return null;
}
try {
- return mController.getEid();
+ return getIEuiccController().getEid();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -232,7 +230,7 @@
return;
}
try {
- mController.downloadSubscription(subscription, switchAfterDownload,
+ getIEuiccController().downloadSubscription(subscription, switchAfterDownload,
mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -296,7 +294,7 @@
return;
}
try {
- mController.continueOperation(resolutionIntent, resolutionExtras);
+ getIEuiccController().continueOperation(resolutionIntent, resolutionExtras);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -328,7 +326,7 @@
return;
}
try {
- mController.getDownloadableSubscriptionMetadata(
+ getIEuiccController().getDownloadableSubscriptionMetadata(
subscription, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -358,7 +356,7 @@
return;
}
try {
- mController.getDefaultDownloadableSubscriptionList(
+ getIEuiccController().getDefaultDownloadableSubscriptionList(
mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -377,7 +375,7 @@
return null;
}
try {
- return mController.getEuiccInfo();
+ return getIEuiccController().getEuiccInfo();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -402,7 +400,7 @@
return;
}
try {
- mController.deleteSubscription(
+ getIEuiccController().deleteSubscription(
subscriptionId, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -429,7 +427,7 @@
return;
}
try {
- mController.switchToSubscription(
+ getIEuiccController().switchToSubscription(
subscriptionId, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -455,7 +453,8 @@
return;
}
try {
- mController.updateSubscriptionNickname(subscriptionId, nickname, callbackIntent);
+ getIEuiccController().updateSubscriptionNickname(
+ subscriptionId, nickname, callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -477,7 +476,7 @@
return;
}
try {
- mController.eraseSubscriptions(callbackIntent);
+ getIEuiccController().eraseSubscriptions(callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -507,7 +506,7 @@
return;
}
try {
- mController.retainSubscriptionsForFactoryReset(callbackIntent);
+ getIEuiccController().retainSubscriptionsForFactoryReset(callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -520,4 +519,8 @@
// Caller canceled the callback; do nothing.
}
}
+
+ private static IEuiccController getIEuiccController() {
+ return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
+ }
}
diff --git a/test-mock/Android.mk b/test-mock/Android.mk
index a761a07..7926a77 100644
--- a/test-mock/Android.mk
+++ b/test-mock/Android.mk
@@ -16,7 +16,12 @@
LOCAL_PATH:= $(call my-dir)
-android_test_mock_source_files := $(call all-java-files-under, src/android/test/mock)
+# Includes the main framework source to ensure that doclava has access to the
+# visibility information for the base classes of the mock classes. Without it
+# otherwise hidden methods could be visible.
+android_test_mock_source_files := \
+ $(call all-java-files-under, src/android/test/mock) \
+ $(call all-java-files-under, ../core/java) \
# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
@@ -67,6 +72,8 @@
LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp)
android_test_mock_gen_stamp :=
+LOCAL_SDK_VERSION := current
+
include $(BUILD_STATIC_JAVA_LIBRARY)
# Archive a copy of the classes.jar in SDK build.
@@ -104,4 +111,91 @@
@echo Copying removed.txt
$(hide) $(ACP) $(ANDROID_TEST_MOCK_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_REMOVED_API_FILE)
+# Generate the stub source files for android.test.mock.stubs-system
+# =================================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(android_test_mock_source_files)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src/android/test/mock
+
+ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/api.txt
+ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/removed.txt
+
+ANDROID_TEST_MOCK_SYSTEM_API_FILE := $(LOCAL_PATH)/api/android-test-mock-system-current.txt
+ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-mock-system-removed.txt
+
+LOCAL_DROIDDOC_OPTIONS:= \
+ -stubpackages android.test.mock \
+ -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/src \
+ -nodocs \
+ -showAnnotation android.annotation.SystemApi \
+ -api $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) \
+ -removedApi $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE) \
+
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_MODULE := android-test-mock-system-api-stubs-gen
+
+include $(BUILD_DROIDDOC)
+
+# Remember the target that will trigger the code generation.
+android_test_mock_system_gen_stamp := $(full_target)
+
+# Add some additional dependencies
+$(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE): $(full_target)
+$(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE): $(full_target)
+
+# Build the android.test.mock.stubs-system library
+# ================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.test.mock.stubs-system
+
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+
+# Make sure to run droiddoc first to generate the stub source files.
+LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_system_gen_stamp)
+android_test_mock_system_gen_stamp :=
+
+LOCAL_SDK_VERSION := system_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Archive a copy of the classes.jar in SDK build.
+$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.mock.stubs_system.jar)
+
+# Check that the android.test.mock.stubs-system library has not changed
+# =====================================================================
+
+# Check that the API we're building hasn't changed from the not-yet-released
+# SDK version.
+$(eval $(call check-api, \
+ check-android-test-mock-system-api-current, \
+ $(ANDROID_TEST_MOCK_SYSTEM_API_FILE), \
+ $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE), \
+ $(ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE), \
+ $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE), \
+ -error 2 -error 3 -error 4 -error 5 -error 6 \
+ -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+ -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
+ -error 25 -error 26 -error 27, \
+ cat $(LOCAL_PATH)/api/apicheck_msg_android_test_mock-system.txt, \
+ check-android-test-mock-system-api, \
+ $(call doc-timestamp-for,android-test-mock-system-api-stubs-gen) \
+ ))
+
+.PHONY: check-android-test-mock-system-api
+checkapi: check-android-test-mock-system-api
+
+.PHONY: update-android-test-mock-system-api
+update-api: update-android-test-mock-system-api
+
+update-android-test-mock-system-api: $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) | $(ACP)
+ @echo Copying current.txt
+ $(hide) $(ACP) $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) $(ANDROID_TEST_MOCK_SYSTEM_API_FILE)
+ @echo Copying removed.txt
+ $(hide) $(ACP) $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE)
+
endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
+
diff --git a/test-mock/api/android-test-mock-current.txt b/test-mock/api/android-test-mock-current.txt
index 3aa350b..48a1f80 100644
--- a/test-mock/api/android-test-mock-current.txt
+++ b/test-mock/api/android-test-mock-current.txt
@@ -10,7 +10,6 @@
ctor public MockContentProvider(android.content.Context, java.lang.String, java.lang.String, android.content.pm.PathPermission[]);
method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>);
method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
- method public final android.content.IContentProvider getIContentProvider();
method public java.lang.String getType(android.net.Uri);
method public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public boolean onCreate();
@@ -22,37 +21,26 @@
public class MockContentResolver extends android.content.ContentResolver {
ctor public MockContentResolver();
ctor public MockContentResolver(android.content.Context);
- method protected android.content.IContentProvider acquireProvider(android.content.Context, java.lang.String);
- method protected android.content.IContentProvider acquireUnstableProvider(android.content.Context, java.lang.String);
method public void addProvider(java.lang.String, android.content.ContentProvider);
- method public boolean releaseProvider(android.content.IContentProvider);
- method public boolean releaseUnstableProvider(android.content.IContentProvider);
- method public void unstableProviderDied(android.content.IContentProvider);
}
public class MockContext extends android.content.Context {
ctor public MockContext();
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
- method public boolean canLoadUnsafeResources();
method public int checkCallingOrSelfPermission(java.lang.String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
method public int checkCallingPermission(java.lang.String);
method public int checkCallingUriPermission(android.net.Uri, int);
method public int checkPermission(java.lang.String, int, int);
- method public int checkPermission(java.lang.String, int, int, android.os.IBinder);
method public int checkSelfPermission(java.lang.String);
method public int checkUriPermission(android.net.Uri, int, int, int);
- method public int checkUriPermission(android.net.Uri, int, int, int, android.os.IBinder);
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public void clearWallpaper();
- method public android.content.Context createApplicationContext(android.content.pm.ApplicationInfo, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.Context createCredentialProtectedStorageContext();
method public android.content.Context createDeviceProtectedStorageContext();
method public android.content.Context createDisplayContext(android.view.Display);
method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.Context createPackageContextAsUser(java.lang.String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.String[] databaseList();
method public boolean deleteDatabase(java.lang.String);
method public boolean deleteFile(java.lang.String);
@@ -68,7 +56,6 @@
method public android.content.Context getApplicationContext();
method public android.content.pm.ApplicationInfo getApplicationInfo();
method public android.content.res.AssetManager getAssets();
- method public java.lang.String getBasePackageName();
method public java.io.File getCacheDir();
method public java.lang.ClassLoader getClassLoader();
method public java.io.File getCodeCacheDir();
@@ -76,8 +63,6 @@
method public java.io.File getDataDir();
method public java.io.File getDatabasePath(java.lang.String);
method public java.io.File getDir(java.lang.String, int);
- method public android.view.Display getDisplay();
- method public android.view.DisplayAdjustments getDisplayAdjustments(int);
method public java.io.File getExternalCacheDir();
method public java.io.File[] getExternalCacheDirs();
method public java.io.File getExternalFilesDir(java.lang.String);
@@ -89,25 +74,19 @@
method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
- method public java.lang.String getOpPackageName();
method public java.lang.String getPackageCodePath();
method public android.content.pm.PackageManager getPackageManager();
method public java.lang.String getPackageName();
method public java.lang.String getPackageResourcePath();
- method public java.io.File getPreloadsFileCache();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
- method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
- method public java.io.File getSharedPreferencesPath(java.lang.String);
method public java.lang.Object getSystemService(java.lang.String);
method public java.lang.String getSystemServiceName(java.lang.Class<?>);
method public android.content.res.Resources.Theme getTheme();
- method public int getUserId();
method public android.graphics.drawable.Drawable getWallpaper();
method public int getWallpaperDesiredMinimumHeight();
method public int getWallpaperDesiredMinimumWidth();
method public void grantUriPermission(java.lang.String, android.net.Uri, int);
- method public boolean isCredentialProtectedStorage();
method public boolean isDeviceProtectedStorage();
method public boolean moveDatabaseFrom(android.content.Context, java.lang.String);
method public boolean moveSharedPreferencesFrom(android.content.Context, java.lang.String);
@@ -120,31 +99,19 @@
method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, int);
method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler);
method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, int);
- method public android.content.Intent registerReceiverAsUser(android.content.BroadcastReceiver, android.os.UserHandle, android.content.IntentFilter, java.lang.String, android.os.Handler);
- method public void reloadSharedPreferences();
method public void removeStickyBroadcast(android.content.Intent);
method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
method public void revokeUriPermission(android.net.Uri, int);
method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
method public void sendBroadcast(android.content.Intent);
method public void sendBroadcast(android.content.Intent, java.lang.String);
- method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
- method public void sendBroadcast(android.content.Intent, java.lang.String, int);
method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String);
- method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
- method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int);
- method public void sendBroadcastMultiplePermissions(android.content.Intent, java.lang.String[]);
method public void sendOrderedBroadcast(android.content.Intent, java.lang.String);
method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void sendStickyBroadcast(android.content.Intent);
method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
- method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.os.Bundle);
method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void setTheme(int);
@@ -155,17 +122,13 @@
method public void startActivity(android.content.Intent);
method public void startActivity(android.content.Intent, android.os.Bundle);
method public android.content.ComponentName startForegroundService(android.content.Intent);
- method public android.content.ComponentName startForegroundServiceAsUser(android.content.Intent, android.os.UserHandle);
method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle);
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public android.content.ComponentName startService(android.content.Intent);
- method public android.content.ComponentName startServiceAsUser(android.content.Intent, android.os.UserHandle);
method public boolean stopService(android.content.Intent);
- method public boolean stopServiceAsUser(android.content.Intent, android.os.UserHandle);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
- method public void updateDisplay(int);
}
public deprecated class MockCursor implements android.database.Cursor {
@@ -221,8 +184,6 @@
public deprecated class MockPackageManager extends android.content.pm.PackageManager {
ctor public MockPackageManager();
- method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
- method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public void addPackageToPreferred(java.lang.String);
method public boolean addPermission(android.content.pm.PermissionInfo);
method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
@@ -232,19 +193,10 @@
method public int checkPermission(java.lang.String, java.lang.String);
method public int checkSignatures(java.lang.String, java.lang.String);
method public int checkSignatures(int, int);
- method public void clearApplicationUserData(java.lang.String, android.content.pm.IPackageDataObserver);
- method public void clearCrossProfileIntentFilters(int);
method public void clearInstantAppCookie();
method public void clearPackagePreferredActivities(java.lang.String);
method public java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]);
- method public void deleteApplicationCacheFiles(java.lang.String, android.content.pm.IPackageDataObserver);
- method public void deleteApplicationCacheFilesAsUser(java.lang.String, int, android.content.pm.IPackageDataObserver);
- method public void deletePackage(java.lang.String, android.content.pm.IPackageDeleteObserver, int);
- method public void deletePackageAsUser(java.lang.String, android.content.pm.IPackageDeleteObserver, int, int);
method public void extendVerificationTimeout(int, int, long);
- method public void flushPackageRestrictionsAsUser(int);
- method public void freeStorage(java.lang.String, long, android.content.IntentSender);
- method public void freeStorageAndNotify(java.lang.String, long, android.content.pm.IPackageDataObserver);
method public android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.graphics.drawable.Drawable getActivityBanner(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.graphics.drawable.Drawable getActivityIcon(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -257,148 +209,75 @@
method public android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationBanner(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int getApplicationEnabledSetting(java.lang.String);
- method public boolean getApplicationHiddenSettingAsUser(java.lang.String, android.os.UserHandle);
method public android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationIcon(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.ApplicationInfo getApplicationInfoAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ChangedPackages getChangedPackages(int);
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
- method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public android.content.ComponentName getHomeActivities(java.util.List<android.content.pm.ResolveInfo>);
- method public int getInstallReason(java.lang.String, android.os.UserHandle);
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
- method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
- method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method public java.lang.String getInstallerPackageName(java.lang.String);
- method public java.lang.String getInstantAppAndroidId(java.lang.String, android.os.UserHandle);
method public byte[] getInstantAppCookie();
method public int getInstantAppCookieMaxBytes();
- method public int getInstantAppCookieMaxSize();
- method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
- method public android.content.ComponentName getInstantAppInstallerComponent();
- method public android.content.ComponentName getInstantAppResolverSettingsComponent();
- method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
- method public int getIntentVerificationStatusAsUser(java.lang.String, int);
- method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
- method public int getMoveStatus(int);
method public java.lang.String getNameForUid(int);
- method public java.lang.String[] getNamesForUids(int[]);
- method public java.util.List<android.os.storage.VolumeInfo> getPackageCandidateVolumes(android.content.pm.ApplicationInfo);
- method public android.os.storage.VolumeInfo getPackageCurrentVolume(android.content.pm.ApplicationInfo);
method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.PackageInfo getPackageInfoAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInstaller getPackageInstaller();
- method public void getPackageSizeInfoAsUser(java.lang.String, int, android.content.pm.IPackageStatsObserver);
method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int getPackageUidAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int getPackageUidAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.String[] getPackagesForUid(int);
method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
- method public java.lang.String getPermissionControllerPackageName();
- method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
method public java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
- method public java.util.List<android.os.storage.VolumeInfo> getPrimaryStorageCandidateVolumes();
- method public android.os.storage.VolumeInfo getPrimaryStorageCurrentVolume();
method public android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.res.Resources getResourcesForApplicationAsUser(java.lang.String, int);
method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public java.lang.String getServicesSystemSharedLibraryPackageName();
method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
- method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibrariesAsUser(int, int);
- method public java.lang.String getSharedSystemSharedLibraryPackageName();
- method public android.content.pm.KeySet getSigningKeySet(java.lang.String);
method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public java.lang.String[] getSystemSharedLibraryNames();
method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public int getUidForSharedUser(java.lang.String);
- method public android.graphics.drawable.Drawable getUserBadgeForDensity(android.os.UserHandle, int);
- method public android.graphics.drawable.Drawable getUserBadgeForDensityNoBackground(android.os.UserHandle, int);
method public android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int);
method public android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle);
method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
- method public android.content.pm.VerifierDeviceIdentity getVerifierDeviceIdentity();
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public boolean hasSystemFeature(java.lang.String);
method public boolean hasSystemFeature(java.lang.String, int);
- method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int installExistingPackageAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void installPackage(android.net.Uri, android.app.PackageInstallObserver, int, java.lang.String);
method public boolean isInstantApp();
method public boolean isInstantApp(java.lang.String);
- method public boolean isPackageAvailable(java.lang.String);
- method public boolean isPackageSuspendedForUser(java.lang.String, int);
- method public boolean isPermissionReviewModeEnabled();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
- method public boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
- method public boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
- method public boolean isUpgrade();
- method public android.graphics.drawable.Drawable loadItemIcon(android.content.pm.PackageItemInfo, android.content.pm.ApplicationInfo);
- method public android.graphics.drawable.Drawable loadUnbadgedItemIcon(android.content.pm.PackageItemInfo, android.content.pm.ApplicationInfo);
- method public int movePackage(java.lang.String, android.os.storage.VolumeInfo);
- method public int movePrimaryStorage(android.os.storage.VolumeInfo);
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
- method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, int);
method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(android.content.Intent, int);
- method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(android.content.Intent, int, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
- method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(android.content.Intent, int, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
- method public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(android.content.Intent, int, int);
method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
- method public void registerMoveCallback(android.content.pm.PackageManager.MoveCallback, android.os.Handler);
- method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public void removePackageFromPreferred(java.lang.String);
method public void removePermission(java.lang.String);
- method public void replacePreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
- method public android.content.pm.ResolveInfo resolveActivityAsUser(android.content.Intent, int, int);
method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
- method public android.content.pm.ProviderInfo resolveContentProviderAsUser(java.lang.String, int, int);
method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
- method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public void setApplicationCategoryHint(java.lang.String, int);
method public void setApplicationEnabledSetting(java.lang.String, int, int);
- method public boolean setApplicationHiddenSettingAsUser(java.lang.String, boolean, android.os.UserHandle);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
- method public boolean setInstantAppCookie(byte[]);
- method public java.lang.String[] setPackagesSuspendedAsUser(java.lang.String[], boolean, int);
- method public void setUpdateAvailable(java.lang.String, boolean);
- method public boolean shouldShowRequestPermissionRationale(java.lang.String);
- method public void unregisterMoveCallback(android.content.pm.PackageManager.MoveCallback);
method public void updateInstantAppCookie(byte[]);
- method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
- method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
- method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
method public void verifyPendingInstall(int, int);
}
diff --git a/test-mock/api/android-test-mock-removed.txt b/test-mock/api/android-test-mock-removed.txt
index 5b358cf..bd109a8 100644
--- a/test-mock/api/android-test-mock-removed.txt
+++ b/test-mock/api/android-test-mock-removed.txt
@@ -8,6 +8,7 @@
public deprecated class MockPackageManager extends android.content.pm.PackageManager {
method public deprecated java.lang.String getDefaultBrowserPackageName(int);
method public deprecated boolean setDefaultBrowserPackageName(java.lang.String, int);
+ method public boolean setInstantAppCookie(byte[]);
}
}
diff --git a/test-mock/api/android-test-mock-system-current.txt b/test-mock/api/android-test-mock-system-current.txt
new file mode 100644
index 0000000..20401a5
--- /dev/null
+++ b/test-mock/api/android-test-mock-system-current.txt
@@ -0,0 +1,38 @@
+package android.test.mock {
+
+ public class MockContext extends android.content.Context {
+ method public android.content.Context createCredentialProtectedStorageContext();
+ method public java.io.File getPreloadsFileCache();
+ method public boolean isCredentialProtectedStorage();
+ method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
+ method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
+ method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+ }
+
+ public deprecated class MockPackageManager extends android.content.pm.PackageManager {
+ method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method public java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
+ method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
+ method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+ method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
+ method public android.content.ComponentName getInstantAppInstallerComponent();
+ method public android.content.ComponentName getInstantAppResolverSettingsComponent();
+ method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+ method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
+ method public int getIntentVerificationStatusAsUser(java.lang.String, int);
+ method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
+ method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+ method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
+ method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+ method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
+ method public void setUpdateAvailable(java.lang.String, boolean);
+ method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
+ method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
+ method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+ }
+
+}
+
diff --git a/test-mock/api/android-test-mock-system-removed.txt b/test-mock/api/android-test-mock-system-removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test-mock/api/android-test-mock-system-removed.txt
diff --git a/test-mock/api/apicheck_msg_android_test_mock-system.txt b/test-mock/api/apicheck_msg_android_test_mock-system.txt
new file mode 100644
index 0000000..3a97117
--- /dev/null
+++ b/test-mock/api/apicheck_msg_android_test_mock-system.txt
@@ -0,0 +1,17 @@
+
+******************************
+You have tried to change the API from what has been previously approved.
+
+To make these errors go away, you have two choices:
+ 1) You can add "@hide" javadoc comments to the methods, etc. listed in the
+ errors above.
+
+ 2) You can update android-test-mock-current.txt by executing the following command:
+ make update-android-test-mock-system-api
+
+ To submit the revised android-test-mock-system-current.txt to the main Android repository,
+ you will need approval.
+******************************
+
+
+
diff --git a/tests/AppLaunch/Android.mk b/tests/AppLaunch/Android.mk
index 09739e5..917293f 100644
--- a/tests/AppLaunch/Android.mk
+++ b/tests/AppLaunch/Android.mk
@@ -13,6 +13,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
# Use the following include to make our test apk.
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index f38a9a3..5d1e10e 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -475,4 +475,26 @@
testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
udpEncapResp.fileDescriptor.close();
}
+
+ @Test
+ public void testOpenUdpEncapsulationSocketCallsSetEncapSocketOwner() throws Exception {
+ IpSecUdpEncapResponse udpEncapResp =
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+
+ FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
+ ArgumentMatcher<FileDescriptor> fdMatcher = (arg) -> {
+ try {
+ StructStat sockStat = Os.fstat(sockFd);
+ StructStat argStat = Os.fstat(arg);
+
+ return sockStat.st_ino == argStat.st_ino
+ && sockStat.st_dev == argStat.st_dev;
+ } catch (ErrnoException e) {
+ return false;
+ }
+ };
+
+ verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid()));
+ mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+ }
}
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
index 543c652..a76616f 100644
--- a/tests/utils/testutils/Android.mk
+++ b/tests/utils/testutils/Android.mk
@@ -24,9 +24,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under,java)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- mockito-target-minus-junit4
+ android-support-test
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ android.test.base \
+ android.test.mock \
+ mockito-target-minus-junit4 \
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 0f6fb50..5831875 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -294,36 +294,42 @@
printer->Print("/");
printer->Print(entry->name);
- switch (entry->symbol_status.state) {
- case SymbolState::kPublic:
+ switch (entry->visibility.level) {
+ case Visibility::Level::kPublic:
printer->Print(" PUBLIC");
break;
- case SymbolState::kPrivate:
+ case Visibility::Level::kPrivate:
printer->Print(" _PRIVATE_");
break;
- case SymbolState::kUndefined:
+ case Visibility::Level::kUndefined:
// Print nothing.
break;
}
+ if (entry->overlayable) {
+ printer->Print(" OVERLAYABLE");
+ }
+
printer->Println();
- printer->Indent();
- for (const auto& value : entry->values) {
- printer->Print("(");
- printer->Print(value->config.to_string());
- printer->Print(") ");
- value->value->Accept(&headline_printer);
- if (options.show_sources && !value->value->GetSource().path.empty()) {
- printer->Print(" src=");
- printer->Print(value->value->GetSource().to_string());
- }
- printer->Println();
+ if (options.show_values) {
printer->Indent();
- value->value->Accept(&body_printer);
+ for (const auto& value : entry->values) {
+ printer->Print("(");
+ printer->Print(value->config.to_string());
+ printer->Print(") ");
+ value->value->Accept(&headline_printer);
+ if (options.show_sources && !value->value->GetSource().path.empty()) {
+ printer->Print(" src=");
+ printer->Print(value->value->GetSource().to_string());
+ }
+ printer->Println();
+ printer->Indent();
+ value->value->Accept(&body_printer);
+ printer->Undent();
+ }
printer->Undent();
}
- printer->Undent();
}
printer->Undent();
}
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index 3c1ee4c..6209a04 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -29,6 +29,7 @@
struct DebugPrintTableOptions {
bool show_sources = false;
+ bool show_values = true;
};
struct Debug {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 4cc60a8..24b28dd 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -30,7 +30,7 @@
#include "util/Util.h"
#include "xml/XmlPullParser.h"
-using android::StringPiece;
+using ::android::StringPiece;
namespace aapt {
@@ -90,9 +90,12 @@
ConfigDescription config;
std::string product;
Source source;
+
ResourceId id;
- Maybe<SymbolState> symbol_state;
+ Visibility::Level visibility_level = Visibility::Level::kUndefined;
bool allow_new = false;
+ bool overlayable = false;
+
std::string comment;
std::unique_ptr<Value> value;
std::list<ParsedResource> child_resources;
@@ -106,24 +109,41 @@
res->comment = trimmed_comment.to_string();
}
- if (res->symbol_state) {
- Symbol symbol;
- symbol.state = res->symbol_state.value();
- symbol.source = res->source;
- symbol.comment = res->comment;
- symbol.allow_new = res->allow_new;
- if (!table->SetSymbolState(res->name, res->id, symbol, diag)) {
+ if (res->visibility_level != Visibility::Level::kUndefined) {
+ Visibility visibility;
+ visibility.level = res->visibility_level;
+ visibility.source = res->source;
+ visibility.comment = res->comment;
+ if (!table->SetVisibilityWithId(res->name, visibility, res->id, diag)) {
return false;
}
}
- if (res->value) {
+ if (res->allow_new) {
+ AllowNew allow_new;
+ allow_new.source = res->source;
+ allow_new.comment = res->comment;
+ if (!table->SetAllowNew(res->name, allow_new, diag)) {
+ return false;
+ }
+ }
+
+ if (res->overlayable) {
+ Overlayable overlayable;
+ overlayable.source = res->source;
+ overlayable.comment = res->comment;
+ if (!table->SetOverlayable(res->name, overlayable, diag)) {
+ return false;
+ }
+ }
+
+ if (res->value != nullptr) {
// Attach the comment, source and config to the value.
res->value->SetComment(std::move(res->comment));
res->value->SetSource(std::move(res->source));
- if (!table->AddResource(res->name, res->id, res->config, res->product, std::move(res->value),
- diag)) {
+ if (!table->AddResourceWithId(res->name, res->id, res->config, res->product,
+ std::move(res->value), diag)) {
return false;
}
}
@@ -601,8 +621,7 @@
// Process the raw value.
std::unique_ptr<Item> processed_item =
- ResourceUtils::TryParseItemForAttribute(raw_value, type_mask,
- on_create_reference);
+ ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference);
if (processed_item) {
// Fix up the reference.
if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
@@ -689,8 +708,7 @@
return true;
}
-bool ResourceParser::ParsePublic(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
+bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diag_->Warn(DiagMessage(out_resource->source)
<< "ignoring configuration '" << out_resource->config << "' for <public> tag");
@@ -728,7 +746,7 @@
out_resource->value = util::make_unique<Id>();
}
- out_resource->symbol_state = SymbolState::kPublic;
+ out_resource->visibility_level = Visibility::Level::kPublic;
return true;
}
@@ -818,7 +836,7 @@
child_resource.id = next_id;
child_resource.comment = std::move(comment);
child_resource.source = item_source;
- child_resource.symbol_state = SymbolState::kPublic;
+ child_resource.visibility_level = Visibility::Level::kPublic;
out_resource->child_resources.push_back(std::move(child_resource));
next_id.id += 1;
@@ -864,7 +882,7 @@
return false;
}
- out_resource->symbol_state = SymbolState::kPrivate;
+ out_resource->visibility_level = Visibility::Level::kPrivate;
return true;
}
@@ -920,8 +938,12 @@
continue;
}
- // TODO(b/64980941): Mark the symbol as overlayable and allow marking which entity can overlay
- // the resource (system/app).
+ ParsedResource child_resource;
+ child_resource.name.type = *type;
+ child_resource.name.entry = maybe_name.value().to_string();
+ child_resource.source = item_source;
+ child_resource.overlayable = true;
+ out_resource->child_resources.push_back(std::move(child_resource));
xml::XmlPullParser::SkipCurrentElement(parser);
} else if (!ShouldIgnoreElement(element_namespace, element_name)) {
@@ -932,10 +954,9 @@
return !error;
}
-bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
+bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (ParseSymbolImpl(parser, out_resource)) {
- out_resource->symbol_state = SymbolState::kUndefined;
+ out_resource->visibility_level = Visibility::Level::kUndefined;
out_resource->allow_new = true;
return true;
}
@@ -1395,9 +1416,8 @@
ParsedResource* out_resource) {
out_resource->name.type = ResourceType::kStyleable;
- // Declare-styleable is kPrivate by default, because it technically only
- // exists in R.java.
- out_resource->symbol_state = SymbolState::kPublic;
+ // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
+ out_resource->visibility_level = Visibility::Level::kPublic;
// Declare-styleable only ends up in default config;
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 9a5cd3e..618c8ed 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -29,8 +29,8 @@
using ::aapt::io::StringInputStream;
using ::aapt::test::StrValueEq;
using ::aapt::test::ValueEq;
-using ::android::ResTable_map;
using ::android::Res_value;
+using ::android::ResTable_map;
using ::android::StringPiece;
using ::testing::Eq;
using ::testing::IsEmpty;
@@ -38,6 +38,7 @@
using ::testing::NotNull;
using ::testing::Pointee;
using ::testing::SizeIs;
+using ::testing::StrEq;
namespace aapt {
@@ -482,7 +483,7 @@
Maybe<ResourceTable::SearchResult> result =
table_.FindResource(test::ParseNameOrDie("styleable/foo"));
ASSERT_TRUE(result);
- EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+ EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar");
ASSERT_THAT(attr, NotNull());
@@ -718,6 +719,26 @@
EXPECT_THAT(actual_id, Eq(ResourceId(0x01010041)));
}
+TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) {
+ std::string input = R"(
+ <!-- private -->
+ <java-symbol type="string" name="foo" />
+ <!-- public -->
+ <public type="string" name="foo" id="0x01020000" />
+ <!-- private2 -->
+ <java-symbol type="string" name="foo" />)";
+ ASSERT_TRUE(TestParse(input));
+
+ Maybe<ResourceTable::SearchResult> result =
+ table_.FindResource(test::ParseNameOrDie("string/foo"));
+ ASSERT_TRUE(result);
+
+ ResourceEntry* entry = result.value().entry;
+ ASSERT_THAT(entry, NotNull());
+ EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kPublic));
+ EXPECT_THAT(entry->visibility.comment, StrEq("public"));
+}
+
TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
ASSERT_TRUE(TestParse(R"(<item type="layout" name="foo">@layout/bar</item>)"));
ASSERT_FALSE(TestParse(R"(<item type="layout" name="bar">"this is a string"</item>)"));
@@ -731,8 +752,8 @@
ASSERT_TRUE(result);
const ResourceEntry* entry = result.value().entry;
ASSERT_THAT(entry, NotNull());
- EXPECT_THAT(entry->symbol_status.state, Eq(SymbolState::kUndefined));
- EXPECT_TRUE(entry->symbol_status.allow_new);
+ EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kUndefined));
+ EXPECT_TRUE(entry->allow_new);
}
TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
@@ -833,6 +854,22 @@
<item type="string" name="bar" />
</overlayable>)";
ASSERT_TRUE(TestParse(input));
+
+ Maybe<ResourceTable::SearchResult> search_result =
+ table_.FindResource(test::ParseNameOrDie("string/bar"));
+ ASSERT_TRUE(search_result);
+ ASSERT_THAT(search_result.value().entry, NotNull());
+ EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+ EXPECT_TRUE(search_result.value().entry->overlayable);
+}
+
+TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
+ std::string input = R"(
+ <overlayable>
+ <item type="string" name="foo" />
+ <item type="string" name="foo" />
+ </overlayable>)";
+ EXPECT_FALSE(TestParse(input));
}
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 0304e21..3172892 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -22,6 +22,7 @@
#include <tuple>
#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
#include "androidfw/ResourceTypes.h"
#include "ConfigDescription.h"
@@ -33,6 +34,7 @@
using ::aapt::text::IsValidResourceEntryName;
using ::android::StringPiece;
+using ::android::base::StringPrintf;
namespace aapt {
@@ -45,7 +47,7 @@
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
}
-ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) const {
const auto last = packages.end();
auto iter = std::lower_bound(packages.begin(), last, name,
less_than_struct_with_name<ResourceTablePackage>);
@@ -55,7 +57,7 @@
return nullptr;
}
-ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) {
+ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) const {
for (auto& package : packages) {
if (package->id && package->id.value() == id) {
return package.get();
@@ -206,30 +208,23 @@
return results;
}
-/**
- * The default handler for collisions.
- *
- * Typically, a weak value will be overridden by a strong value. An existing
- * weak
- * value will not be overridden by an incoming weak value.
- *
- * There are some exceptions:
- *
- * Attributes: There are two types of Attribute values: USE and DECL.
- *
- * USE is anywhere an Attribute is declared without a format, and in a place
- * that would
- * be legal to declare if the Attribute already existed. This is typically in a
- * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also
- * weak.
- *
- * DECL is an absolute declaration of an Attribute and specifies an explicit
- * format.
- *
- * A DECL will override a USE without error. Two DECLs must match in their
- * format for there to be
- * no error.
- */
+// The default handler for collisions.
+//
+// Typically, a weak value will be overridden by a strong value. An existing weak
+// value will not be overridden by an incoming weak value.
+//
+// There are some exceptions:
+//
+// Attributes: There are two types of Attribute values: USE and DECL.
+//
+// USE is anywhere an Attribute is declared without a format, and in a place that would
+// be legal to declare if the Attribute already existed. This is typically in a
+// <declare-styleable> tag. Attributes defined in a <declare-styleable> are also weak.
+//
+// DECL is an absolute declaration of an Attribute and specifies an explicit format.
+//
+// A DECL will override a USE without error. Two DECLs must match in their format for there to be
+// no error.
ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing,
Value* incoming) {
Attribute* existing_attr = ValueCast<Attribute>(existing);
@@ -287,14 +282,14 @@
return CollisionResult::kConflict;
}
-static StringPiece ValidateName(const StringPiece& name) {
+static StringPiece ResourceNameValidator(const StringPiece& name) {
if (!IsValidResourceEntryName(name)) {
return name;
}
return {};
}
-static StringPiece SkipValidateName(const StringPiece& /*name*/) {
+static StringPiece SkipNameValidator(const StringPiece& /*name*/) {
return {};
}
@@ -303,17 +298,14 @@
const StringPiece& product,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return AddResourceImpl(name, {}, config, product, std::move(value), ValidateName,
+ return AddResourceImpl(name, {}, config, product, std::move(value), ResourceNameValidator,
ResolveValueCollision, diag);
}
-bool ResourceTable::AddResource(const ResourceNameRef& name,
- const ResourceId& res_id,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return AddResourceImpl(name, res_id, config, product, std::move(value), ValidateName,
+bool ResourceTable::AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
+ const ConfigDescription& config, const StringPiece& product,
+ std::unique_ptr<Value> value, IDiagnostics* diag) {
+ return AddResourceImpl(name, res_id, config, product, std::move(value), ResourceNameValidator,
ResolveValueCollision, diag);
}
@@ -322,14 +314,14 @@
const Source& source,
const StringPiece& path,
IDiagnostics* diag) {
- return AddFileReferenceImpl(name, config, source, path, nullptr, ValidateName, diag);
+ return AddFileReferenceImpl(name, config, source, path, nullptr, ResourceNameValidator, diag);
}
-bool ResourceTable::AddFileReferenceAllowMangled(
- const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, const StringPiece& path, io::IFile* file,
- IDiagnostics* diag) {
- return AddFileReferenceImpl(name, config, source, path, file, SkipValidateName, diag);
+bool ResourceTable::AddFileReferenceMangled(const ResourceNameRef& name,
+ const ConfigDescription& config, const Source& source,
+ const StringPiece& path, io::IFile* file,
+ IDiagnostics* diag) {
+ return AddFileReferenceImpl(name, config, source, path, file, SkipNameValidator, diag);
}
bool ResourceTable::AddFileReferenceImpl(const ResourceNameRef& name,
@@ -344,88 +336,85 @@
name_validator, ResolveValueCollision, diag);
}
-bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipValidateName,
+bool ResourceTable::AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const StringPiece& product, std::unique_ptr<Value> value,
+ IDiagnostics* diag) {
+ return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipNameValidator,
ResolveValueCollision, diag);
}
-bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
- const ResourceId& id,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return AddResourceImpl(name, id, config, product, std::move(value), SkipValidateName,
+bool ResourceTable::AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
+ const ConfigDescription& config,
+ const StringPiece& product,
+ std::unique_ptr<Value> value, IDiagnostics* diag) {
+ return AddResourceImpl(name, id, config, product, std::move(value), SkipNameValidator,
ResolveValueCollision, diag);
}
+bool ResourceTable::ValidateName(NameValidator name_validator, const ResourceNameRef& name,
+ const Source& source, IDiagnostics* diag) {
+ const StringPiece bad_char = name_validator(name.entry);
+ if (!bad_char.empty()) {
+ diag->Error(DiagMessage(source) << "resource '" << name << "' has invalid entry name '"
+ << name.entry << "'. Invalid character '" << bad_char << "'");
+ return false;
+ }
+ return true;
+}
+
bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
const ConfigDescription& config, const StringPiece& product,
std::unique_ptr<Value> value, NameValidator name_validator,
- const CollisionResolverFunc& conflictResolver,
+ const CollisionResolverFunc& conflict_resolver,
IDiagnostics* diag) {
CHECK(value != nullptr);
CHECK(diag != nullptr);
- const StringPiece bad_char = name_validator(name.entry);
- if (!bad_char.empty()) {
- diag->Error(DiagMessage(value->GetSource()) << "resource '" << name
- << "' has invalid entry name '" << name.entry
- << "'. Invalid character '" << bad_char << "'");
-
+ const Source& source = value->GetSource();
+ if (!ValidateName(name_validator, name, source, diag)) {
return false;
}
ResourceTablePackage* package = FindOrCreatePackage(name.package);
if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
- diag->Error(DiagMessage(value->GetSource())
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but package '" << package->name << "' already has ID "
- << std::hex << (int)package->id.value() << std::dec);
+ diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id
+ << " but package '" << package->name << "' already has ID "
+ << StringPrintf("%02x", package->id.value()));
return false;
}
ResourceTableType* type = package->FindOrCreateType(name.type);
if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
- diag->Error(DiagMessage(value->GetSource())
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but type '" << type->type << "' already has ID "
- << std::hex << (int)type->id.value() << std::dec);
+ diag->Error(DiagMessage(source)
+ << "trying to add resource '" << name << "' with ID " << res_id << " but type '"
+ << type->type << "' already has ID " << StringPrintf("%02x", type->id.value()));
return false;
}
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
- diag->Error(DiagMessage(value->GetSource())
+ diag->Error(DiagMessage(source)
<< "trying to add resource '" << name << "' with ID " << res_id
<< " but resource already has ID "
- << ResourceId(package->id.value(), type->id.value(),
- entry->id.value()));
+ << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
return false;
}
ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product);
- if (!config_value->value) {
+ if (config_value->value == nullptr) {
// Resource does not exist, add it now.
config_value->value = std::move(value);
-
} else {
- switch (conflictResolver(config_value->value.get(), value.get())) {
+ switch (conflict_resolver(config_value->value.get(), value.get())) {
case CollisionResult::kTakeNew:
// Take the incoming value.
config_value->value = std::move(value);
break;
case CollisionResult::kConflict:
- diag->Error(DiagMessage(value->GetSource())
- << "duplicate value for resource '" << name << "' "
- << "with config '" << config << "'");
- diag->Error(DiagMessage(config_value->value->GetSource())
- << "resource previously defined here");
+ diag->Error(DiagMessage(source) << "duplicate value for resource '" << name << "' "
+ << "with config '" << config << "'");
+ diag->Error(DiagMessage(source) << "resource previously defined here");
return false;
case CollisionResult::kKeepOriginal:
@@ -441,52 +430,56 @@
return true;
}
-bool ResourceTable::SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, IDiagnostics* diag) {
- return SetSymbolStateImpl(name, res_id, symbol, ValidateName, diag);
+bool ResourceTable::SetVisibility(const ResourceNameRef& name, const Visibility& visibility,
+ IDiagnostics* diag) {
+ return SetVisibilityImpl(name, visibility, ResourceId{}, ResourceNameValidator, diag);
}
-bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name,
- const ResourceId& res_id,
- const Symbol& symbol,
- IDiagnostics* diag) {
- return SetSymbolStateImpl(name, res_id, symbol, SkipValidateName, diag);
+bool ResourceTable::SetVisibilityMangled(const ResourceNameRef& name, const Visibility& visibility,
+ IDiagnostics* diag) {
+ return SetVisibilityImpl(name, visibility, ResourceId{}, SkipNameValidator, diag);
}
-bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, NameValidator name_validator,
- IDiagnostics* diag) {
+bool ResourceTable::SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, IDiagnostics* diag) {
+ return SetVisibilityImpl(name, visibility, res_id, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetVisibilityWithIdMangled(const ResourceNameRef& name,
+ const Visibility& visibility,
+ const ResourceId& res_id, IDiagnostics* diag) {
+ return SetVisibilityImpl(name, visibility, res_id, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, NameValidator name_validator,
+ IDiagnostics* diag) {
CHECK(diag != nullptr);
- const StringPiece bad_char = name_validator(name.entry);
- if (!bad_char.empty()) {
- diag->Error(DiagMessage(symbol.source) << "resource '" << name << "' has invalid entry name '"
- << name.entry << "'. Invalid character '" << bad_char
- << "'");
+ const Source& source = visibility.source;
+ if (!ValidateName(name_validator, name, source, diag)) {
return false;
}
ResourceTablePackage* package = FindOrCreatePackage(name.package);
if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
- diag->Error(DiagMessage(symbol.source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but package '" << package->name << "' already has ID "
- << std::hex << (int)package->id.value() << std::dec);
+ diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id
+ << " but package '" << package->name << "' already has ID "
+ << StringPrintf("%02x", package->id.value()));
return false;
}
ResourceTableType* type = package->FindOrCreateType(name.type);
if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
- diag->Error(DiagMessage(symbol.source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but type '" << type->type << "' already has ID "
- << std::hex << (int)type->id.value() << std::dec);
+ diag->Error(DiagMessage(source)
+ << "trying to add resource '" << name << "' with ID " << res_id << " but type '"
+ << type->type << "' already has ID " << StringPrintf("%02x", type->id.value()));
return false;
}
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
- diag->Error(DiagMessage(symbol.source)
+ diag->Error(DiagMessage(source)
<< "trying to add resource '" << name << "' with ID " << res_id
<< " but resource already has ID "
<< ResourceId(package->id.value(), type->id.value(), entry->id.value()));
@@ -499,48 +492,96 @@
entry->id = res_id.entry_id();
}
- // Only mark the type state as public, it doesn't care about being private.
- if (symbol.state == SymbolState::kPublic) {
- type->symbol_status.state = SymbolState::kPublic;
+ // Only mark the type visibility level as public, it doesn't care about being private.
+ if (visibility.level == Visibility::Level::kPublic) {
+ type->visibility_level = Visibility::Level::kPublic;
}
- if (symbol.allow_new) {
- // This symbol can be added as a new resource when merging (if it belongs to an overlay).
- entry->symbol_status.allow_new = true;
- }
-
- if (symbol.state == SymbolState::kUndefined &&
- entry->symbol_status.state != SymbolState::kUndefined) {
+ if (visibility.level == Visibility::Level::kUndefined &&
+ entry->visibility.level != Visibility::Level::kUndefined) {
// We can't undefine a symbol (remove its visibility). Ignore.
return true;
}
- if (symbol.state == SymbolState::kPrivate &&
- entry->symbol_status.state == SymbolState::kPublic) {
+ if (visibility.level < entry->visibility.level) {
// We can't downgrade public to private. Ignore.
return true;
}
// This symbol definition takes precedence, replace.
- entry->symbol_status.state = symbol.state;
- entry->symbol_status.source = symbol.source;
- entry->symbol_status.comment = symbol.comment;
+ entry->visibility = visibility;
return true;
}
-Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) {
+bool ResourceTable::SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new,
+ IDiagnostics* diag) {
+ return SetAllowNewImpl(name, allow_new, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
+ IDiagnostics* diag) {
+ return SetAllowNewImpl(name, allow_new, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
+ NameValidator name_validator, IDiagnostics* diag) {
+ CHECK(diag != nullptr);
+
+ if (!ValidateName(name_validator, name, allow_new.source, diag)) {
+ return false;
+ }
+
+ ResourceTablePackage* package = FindOrCreatePackage(name.package);
+ ResourceTableType* type = package->FindOrCreateType(name.type);
+ ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+ entry->allow_new = allow_new;
+ return true;
+}
+
+bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+ IDiagnostics* diag) {
+ return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name,
+ const Overlayable& overlayable, IDiagnostics* diag) {
+ return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+ NameValidator name_validator, IDiagnostics* diag) {
+ CHECK(diag != nullptr);
+
+ if (!ValidateName(name_validator, name, overlayable.source, diag)) {
+ return false;
+ }
+
+ ResourceTablePackage* package = FindOrCreatePackage(name.package);
+ ResourceTableType* type = package->FindOrCreateType(name.type);
+ ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+ if (entry->overlayable) {
+ diag->Error(DiagMessage(overlayable.source)
+ << "duplicate overlayable declaration for resource '" << name << "'");
+ diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here");
+ return false;
+ }
+ entry->overlayable = overlayable;
+ return true;
+}
+
+Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const {
ResourceTablePackage* package = FindPackage(name.package);
- if (!package) {
+ if (package == nullptr) {
return {};
}
ResourceTableType* type = package->FindType(name.type);
- if (!type) {
+ if (type == nullptr) {
return {};
}
ResourceEntry* entry = type->FindEntry(name.entry);
- if (!entry) {
+ if (entry == nullptr) {
return {};
}
return SearchResult{package, type, entry};
@@ -552,23 +593,20 @@
ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id);
for (const auto& type : pkg->types) {
ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type);
- if (!new_type->id) {
- new_type->id = type->id;
- new_type->symbol_status = type->symbol_status;
- }
+ new_type->id = type->id;
+ new_type->visibility_level = type->visibility_level;
for (const auto& entry : type->entries) {
ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name);
- if (!new_entry->id) {
- new_entry->id = entry->id;
- new_entry->symbol_status = entry->symbol_status;
- }
+ new_entry->id = entry->id;
+ new_entry->visibility = entry->visibility;
+ new_entry->allow_new = entry->allow_new;
+ new_entry->overlayable = entry->overlayable;
for (const auto& config_value : entry->values) {
ResourceConfigValue* new_value =
new_entry->FindOrCreateValue(config_value->config, config_value->product);
- Value* value = config_value->value->Clone(&new_table->string_pool);
- new_value->value = std::unique_ptr<Value>(value);
+ new_value->value.reset(config_value->value->Clone(&new_table->string_pool));
}
}
}
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index d5db67e..eaa2d0b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -38,40 +38,40 @@
namespace aapt {
-enum class SymbolState {
- kUndefined,
- kPrivate,
- kPublic,
+// The Public status of a resource.
+struct Visibility {
+ enum class Level {
+ kUndefined,
+ kPrivate,
+ kPublic,
+ };
+
+ Level level = Level::kUndefined;
+ Source source;
+ std::string comment;
};
-/**
- * The Public status of a resource.
- */
-struct Symbol {
- SymbolState state = SymbolState::kUndefined;
+// Represents <add-resource> in an overlay.
+struct AllowNew {
Source source;
+ std::string comment;
+};
- // Whether this entry (originating from an overlay) can be added as a new resource.
- bool allow_new = false;
-
+// The policy dictating whether an entry is overlayable at runtime by RROs.
+struct Overlayable {
+ Source source;
std::string comment;
};
class ResourceConfigValue {
public:
- /**
- * The configuration for which this value is defined.
- */
+ // The configuration for which this value is defined.
const ConfigDescription config;
- /**
- * The product for which this value is defined.
- */
+ // The product for which this value is defined.
const std::string product;
- /**
- * The actual Value.
- */
+ // The actual Value.
std::unique_ptr<Value> value;
ResourceConfigValue(const ConfigDescription& config, const android::StringPiece& product)
@@ -87,27 +87,21 @@
*/
class ResourceEntry {
public:
- /**
- * The name of the resource. Immutable, as
- * this determines the order of this resource
- * when doing lookups.
- */
+ // The name of the resource. Immutable, as this determines the order of this resource
+ // when doing lookups.
const std::string name;
- /**
- * The entry ID for this resource.
- */
+ // The entry ID for this resource (the EEEE in 0xPPTTEEEE).
Maybe<uint16_t> id;
- /**
- * Whether this resource is public (and must maintain the same entry ID across
- * builds).
- */
- Symbol symbol_status;
+ // Whether this resource is public (and must maintain the same entry ID across builds).
+ Visibility visibility;
- /**
- * The resource's values for each configuration.
- */
+ Maybe<AllowNew> allow_new;
+
+ Maybe<Overlayable> overlayable;
+
+ // The resource's values for each configuration.
std::vector<std::unique_ptr<ResourceConfigValue>> values;
explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
@@ -125,31 +119,19 @@
DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
};
-/**
- * Represents a resource type, which holds entries defined
- * for this type.
- */
+// Represents a resource type (eg. string, drawable, layout, etc.) containing resource entries.
class ResourceTableType {
public:
- /**
- * The logical type of resource (string, drawable, layout, etc.).
- */
+ // The logical type of resource (string, drawable, layout, etc.).
const ResourceType type;
- /**
- * The type ID for this resource.
- */
+ // The type ID for this resource (the TT in 0xPPTTEEEE).
Maybe<uint8_t> id;
- /**
- * Whether this type is public (and must maintain the same
- * type ID across builds).
- */
- Symbol symbol_status;
+ // Whether this type is public (and must maintain the same type ID across builds).
+ Visibility::Level visibility_level = Visibility::Level::kUndefined;
- /**
- * List of resources for this type.
- */
+ // List of resources for this type.
std::vector<std::unique_ptr<ResourceEntry>> entries;
explicit ResourceTableType(const ResourceType type) : type(type) {}
@@ -163,9 +145,11 @@
class ResourceTablePackage {
public:
- Maybe<uint8_t> id;
std::string name;
+ // The package ID (the PP in 0xPPTTEEEE).
+ Maybe<uint8_t> id;
+
std::vector<std::unique_ptr<ResourceTableType>> types;
ResourceTablePackage() = default;
@@ -176,10 +160,7 @@
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
};
-/**
- * The container and index for all resources defined for an app. This gets
- * flattened into a binary resource table (resources.arsc).
- */
+// The container and index for all resources defined for an app.
class ResourceTable {
public:
ResourceTable() = default;
@@ -188,47 +169,51 @@
using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
- /**
- * When a collision of resources occurs, this method decides which value to
- * keep.
- */
+ // When a collision of resources occurs, this method decides which value to keep.
static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
bool AddResource(const ResourceNameRef& name, const ConfigDescription& config,
const android::StringPiece& product, std::unique_ptr<Value> value,
IDiagnostics* diag);
- bool AddResource(const ResourceNameRef& name, const ResourceId& res_id,
- const ConfigDescription& config, const android::StringPiece& product,
- std::unique_ptr<Value> value, IDiagnostics* diag);
+ bool AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
+ const ConfigDescription& config, const android::StringPiece& product,
+ std::unique_ptr<Value> value, IDiagnostics* diag);
bool AddFileReference(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, const android::StringPiece& path, IDiagnostics* diag);
- bool AddFileReferenceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, const android::StringPiece& path,
- io::IFile* file, IDiagnostics* diag);
+ bool AddFileReferenceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const android::StringPiece& path,
+ io::IFile* file, IDiagnostics* diag);
- /**
- * Same as AddResource, but doesn't verify the validity of the name. This is
- * used
- * when loading resources from an existing binary resource table that may have
- * mangled
- * names.
- */
- bool AddResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
- const android::StringPiece& product, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ // Same as AddResource, but doesn't verify the validity of the name. This is used
+ // when loading resources from an existing binary resource table that may have mangled names.
+ bool AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::StringPiece& product, std::unique_ptr<Value> value,
+ IDiagnostics* diag);
- bool AddResourceAllowMangled(const ResourceNameRef& name, const ResourceId& id,
- const ConfigDescription& config, const android::StringPiece& product,
- std::unique_ptr<Value> value, IDiagnostics* diag);
+ bool AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
+ const ConfigDescription& config,
+ const android::StringPiece& product, std::unique_ptr<Value> value,
+ IDiagnostics* diag);
- bool SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, IDiagnostics* diag);
+ bool SetVisibility(const ResourceNameRef& name, const Visibility& visibility, IDiagnostics* diag);
+ bool SetVisibilityMangled(const ResourceNameRef& name, const Visibility& visibility,
+ IDiagnostics* diag);
+ bool SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, IDiagnostics* diag);
+ bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, IDiagnostics* diag);
- bool SetSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, IDiagnostics* diag);
+ bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+ IDiagnostics* diag);
+ bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
+ IDiagnostics* diag);
+
+ bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
+ bool SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
+ IDiagnostics* diag);
struct SearchResult {
ResourceTablePackage* package;
@@ -236,40 +221,28 @@
ResourceEntry* entry;
};
- Maybe<SearchResult> FindResource(const ResourceNameRef& name);
+ Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
- /**
- * Returns the package struct with the given name, or nullptr if such a
- * package does not
- * exist. The empty string is a valid package and typically is used to
- * represent the
- * 'current' package before it is known to the ResourceTable.
- */
- ResourceTablePackage* FindPackage(const android::StringPiece& name);
+ // Returns the package struct with the given name, or nullptr if such a package does not
+ // exist. The empty string is a valid package and typically is used to represent the
+ // 'current' package before it is known to the ResourceTable.
+ ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
- ResourceTablePackage* FindPackageById(uint8_t id);
+ ResourceTablePackage* FindPackageById(uint8_t id) const;
ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
std::unique_ptr<ResourceTable> Clone() const;
- /**
- * The string pool used by this resource table. Values that reference strings
- * must use
- * this pool to create their strings.
- *
- * NOTE: `string_pool` must come before `packages` so that it is destroyed
- * after.
- * When `string_pool` references are destroyed (as they will be when
- * `packages`
- * is destroyed), they decrement a refCount, which would cause invalid
- * memory access if the pool was already destroyed.
- */
+ // The string pool used by this resource table. Values that reference strings must use
+ // this pool to create their strings.
+ // NOTE: `string_pool` must come before `packages` so that it is destroyed after.
+ // When `string_pool` references are destroyed (as they will be when `packages` is destroyed),
+ // they decrement a refCount, which would cause invalid memory access if the pool was already
+ // destroyed.
StringPool string_pool;
- /**
- * The list of packages in this table, sorted alphabetically by package name.
- */
+ // The list of packages in this table, sorted alphabetically by package name.
std::vector<std::unique_ptr<ResourceTablePackage>> packages;
// Set of dynamic packages that this table may reference. Their package names get encoded
@@ -284,6 +257,9 @@
ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
+ bool ValidateName(NameValidator validator, const ResourceNameRef& name, const Source& source,
+ IDiagnostics* diag);
+
bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
const ConfigDescription& config, const android::StringPiece& product,
std::unique_ptr<Value> value, NameValidator name_validator,
@@ -293,8 +269,19 @@
const Source& source, const android::StringPiece& path, io::IFile* file,
NameValidator name_validator, IDiagnostics* diag);
+ bool SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
+ const ResourceId& res_id, NameValidator name_validator,
+ IDiagnostics* diag);
+
+ bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
+ NameValidator name_validator, IDiagnostics* diag);
+
+ bool SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+ NameValidator name_validator, IDiagnostics* diag);
+
bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
- const Symbol& symbol, NameValidator name_validator, IDiagnostics* diag);
+ const Visibility& symbol, NameValidator name_validator,
+ IDiagnostics* diag);
DISALLOW_COPY_AND_ASSIGN(ResourceTable);
};
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 2a3c131..eb75f94 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -24,7 +24,10 @@
#include <ostream>
#include <string>
+using ::android::StringPiece;
+using ::testing::Eq;
using ::testing::NotNull;
+using ::testing::StrEq;
namespace aapt {
@@ -45,7 +48,7 @@
TEST(ResourceTableTest, AddResourceWithWeirdNameWhenAddingMangledResources) {
ResourceTable table;
- EXPECT_TRUE(table.AddResourceAllowMangled(
+ EXPECT_TRUE(table.AddResourceMangled(
test::ParseNameOrDie("android:id/heythere "), ConfigDescription{}, "",
test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), test::GetDiagnostics()));
}
@@ -141,4 +144,104 @@
EXPECT_EQ(std::string("tablet"), values[1]->product);
}
+static StringPiece LevelToString(Visibility::Level level) {
+ switch (level) {
+ case Visibility::Level::kPrivate:
+ return "private";
+ case Visibility::Level::kPublic:
+ return "private";
+ default:
+ return "undefined";
+ }
+}
+
+static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
+ const ResourceNameRef& name,
+ Visibility::Level level,
+ const StringPiece& comment) {
+ Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
+ if (!result) {
+ return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
+ }
+
+ const Visibility& visibility = result.value().entry->visibility;
+ if (visibility.level != level) {
+ return ::testing::AssertionFailure() << "expected visibility " << LevelToString(level)
+ << " but got " << LevelToString(visibility.level);
+ }
+
+ if (visibility.comment != comment) {
+ return ::testing::AssertionFailure() << "expected visibility comment '" << comment
+ << "' but got '" << visibility.comment << "'";
+ }
+ return ::testing::AssertionSuccess();
+}
+
+TEST(ResourceTableTest, SetVisibility) {
+ using Level = Visibility::Level;
+
+ ResourceTable table;
+ const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+ Visibility visibility;
+ visibility.level = Visibility::Level::kPrivate;
+ visibility.comment = "private";
+ ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
+
+ visibility.level = Visibility::Level::kUndefined;
+ visibility.comment = "undefined";
+ ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
+
+ visibility.level = Visibility::Level::kPublic;
+ visibility.comment = "public";
+ ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
+
+ visibility.level = Visibility::Level::kPrivate;
+ visibility.comment = "private";
+ ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
+}
+
+TEST(ResourceTableTest, SetAllowNew) {
+ ResourceTable table;
+ const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+ AllowNew allow_new;
+ Maybe<ResourceTable::SearchResult> result;
+
+ allow_new.comment = "first";
+ ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+ result = table.FindResource(name);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.value().entry->allow_new);
+ ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("first"));
+
+ allow_new.comment = "second";
+ ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+ result = table.FindResource(name);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.value().entry->allow_new);
+ ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
+}
+
+TEST(ResourceTableTest, SetOverlayable) {
+ ResourceTable table;
+ const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+ Overlayable overlayable;
+
+ overlayable.comment = "first";
+ ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+ Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.value().entry->overlayable);
+ ASSERT_THAT(result.value().entry->overlayable.value().comment, StrEq("first"));
+
+ overlayable.comment = "second";
+ ASSERT_FALSE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 7e7c86d..8552195 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -93,11 +93,10 @@
repeated Entry entry = 3;
}
-// The status of a symbol/entry. This contains information like visibility (public/private),
-// comments, and whether the entry can be overridden.
-message SymbolStatus {
+// The Visibility of a symbol/entry (public, private, undefined).
+message Visibility {
// The visibility of the resource outside of its package.
- enum Visibility {
+ enum Level {
// No visibility was explicitly specified. This is typically treated as private.
// The distinction is important when two separate R.java files are generated: a public and
// private one. An unknown visibility, in this case, would cause the resource to be omitted
@@ -115,17 +114,32 @@
PUBLIC = 2;
}
- Visibility visibility = 1;
+ Level level = 1;
// The path at which this entry's visibility was defined (eg. public.xml).
Source source = 2;
// The comment associated with the <public> tag.
string comment = 3;
+}
- // Whether the symbol can be merged into another resource table without there being an existing
- // definition to override. Used for overlays and set to true when <add-resource> is specified.
- bool allow_new = 4;
+// Whether a resource comes from a compile-time overlay and is explicitly allowed to not overlay an
+// existing resource.
+message AllowNew {
+ // Where this was defined in source.
+ Source source = 1;
+
+ // Any comment associated with the declaration.
+ string comment = 2;
+}
+
+// Whether a resource is overlayable by runtime resource overlays (RRO).
+message Overlayable {
+ // Where this declaration was defined in source.
+ Source source = 1;
+
+ // Any comment associated with the declaration.
+ string comment = 2;
}
// An entry ID in the range [0x0000, 0xffff].
@@ -147,12 +161,19 @@
// form package:type/entry.
string name = 2;
- // The symbol status of this entry, which includes visibility information.
- SymbolStatus symbol_status = 3;
+ // The visibility of this entry (public, private, undefined).
+ Visibility visibility = 3;
+
+ // Whether this resource, when originating from a compile-time overlay, is allowed to NOT overlay
+ // any existing resources.
+ AllowNew allow_new = 4;
+
+ // Whether this resource can be overlaid by a runtime resource overlay (RRO).
+ Overlayable overlayable = 5;
// The set of values defined for this entry, each corresponding to a different
// configuration/variant.
- repeated ConfigValue config_value = 4;
+ repeated ConfigValue config_value = 6;
}
// A Configuration/Value pair.
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 83512b9..7c1e96e 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -49,6 +49,7 @@
#include "xml/XmlPullParser.h"
using ::aapt::io::FileInputStream;
+using ::aapt::text::Printer;
using ::android::StringPiece;
using ::android::base::SystemErrorCodeToString;
using ::google::protobuf::io::CopyingOutputStreamAdaptor;
@@ -112,6 +113,7 @@
struct CompileOptions {
std::string output_path;
Maybe<std::string> res_dir;
+ Maybe<std::string> generate_text_symbols_path;
bool pseudolocalize = false;
bool no_png_crunch = false;
bool legacy_mode = false;
@@ -261,6 +263,58 @@
context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
return false;
}
+
+ if (options.generate_text_symbols_path) {
+ io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+
+ if (fout_text.HadError()) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "failed writing to'"
+ << options.generate_text_symbols_path.value()
+ << "': " << fout_text.GetError());
+ return false;
+ }
+
+ Printer r_txt_printer(&fout_text);
+ for (const auto& package : table.packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ // Check access modifiers.
+ switch(entry->visibility.level) {
+ case Visibility::Level::kUndefined :
+ r_txt_printer.Print("default ");
+ break;
+ case Visibility::Level::kPublic :
+ r_txt_printer.Print("public ");
+ break;
+ case Visibility::Level::kPrivate :
+ r_txt_printer.Print("private ");
+ }
+
+ if (type->type != ResourceType::kStyleable) {
+ r_txt_printer.Print("int ");
+ r_txt_printer.Print(to_string(type->type));
+ r_txt_printer.Print(" ");
+ r_txt_printer.Println(entry->name);
+ } else {
+ r_txt_printer.Print("int[] styleable ");
+ r_txt_printer.Println(entry->name);
+
+ if (!entry->values.empty()) {
+ auto styleable = static_cast<const Styleable*>(entry->values.front()->value.get());
+ for (const auto& attr : styleable->entries) {
+ r_txt_printer.Print("default int styleable ");
+ r_txt_printer.Print(entry->name);
+ r_txt_printer.Print("_");
+ r_txt_printer.Println(attr.name.value().entry);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
return true;
}
@@ -402,6 +456,31 @@
context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
return false;
}
+
+ if (options.generate_text_symbols_path) {
+ io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+
+ if (fout_text.HadError()) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "failed writing to'"
+ << options.generate_text_symbols_path.value()
+ << "': " << fout_text.GetError());
+ return false;
+ }
+
+ Printer r_txt_printer(&fout_text);
+ for (const auto res : xmlres->file.exported_symbols) {
+ r_txt_printer.Print("default int id ");
+ r_txt_printer.Println(res.name.entry);
+ }
+
+ // And print ourselves.
+ r_txt_printer.Print("default int ");
+ r_txt_printer.Print(path_data.resource_dir);
+ r_txt_printer.Print(" ");
+ r_txt_printer.Println(path_data.name);
+ }
+
return true;
}
@@ -609,6 +688,10 @@
Flags()
.RequiredFlag("-o", "Output path", &options.output_path)
.OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
+ .OptionalFlag("--output-text-symbols",
+ "Generates a text file containing the resource symbols in the\n"
+ "specified file",
+ &options.generate_text_symbols_path)
.OptionalSwitch("--pseudo-localize",
"Generate resources for pseudo-locales "
"(en-XA and ar-XB)",
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index fc1f1d6..12113ed 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -75,14 +75,14 @@
std::cerr << source << ": " << message << "\n";
}
-static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a, const Symbol& symbol_b) {
- return symbol_a.state != symbol_b.state;
+static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) {
+ return vis_a.level != vis_b.level;
}
template <typename Id>
-static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a, const Symbol& symbol_b,
- const Maybe<Id>& id_b) {
- if (symbol_a.state == SymbolState::kPublic || symbol_b.state == SymbolState::kPublic) {
+static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a,
+ const Visibility::Level& level_b, const Maybe<Id>& id_b) {
+ if (level_a == Visibility::Level::kPublic || level_b == Visibility::Level::kPublic) {
return id_a != id_b;
}
return false;
@@ -157,17 +157,17 @@
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
- if (IsSymbolVisibilityDifferent(entry_a->symbol_status, entry_b->symbol_status)) {
+ if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
<< " has different visibility (";
- if (entry_b->symbol_status.state == SymbolState::kPublic) {
+ if (entry_b->visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
}
str_stream << " vs ";
- if (entry_a->symbol_status.state == SymbolState::kPublic) {
+ if (entry_a->visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
@@ -175,7 +175,7 @@
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else if (IsIdDiff(entry_a->symbol_status, entry_a->id, entry_b->symbol_status,
+ } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
entry_b->id)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
@@ -225,16 +225,16 @@
EmitDiffLine(apk_a->GetSource(), str_stream.str());
diff = true;
} else {
- if (IsSymbolVisibilityDifferent(type_a->symbol_status, type_b->symbol_status)) {
+ if (type_a->visibility_level != type_b->visibility_level) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
- if (type_b->symbol_status.state == SymbolState::kPublic) {
+ if (type_b->visibility_level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
}
str_stream << " vs ";
- if (type_a->symbol_status.state == SymbolState::kPublic) {
+ if (type_a->visibility_level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
@@ -242,7 +242,8 @@
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else if (IsIdDiff(type_a->symbol_status, type_a->id, type_b->symbol_status, type_b->id)) {
+ } else if (IsIdDiff(type_a->visibility_level, type_a->id, type_b->visibility_level,
+ type_b->id)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
if (type_b->id) {
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index bc7f5a8..3d2fb55 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -69,15 +69,13 @@
printer->Println(StringPrintf("Data: offset=%" PRIi64 " length=%zd", offset, len));
}
-static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+static bool TryDumpFile(IAaptContext* context, const std::string& file_path,
+ const DebugPrintTableOptions& print_options) {
// Use a smaller buffer so that there is less latency for dumping to stdout.
constexpr size_t kStdOutBufferSize = 1024u;
io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
Printer printer(&fout);
- DebugPrintTableOptions print_options;
- print_options.show_sources = true;
-
std::string err;
std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
if (zip) {
@@ -244,7 +242,12 @@
// Entry point for dump command.
int Dump(const std::vector<StringPiece>& args) {
bool verbose = false;
- Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
+ bool no_values = false;
+ Flags flags = Flags()
+ .OptionalSwitch("--no-values",
+ "Suppresses output of values when displaying resource tables.",
+ &no_values)
+ .OptionalSwitch("-v", "increase verbosity of output", &verbose);
if (!flags.Parse("aapt2 dump", args, &std::cerr)) {
return 1;
}
@@ -252,8 +255,11 @@
DumpContext context;
context.SetVerbose(verbose);
+ DebugPrintTableOptions dump_table_options;
+ dump_table_options.show_sources = true;
+ dump_table_options.show_values = !no_values;
for (const std::string& arg : flags.GetArgs()) {
- if (!TryDumpFile(&context, arg)) {
+ if (!TryDumpFile(&context, arg, dump_table_options)) {
return 1;
}
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index d782de5..72e07dc 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -631,9 +631,9 @@
dst_path =
ResourceUtils::BuildResourceFileName(doc->file, context_->GetNameMangler());
- bool result = table->AddFileReferenceAllowMangled(doc->file.name, doc->file.config,
- doc->file.source, dst_path, nullptr,
- context_->GetDiagnostics());
+ bool result =
+ table->AddFileReferenceMangled(doc->file.name, doc->file.config, doc->file.source,
+ dst_path, nullptr, context_->GetDiagnostics());
if (!result) {
return false;
}
@@ -1343,9 +1343,9 @@
std::unique_ptr<Id> id = util::make_unique<Id>();
id->SetSource(source.WithLine(exported_symbol.line));
- bool result = final_table_.AddResourceAllowMangled(
- res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id),
- context_->GetDiagnostics());
+ bool result =
+ final_table_.AddResourceMangled(res_name, ConfigDescription::DefaultConfig(),
+ std::string(), std::move(id), context_->GetDiagnostics());
if (!result) {
return false;
}
@@ -2121,6 +2121,9 @@
&options.manifest_fixer_options.rename_instrumentation_target_package)
.OptionalFlagList("-0", "File extensions not to compress.",
&options.extensions_to_not_compress)
+ .OptionalSwitch("--warn-manifest-validation",
+ "Treat manifest validation errors as warnings.",
+ &options.manifest_fixer_options.warn_validation)
.OptionalFlagList("--split",
"Split resources matching a set of configs out to a Split APK.\n"
"Syntax: path/to/output.apk:<config>[,<config>[...]].\n"
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index d8bb999..9c76119 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -377,44 +377,10 @@
}
const std::string& apk_path = flags.GetArgs()[0];
- std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
- if (!apk) {
- return 1;
- }
context.SetVerbose(verbose);
IDiagnostics* diag = context.GetDiagnostics();
- if (target_densities) {
- // Parse the target screen densities.
- for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
- Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
- if (!target_density) {
- return 1;
- }
- options.table_splitter_options.preferred_densities.push_back(target_density.value());
- }
- }
-
- std::unique_ptr<IConfigFilter> filter;
- if (!configs.empty()) {
- filter = ParseConfigFilterParameters(configs, diag);
- if (filter == nullptr) {
- return 1;
- }
- options.table_splitter_options.config_filter = filter.get();
- }
-
- // Parse the split parameters.
- for (const std::string& split_arg : split_args) {
- options.split_paths.emplace_back();
- options.split_constraints.emplace_back();
- if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
- &options.split_constraints.back())) {
- return 1;
- }
- }
-
if (config_path) {
std::string& path = config_path.value();
Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
@@ -456,6 +422,41 @@
return 1;
}
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
+ if (!apk) {
+ return 1;
+ }
+
+ if (target_densities) {
+ // Parse the target screen densities.
+ for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
+ Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
+ if (!target_density) {
+ return 1;
+ }
+ options.table_splitter_options.preferred_densities.push_back(target_density.value());
+ }
+ }
+
+ std::unique_ptr<IConfigFilter> filter;
+ if (!configs.empty()) {
+ filter = ParseConfigFilterParameters(configs, diag);
+ if (filter == nullptr) {
+ return 1;
+ }
+ options.table_splitter_options.config_filter = filter.get();
+ }
+
+ // Parse the split parameters.
+ for (const std::string& split_arg : split_args) {
+ options.split_paths.emplace_back();
+ options.split_constraints.emplace_back();
+ if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
+ &options.split_constraints.back())) {
+ return 1;
+ }
+ }
+
if (options.table_flattener_options.collapse_key_stringpool) {
if (whitelist_path) {
std::string& path = whitelist_path.value();
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index ebc523f..eabeb47 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -49,13 +49,15 @@
using ::aapt::configuration::ConfiguredArtifact;
using ::aapt::configuration::DeviceFeature;
using ::aapt::configuration::Entry;
+using ::aapt::configuration::ExtractConfiguration;
using ::aapt::configuration::GlTexture;
using ::aapt::configuration::Group;
using ::aapt::configuration::Locale;
+using ::aapt::configuration::OrderedEntry;
using ::aapt::configuration::OutputArtifact;
using ::aapt::configuration::PostProcessingConfiguration;
using ::aapt::configuration::handler::AbiGroupTagHandler;
-using ::aapt::configuration::handler::AndroidSdkGroupTagHandler;
+using ::aapt::configuration::handler::AndroidSdkTagHandler;
using ::aapt::configuration::handler::ArtifactFormatTagHandler;
using ::aapt::configuration::handler::ArtifactTagHandler;
using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
@@ -130,7 +132,7 @@
return false;
}
- for (const T& item : group->second) {
+ for (const T& item : group->second.entry) {
target->push_back(item);
}
return true;
@@ -188,61 +190,6 @@
};
}
-/** Returns the binary reprasentation of the XML configuration. */
-Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
- IDiagnostics* diag) {
- StringInputStream in(contents);
- std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source("config.xml"));
- if (!doc) {
- return {};
- }
-
- // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
- Element* root = doc->root.get();
- if (root == nullptr) {
- diag->Error(DiagMessage() << "Could not find the root element in the XML document");
- return {};
- }
-
- std::string& xml_ns = root->namespace_uri;
- if (!xml_ns.empty()) {
- if (xml_ns != kAaptXmlNs) {
- diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
- return {};
- }
-
- xml_ns.clear();
- NamespaceVisitor visitor;
- root->Accept(&visitor);
- }
-
- XmlActionExecutor executor;
- XmlNodeAction& root_action = executor["post-process"];
- XmlNodeAction& artifacts_action = root_action["artifacts"];
- XmlNodeAction& groups_action = root_action["groups"];
-
- PostProcessingConfiguration config;
-
- // Parse the artifact elements.
- artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
- artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
-
- // Parse the different configuration groups.
- groups_action["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
- groups_action["screen-density-group"].Action(Bind(&config, ScreenDensityGroupTagHandler));
- groups_action["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
- groups_action["android-sdk-group"].Action(Bind(&config, AndroidSdkGroupTagHandler));
- groups_action["gl-texture-group"].Action(Bind(&config, GlTextureGroupTagHandler));
- groups_action["device-feature-group"].Action(Bind(&config, DeviceFeatureGroupTagHandler));
-
- if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
- diag->Error(DiagMessage() << "Could not process XML document");
- return {};
- }
-
- return {config};
-}
-
/** Converts a ConfiguredArtifact into an OutputArtifact. */
Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact,
const std::string& apk_name,
@@ -302,11 +249,11 @@
has_errors = true;
}
- if (artifact.android_sdk_group) {
- auto entry = config.android_sdk_groups.find(artifact.android_sdk_group.value());
- if (entry == config.android_sdk_groups.end()) {
+ if (artifact.android_sdk) {
+ auto entry = config.android_sdks.find(artifact.android_sdk.value());
+ if (entry == config.android_sdks.end()) {
src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: "
- << artifact.android_sdk_group.value());
+ << artifact.android_sdk.value());
has_errors = true;
} else {
output_artifact.android_sdk = {entry->second};
@@ -323,6 +270,64 @@
namespace configuration {
+/** Returns the binary reprasentation of the XML configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+ const std::string& config_path,
+ IDiagnostics* diag) {
+ StringInputStream in(contents);
+ std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path));
+ if (!doc) {
+ return {};
+ }
+
+ // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
+ Element* root = doc->root.get();
+ if (root == nullptr) {
+ diag->Error(DiagMessage() << "Could not find the root element in the XML document");
+ return {};
+ }
+
+ std::string& xml_ns = root->namespace_uri;
+ if (!xml_ns.empty()) {
+ if (xml_ns != kAaptXmlNs) {
+ diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
+ return {};
+ }
+
+ xml_ns.clear();
+ NamespaceVisitor visitor;
+ root->Accept(&visitor);
+ }
+
+ XmlActionExecutor executor;
+ XmlNodeAction& root_action = executor["post-process"];
+ XmlNodeAction& artifacts_action = root_action["artifacts"];
+
+ PostProcessingConfiguration config;
+
+ // Parse the artifact elements.
+ artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
+ artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
+
+ // Parse the different configuration groups.
+ root_action["abi-groups"]["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
+ root_action["screen-density-groups"]["screen-density-group"].Action(
+ Bind(&config, ScreenDensityGroupTagHandler));
+ root_action["locale-groups"]["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
+ root_action["android-sdks"]["android-sdk"].Action(Bind(&config, AndroidSdkTagHandler));
+ root_action["gl-texture-groups"]["gl-texture-group"].Action(
+ Bind(&config, GlTextureGroupTagHandler));
+ root_action["device-feature-groups"]["device-feature-group"].Action(
+ Bind(&config, DeviceFeatureGroupTagHandler));
+
+ if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
+ diag->Error(DiagMessage() << "Could not process XML document");
+ return {};
+ }
+
+ return {config};
+}
+
const StringPiece& AbiToString(Abi abi) {
return kAbiToStringMap.at(static_cast<size_t>(abi));
}
@@ -383,7 +388,7 @@
return {};
}
- if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) {
+ if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) {
return {};
}
@@ -414,47 +419,37 @@
if (!ReadFileToString(path, &contents, true)) {
return {};
}
- return ConfigurationParser(contents);
+ return ConfigurationParser(contents, path);
}
-ConfigurationParser::ConfigurationParser(std::string contents)
- : contents_(std::move(contents)),
- diag_(&noop_) {
+ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path)
+ : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) {
}
Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse(
const android::StringPiece& apk_path) {
- Maybe<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, diag_);
+ Maybe<PostProcessingConfiguration> maybe_config =
+ ExtractConfiguration(contents_, config_path_, diag_);
if (!maybe_config) {
return {};
}
- const PostProcessingConfiguration& config = maybe_config.value();
-
- // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
- // see: https://developer.android.com/google/play/publishing/multiple-apks.html
- //
- // For now, make sure the version codes are unique.
- std::vector<ConfiguredArtifact> artifacts = config.artifacts;
- std::sort(artifacts.begin(), artifacts.end());
- if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
- diag_->Error(DiagMessage() << "Configuration has duplicate versions");
- return {};
- }
-
- const std::string& apk_name = file::GetFilename(apk_path).to_string();
- const StringPiece ext = file::GetExtension(apk_name);
- const std::string base_name = apk_name.substr(0, apk_name.size() - ext.size());
// Convert from a parsed configuration to a list of artifacts for processing.
+ const std::string& apk_name = file::GetFilename(apk_path).to_string();
std::vector<OutputArtifact> output_artifacts;
bool has_errors = false;
- for (const ConfiguredArtifact& artifact : artifacts) {
+ PostProcessingConfiguration& config = maybe_config.value();
+ config.SortArtifacts();
+
+ int version = 1;
+ for (const ConfiguredArtifact& artifact : config.artifacts) {
Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_);
if (!output_artifact) {
// Defer return an error condition so that all errors are reported.
has_errors = true;
} else {
+ output_artifact.value().version = version++;
output_artifacts.push_back(std::move(output_artifact.value()));
}
}
@@ -470,24 +465,18 @@
bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
- // This will be incremented later so the first version will always be different to the base APK.
- int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
-
ConfiguredArtifact artifact{};
- Maybe<int> version;
for (const auto& attr : root_element->attributes) {
if (attr.name == "name") {
artifact.name = attr.value;
- } else if (attr.name == "version") {
- version = std::stoi(attr.value);
} else if (attr.name == "abi-group") {
artifact.abi_group = {attr.value};
} else if (attr.name == "screen-density-group") {
artifact.screen_density_group = {attr.value};
} else if (attr.name == "locale-group") {
artifact.locale_group = {attr.value};
- } else if (attr.name == "android-sdk-group") {
- artifact.android_sdk_group = {attr.value};
+ } else if (attr.name == "android-sdk") {
+ artifact.android_sdk = {attr.value};
} else if (attr.name == "gl-texture-group") {
artifact.gl_texture_group = {attr.value};
} else if (attr.name == "device-feature-group") {
@@ -497,9 +486,6 @@
<< attr.value);
}
}
-
- artifact.version = (version) ? version.value() : current_version + 1;
-
config->artifacts.push_back(artifact);
return true;
};
@@ -523,9 +509,19 @@
return false;
}
- auto& group = config->abi_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->abi_groups);
bool valid = true;
+ // Special case for empty abi-group tag. Label will be used as the ABI.
+ if (root_element->GetChildElements().empty()) {
+ auto abi = kStringToAbiMap.find(label);
+ if (abi == kStringToAbiMap.end()) {
+ return false;
+ }
+ group.push_back(abi->second);
+ return true;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "abi") {
diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name);
@@ -534,7 +530,13 @@
for (auto& node : child->children) {
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
- group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
+ auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+ if (abi != kStringToAbiMap.end()) {
+ group.push_back(abi->second);
+ } else {
+ diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text);
+ valid = false;
+ }
break;
}
}
@@ -551,9 +553,28 @@
return false;
}
- auto& group = config->screen_density_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->screen_density_groups);
bool valid = true;
+ // Special case for empty screen-density-group tag. Label will be used as the screen density.
+ if (root_element->GetChildElements().empty()) {
+ ConfigDescription config_descriptor;
+ bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+ if (parsed &&
+ (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+ android::ResTable_config::CONFIG_DENSITY)) {
+ // Copy the density with the minimum SDK version stripped out.
+ group.push_back(config_descriptor.CopyWithoutSdkVersion());
+ } else {
+ diag->Error(DiagMessage()
+ << "Could not parse config descriptor for empty screen-density-group: "
+ << label);
+ valid = false;
+ }
+
+ return valid;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "screen-density") {
diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -592,9 +613,28 @@
return false;
}
- auto& group = config->locale_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->locale_groups);
bool valid = true;
+ // Special case to auto insert a locale for an empty group. Label will be used for locale.
+ if (root_element->GetChildElements().empty()) {
+ ConfigDescription config_descriptor;
+ bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+ if (parsed &&
+ (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+ android::ResTable_config::CONFIG_LOCALE)) {
+ // Copy the locale with the minimum SDK version stripped out.
+ group.push_back(config_descriptor.CopyWithoutSdkVersion());
+ } else {
+ diag->Error(DiagMessage()
+ << "Could not parse config descriptor for empty screen-density-group: "
+ << label);
+ valid = false;
+ }
+
+ return valid;
+ }
+
for (auto* child : root_element->GetChildElements()) {
if (child->name != "locale") {
diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -626,61 +666,58 @@
return valid;
};
-bool AndroidSdkGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
- IDiagnostics* diag) {
- std::string label = GetLabel(root_element, diag);
- if (label.empty()) {
- return false;
- }
-
+bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element,
+ IDiagnostics* diag) {
+ AndroidSdk entry = AndroidSdk::ForMinSdk(-1);
bool valid = true;
- bool found = false;
+ for (const auto& attr : root_element->attributes) {
+ bool valid_attr = false;
+ if (attr.name == "label") {
+ entry.label = attr.value;
+ valid_attr = true;
+ } else if (attr.name == "minSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.min_sdk_version = version.value();
+ }
+ } else if (attr.name == "targetSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.target_sdk_version = version;
+ }
+ } else if (attr.name == "maxSdkVersion") {
+ Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+ if (version) {
+ valid_attr = true;
+ entry.max_sdk_version = version;
+ }
+ }
- for (auto* child : root_element->GetChildElements()) {
- if (child->name != "android-sdk") {
- diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
+ if (!valid_attr) {
+ diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
valid = false;
- } else {
- AndroidSdk entry;
- for (const auto& attr : child->attributes) {
- Maybe<int>* target = nullptr;
- if (attr.name == "minSdkVersion") {
- target = &entry.min_sdk_version;
- } else if (attr.name == "targetSdkVersion") {
- target = &entry.target_sdk_version;
- } else if (attr.name == "maxSdkVersion") {
- target = &entry.max_sdk_version;
- } else {
- diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
- continue;
- }
-
- *target = ResourceUtils::ParseSdkVersion(attr.value);
- if (!*target) {
- diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
- valid = false;
- }
- }
-
- // TODO: Fill in the manifest details when they are finalised.
- for (auto node : child->GetChildElements()) {
- if (node->name == "manifest") {
- if (entry.manifest) {
- diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
- continue;
- }
- entry.manifest = {AndroidManifest()};
- }
- }
-
- config->android_sdk_groups[label] = entry;
- if (found) {
- valid = false;
- }
- found = true;
}
}
+ if (entry.min_sdk_version == -1) {
+ diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute");
+ valid = false;
+ }
+
+ // TODO: Fill in the manifest details when they are finalised.
+ for (auto node : root_element->GetChildElements()) {
+ if (node->name == "manifest") {
+ if (entry.manifest) {
+ diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
+ continue;
+ }
+ entry.manifest = {AndroidManifest()};
+ }
+ }
+
+ config->android_sdks[entry.label] = entry;
return valid;
};
@@ -691,7 +728,7 @@
return false;
}
- auto& group = config->gl_texture_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->gl_texture_groups);
bool valid = true;
GlTexture result;
@@ -734,7 +771,7 @@
return false;
}
- auto& group = config->device_feature_groups[label];
+ auto& group = GetOrCreateGroup(label, &config->device_feature_groups);
bool valid = true;
for (auto* child : root_element->GetChildElements()) {
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index ca58910..7f1d445 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -71,7 +71,8 @@
};
struct AndroidSdk {
- Maybe<int> min_sdk_version;
+ std::string label;
+ int min_sdk_version; // min_sdk_version is mandatory if splitting by SDK.
Maybe<int> target_sdk_version;
Maybe<int> max_sdk_version;
Maybe<AndroidManifest> manifest;
@@ -113,15 +114,19 @@
Maybe<AndroidSdk> android_sdk;
std::vector<DeviceFeature> features;
std::vector<GlTexture> textures;
+
+ inline int GetMinSdk(int default_value = -1) const {
+ if (!android_sdk) {
+ return default_value;
+ }
+ return android_sdk.value().min_sdk_version;
+ }
};
} // namespace configuration
// Forward declaration of classes used in the API.
struct IDiagnostics;
-namespace xml {
-class Element;
-}
/**
* XML configuration file parser for the split and optimize commands.
@@ -133,8 +138,8 @@
static Maybe<ConfigurationParser> ForPath(const std::string& path);
/** Returns a ConfigurationParser for the configuration in the provided file contents. */
- static ConfigurationParser ForContents(const std::string& contents) {
- ConfigurationParser parser{contents};
+ static ConfigurationParser ForContents(const std::string& contents, const std::string& path) {
+ ConfigurationParser parser{contents, path};
return parser;
}
@@ -156,7 +161,7 @@
* diagnostics context. The default diagnostics context can be overridden with a call to
* WithDiagnostics(IDiagnostics *).
*/
- explicit ConfigurationParser(std::string contents);
+ ConfigurationParser(std::string contents, const std::string& config_path);
/** Returns the current diagnostics context to any subclasses. */
IDiagnostics* diagnostics() {
@@ -166,6 +171,8 @@
private:
/** The contents of the configuration file to parse. */
const std::string contents_;
+ /** Path to the input configuration. */
+ const std::string config_path_;
/** The diagnostics context to send messages to. */
IDiagnostics* diag_;
};
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 7657ebd..a583057 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -17,35 +17,105 @@
#ifndef AAPT2_CONFIGURATIONPARSER_INTERNAL_H
#define AAPT2_CONFIGURATIONPARSER_INTERNAL_H
+#include "configuration/ConfigurationParser.h"
+
+#include <algorithm>
+#include <limits>
+
namespace aapt {
+
+// Forward declaration of classes used in the API.
+namespace xml {
+class Element;
+}
+
namespace configuration {
+template <typename T>
+struct OrderedEntry {
+ size_t order;
+ std::vector<T> entry;
+};
+
/** A mapping of group labels to group of configuration items. */
template <class T>
-using Group = std::unordered_map<std::string, std::vector<T>>;
+using Group = std::unordered_map<std::string, OrderedEntry<T>>;
/** A mapping of group label to a single configuration item. */
template <class T>
using Entry = std::unordered_map<std::string, T>;
+/** Retrieves an entry from the provided Group, creating a new instance if one does not exist. */
+template <typename T>
+std::vector<T>& GetOrCreateGroup(std::string label, Group<T>* group) {
+ OrderedEntry<T>& entry = (*group)[label];
+ // If this is a new entry, set the order.
+ if (entry.order == 0) {
+ entry.order = group->size();
+ }
+ return entry.entry;
+}
+
+/**
+ * A ComparisonChain is a grouping of comparisons to perform when sorting groups that have a well
+ * defined order of precedence. Comparisons are only made if none of the previous comparisons had a
+ * definite result. A comparison has a result if at least one of the items has an entry for that
+ * value and that they are not equal.
+ */
+class ComparisonChain {
+ public:
+ /**
+ * Adds a new comparison of items in a group to the chain. The new comparison is only used if we
+ * have not been able to determine the sort order with the previous comparisons.
+ */
+ template <typename T>
+ ComparisonChain& Add(const Group<T>& groups, const Maybe<std::string>& lhs,
+ const Maybe<std::string>& rhs) {
+ return Add(GetGroupOrder(groups, lhs), GetGroupOrder(groups, rhs));
+ }
+
+ /**
+ * Adds a new comparison to the chain. The new comparison is only used if we have not been able to
+ * determine the sort order with the previous comparisons.
+ */
+ ComparisonChain& Add(int lhs, int rhs) {
+ if (!has_result_) {
+ has_result_ = (lhs != rhs);
+ result_ = (lhs < rhs);
+ }
+ return *this;
+ }
+
+ /** Returns true if the left hand side should come before the right hand side. */
+ bool Compare() {
+ return result_;
+ }
+
+ private:
+ template <typename T>
+ inline size_t GetGroupOrder(const Group<T>& groups, const Maybe<std::string>& label) {
+ if (!label) {
+ return std::numeric_limits<size_t>::max();
+ }
+ return groups.at(label.value()).order;
+ }
+
+ bool has_result_ = false;
+ bool result_ = false;
+};
+
/** Output artifact configuration options. */
struct ConfiguredArtifact {
/** Name to use for output of processing foo.apk -> foo.<name>.apk. */
Maybe<std::string> name;
- /**
- * Value to add to the base Android manifest versionCode. If it is not present in the
- * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
- * a value, artifacts are a 1 based index.
- */
- int version;
/** If present, uses the ABI group with this name. */
Maybe<std::string> abi_group;
/** If present, uses the screen density group with this name. */
Maybe<std::string> screen_density_group;
/** If present, uses the locale group with this name. */
Maybe<std::string> locale_group;
- /** If present, uses the Android SDK group with this name. */
- Maybe<std::string> android_sdk_group;
+ /** If present, uses the Android SDK with this name. */
+ Maybe<std::string> android_sdk;
/** If present, uses the device feature group with this name. */
Maybe<std::string> device_feature_group;
/** If present, uses the OpenGL texture group with this name. */
@@ -57,31 +127,71 @@
/** Convert an artifact name template into a name string based on configuration contents. */
Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
-
- bool operator<(const ConfiguredArtifact& rhs) const {
- // TODO(safarmer): Order by play store multi-APK requirements.
- return version < rhs.version;
- }
-
- bool operator==(const ConfiguredArtifact& rhs) const {
- return version == rhs.version;
- }
};
/** AAPT2 XML configuration file binary representation. */
struct PostProcessingConfiguration {
- // TODO: Support named artifacts?
std::vector<ConfiguredArtifact> artifacts;
Maybe<std::string> artifact_format;
Group<Abi> abi_groups;
Group<ConfigDescription> screen_density_groups;
Group<ConfigDescription> locale_groups;
- Entry<AndroidSdk> android_sdk_groups;
Group<DeviceFeature> device_feature_groups;
Group<GlTexture> gl_texture_groups;
+ Entry<AndroidSdk> android_sdks;
+
+ /**
+ * Sorts the configured artifacts based on the ordering of the groups in the configuration file.
+ * The only exception to this rule is Android SDK versions. Larger SDK versions will have a larger
+ * versionCode to ensure users get the correct APK when they upgrade their OS.
+ */
+ void SortArtifacts() {
+ std::sort(artifacts.begin(), artifacts.end(), *this);
+ }
+
+ /** Comparator that ensures artifacts are in the preferred order for versionCode rewriting. */
+ bool operator()(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+ // Split dimensions are added in the order of precedence. Items higher in the list result in
+ // higher version codes.
+ return ComparisonChain()
+ // All splits with a minSdkVersion specified must be last to ensure the application will be
+ // updated if a user upgrades the version of Android on their device.
+ .Add(GetMinSdk(lhs), GetMinSdk(rhs))
+ // ABI version is important, especially on x86 phones where they may begin to run in ARM
+ // emulation mode on newer Android versions. This allows us to ensure that the x86 version
+ // is installed on these devices rather than ARM.
+ .Add(abi_groups, lhs.abi_group, rhs.abi_group)
+ // The rest are in arbitrary order based on estimated usage.
+ .Add(screen_density_groups, lhs.screen_density_group, rhs.screen_density_group)
+ .Add(locale_groups, lhs.locale_group, rhs.locale_group)
+ .Add(gl_texture_groups, lhs.gl_texture_group, rhs.gl_texture_group)
+ .Add(device_feature_groups, lhs.device_feature_group, rhs.device_feature_group)
+ .Compare();
+ }
+
+ private:
+ /**
+ * Returns the min_sdk_version from the provided artifact or 0 if none is present. This allows
+ * artifacts that have an Android SDK version to have a higher versionCode than those that do not.
+ */
+ inline int GetMinSdk(const ConfiguredArtifact& artifact) {
+ if (!artifact.android_sdk) {
+ return 0;
+ }
+ const auto& entry = android_sdks.find(artifact.android_sdk.value());
+ if (entry == android_sdks.end()) {
+ return 0;
+ }
+ return entry->second.min_sdk_version;
+ }
};
+/** Parses the provided XML document returning the post processing configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+ const std::string& config_path,
+ IDiagnostics* diag);
+
namespace handler {
/** Handler for <artifact> tags. */
@@ -104,9 +214,9 @@
bool LocaleGroupTagHandler(configuration::PostProcessingConfiguration* config,
xml::Element* element, IDiagnostics* diag);
-/** Handler for <android-sdk-group> tags. */
-bool AndroidSdkGroupTagHandler(configuration::PostProcessingConfiguration* config,
- xml::Element* element, IDiagnostics* diag);
+/** Handler for <android-sdk> tags. */
+bool AndroidSdkTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element,
+ IDiagnostics* diag);
/** Handler for <gl-texture-group> tags. */
bool GlTextureGroupTagHandler(configuration::PostProcessingConfiguration* config,
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 3f356d7..0329846 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -30,11 +30,33 @@
namespace configuration {
void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
- *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+ *os << "SDK: min=" << sdk.min_sdk_version
<< ", target=" << sdk.target_sdk_version.value_or_default(-1)
<< ", max=" << sdk.max_sdk_version.value_or_default(-1);
}
+bool operator==(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+ return lhs.name == rhs.name && lhs.abi_group == rhs.abi_group &&
+ lhs.screen_density_group == rhs.screen_density_group &&
+ lhs.locale_group == rhs.locale_group && lhs.android_sdk == rhs.android_sdk &&
+ lhs.device_feature_group == rhs.device_feature_group &&
+ lhs.gl_texture_group == rhs.gl_texture_group;
+}
+
+std::ostream& operator<<(std::ostream& out, const Maybe<std::string>& value) {
+ PrintTo(value, &out);
+ return out;
+}
+
+void PrintTo(const ConfiguredArtifact& artifact, std::ostream* os) {
+ *os << "\n{"
+ << "\n name: " << artifact.name << "\n sdk: " << artifact.android_sdk
+ << "\n abi: " << artifact.abi_group << "\n density: " << artifact.screen_density_group
+ << "\n locale: " << artifact.locale_group
+ << "\n features: " << artifact.device_feature_group
+ << "\n textures: " << artifact.gl_texture_group << "\n}\n";
+}
+
namespace handler {
namespace {
@@ -44,6 +66,7 @@
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::ConfiguredArtifact;
using ::aapt::configuration::DeviceFeature;
+using ::aapt::configuration::ExtractConfiguration;
using ::aapt::configuration::GlTexture;
using ::aapt::configuration::Locale;
using ::aapt::configuration::PostProcessingConfiguration;
@@ -52,11 +75,13 @@
using ::android::ResTable_config;
using ::android::base::StringPrintf;
using ::testing::ElementsAre;
+using ::testing::Eq;
using ::testing::SizeIs;
+using ::testing::StrEq;
constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
<post-process xmlns="http://schemas.android.com/tools/aapt">
- <groups>
+ <abi-groups>
<abi-group label="arm">
<abi>armeabi-v7a</abi>
<abi>arm64-v8a</abi>
@@ -65,6 +90,8 @@
<abi>x86</abi>
<abi>mips</abi>
</abi-group>
+ </abi-groups>
+ <screen-density-groups>
<screen-density-group label="large">
<screen-density>xhdpi</screen-density>
<screen-density>xxhdpi</screen-density>
@@ -78,6 +105,8 @@
<screen-density>xxhdpi</screen-density>
<screen-density>xxxhdpi</screen-density>
</screen-density-group>
+ </screen-density-groups>
+ <locale-groups>
<locale-group label="europe">
<locale>en</locale>
<locale>es</locale>
@@ -89,25 +118,30 @@
<locale>es-rMX</locale>
<locale>fr-rCA</locale>
</locale-group>
- <android-sdk-group label="v19">
- <android-sdk
- minSdkVersion="19"
- targetSdkVersion="24"
- maxSdkVersion="25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>
+ </locale-groups>
+ <android-sdks>
+ <android-sdk
+ label="v19"
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdks>
+ <gl-texture-groups>
<gl-texture-group label="dxt1">
<gl-texture name="GL_EXT_texture_compression_dxt1">
<texture-path>assets/dxt1/*</texture-path>
</gl-texture>
</gl-texture-group>
+ </gl-texture-groups>
+ <device-feature-groups>
<device-feature-group label="low-latency">
<supports-feature>android.hardware.audio.low_latency</supports-feature>
</device-feature-group>
- </groups>
+ </device-feature-groups>
<artifacts>
<artifact-format>
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -117,7 +151,7 @@
abi-group="arm"
screen-density-group="large"
locale-group="europe"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>
<artifact
@@ -125,7 +159,7 @@
abi-group="other"
screen-density-group="alldpi"
locale-group="north-america"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>
</artifacts>
@@ -134,7 +168,8 @@
class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test {
public:
- ConfigurationParserTest() : ConfigurationParser("") {}
+ ConfigurationParserTest() : ConfigurationParser("", "config.xml") {
+ }
protected:
StdErrDiagnostics diag_;
@@ -145,8 +180,31 @@
EXPECT_FALSE(result);
}
+TEST_F(ConfigurationParserTest, ExtractConfiguration) {
+ Maybe<PostProcessingConfiguration> maybe_config =
+ ExtractConfiguration(kValidConfig, "dummy.xml", &diag_);
+
+ PostProcessingConfiguration config = maybe_config.value();
+
+ auto& arm = config.abi_groups["arm"];
+ auto& other = config.abi_groups["other"];
+ EXPECT_EQ(arm.order, 1ul);
+ EXPECT_EQ(other.order, 2ul);
+
+ auto& large = config.screen_density_groups["large"];
+ auto& alldpi = config.screen_density_groups["alldpi"];
+ EXPECT_EQ(large.order, 1ul);
+ EXPECT_EQ(alldpi.order, 2ul);
+
+ auto& north_america = config.locale_groups["north-america"];
+ auto& europe = config.locale_groups["europe"];
+ // Checked in reverse to make sure access order does not matter.
+ EXPECT_EQ(north_america.order, 2ul);
+ EXPECT_EQ(europe.order, 1ul);
+}
+
TEST_F(ConfigurationParserTest, ValidateFile) {
- auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_);
+ auto parser = ConfigurationParser::ForContents(kValidConfig, "conf.xml").WithDiagnostics(&diag_);
auto result = parser.Parse("test.apk");
ASSERT_TRUE(result);
const std::vector<OutputArtifact>& value = result.value();
@@ -154,6 +212,7 @@
const OutputArtifact& a1 = value[0];
EXPECT_EQ(a1.name, "art1.apk");
+ EXPECT_EQ(a1.version, 1);
EXPECT_THAT(a1.abis, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
EXPECT_THAT(a1.screen_densities,
ElementsAre(test::ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(),
@@ -161,12 +220,15 @@
test::ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion()));
EXPECT_THAT(a1.locales, ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es"),
test::ParseConfigOrDie("fr"), test::ParseConfigOrDie("de")));
- EXPECT_EQ(a1.android_sdk.value().min_sdk_version.value(), 19l);
+ ASSERT_TRUE(a1.android_sdk);
+ ASSERT_TRUE(a1.android_sdk.value().min_sdk_version);
+ EXPECT_EQ(a1.android_sdk.value().min_sdk_version, 19l);
EXPECT_THAT(a1.textures, SizeIs(1ul));
EXPECT_THAT(a1.features, SizeIs(1ul));
const OutputArtifact& a2 = value[1];
EXPECT_EQ(a2.name, "art2.apk");
+ EXPECT_EQ(a2.version, 2);
EXPECT_THAT(a2.abis, ElementsAre(Abi::kX86, Abi::kMips));
EXPECT_THAT(a2.screen_densities,
ElementsAre(test::ParseConfigOrDie("ldpi").CopyWithoutSdkVersion(),
@@ -178,124 +240,138 @@
EXPECT_THAT(a2.locales,
ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es-rMX"),
test::ParseConfigOrDie("fr-rCA")));
- EXPECT_EQ(a2.android_sdk.value().min_sdk_version.value(), 19l);
+ ASSERT_TRUE(a2.android_sdk);
+ ASSERT_TRUE(a2.android_sdk.value().min_sdk_version);
+ EXPECT_EQ(a2.android_sdk.value().min_sdk_version, 19l);
EXPECT_THAT(a2.textures, SizeIs(1ul));
EXPECT_THAT(a2.features, SizeIs(1ul));
}
+TEST_F(ConfigurationParserTest, ConfiguredArtifactOrdering) {
+ // Create a base builder with the configuration groups but no artifacts to allow it to be copied.
+ test::PostProcessingConfigurationBuilder base_builder = test::PostProcessingConfigurationBuilder()
+ .AddAbiGroup("arm")
+ .AddAbiGroup("arm64")
+ .AddAndroidSdk("v23", 23)
+ .AddAndroidSdk("v19", 19);
+
+ {
+ // Test version ordering.
+ ConfiguredArtifact v23;
+ v23.android_sdk = {"v23"};
+ ConfiguredArtifact v19;
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v23).AddArtifact(v19).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(v19));
+ EXPECT_THAT(config.artifacts[1], Eq(v23));
+ }
+
+ {
+ // Test ABI ordering.
+ ConfiguredArtifact arm;
+ arm.android_sdk = {"v19"};
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact arm64;
+ arm64.android_sdk = {"v19"};
+ arm64.abi_group = {"arm64"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(arm64));
+ }
+
+ {
+ // Test Android SDK has precedence over ABI.
+ ConfiguredArtifact arm;
+ arm.android_sdk = {"v23"};
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact arm64;
+ arm64.android_sdk = {"v19"};
+ arm64.abi_group = {"arm64"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm64));
+ EXPECT_THAT(config.artifacts[1], Eq(arm));
+ }
+
+ {
+ // Test version is better than ABI.
+ ConfiguredArtifact arm;
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact v19;
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(v19));
+ }
+
+ {
+ // Test version is sorted higher than no version.
+ ConfiguredArtifact arm;
+ arm.abi_group = {"arm"};
+ ConfiguredArtifact v19;
+ v19.abi_group = {"arm"};
+ v19.android_sdk = {"v19"};
+
+ test::PostProcessingConfigurationBuilder builder = base_builder;
+ PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+ config.SortArtifacts();
+ ASSERT_THAT(config.artifacts, SizeIs(2));
+ EXPECT_THAT(config.artifacts[0], Eq(arm));
+ EXPECT_THAT(config.artifacts[1], Eq(v19));
+ }
+}
+
TEST_F(ConfigurationParserTest, InvalidNamespace) {
constexpr const char* invalid_ns = R"(<?xml version="1.0" encoding="utf-8" ?>
- <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
+ <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
- auto result = ConfigurationParser::ForContents(invalid_ns).Parse("test.apk");
+ auto result = ConfigurationParser::ForContents(invalid_ns, "config.xml").Parse("test.apk");
ASSERT_FALSE(result);
}
TEST_F(ConfigurationParserTest, ArtifactAction) {
PostProcessingConfiguration config;
- {
- const auto doc = test::BuildXmlDom(R"xml(
+ const auto doc = test::BuildXmlDom(R"xml(
<artifact
abi-group="arm"
screen-density-group="large"
locale-group="europe"
- android-sdk-group="v19"
+ android-sdk="v19"
gl-texture-group="dxt1"
device-feature-group="low-latency"/>)xml");
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
+ ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(1ul));
+ EXPECT_THAT(config.artifacts, SizeIs(1ul));
- auto& artifact = config.artifacts.back();
- EXPECT_FALSE(artifact.name); // TODO: make this fail.
- EXPECT_EQ(1, artifact.version);
- EXPECT_EQ("arm", artifact.abi_group.value());
- EXPECT_EQ("large", artifact.screen_density_group.value());
- EXPECT_EQ("europe", artifact.locale_group.value());
- EXPECT_EQ("v19", artifact.android_sdk_group.value());
- EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
- EXPECT_EQ("low-latency", artifact.device_feature_group.value());
- }
-
- {
- // Perform a second action to ensure we get 2 artifacts.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(2ul));
- EXPECT_EQ(2, config.artifacts.back().version);
- }
-
- {
- // Perform a third action with a set version code.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- version="5"
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(3ul));
- EXPECT_EQ(5, config.artifacts.back().version);
- }
-
- {
- // Perform a fourth action to ensure the version code still increments.
- const auto doc = test::BuildXmlDom(R"xml(
- <artifact
- abi-group="other"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml");
-
- ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
- EXPECT_THAT(config.artifacts, SizeIs(4ul));
- EXPECT_EQ(6, config.artifacts.back().version);
- }
-}
-
-TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) {
- static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?>
- <pst-process xmlns="http://schemas.android.com/tools/aapt">>
- <artifacts>
- <artifact-format>
- ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
- </artifact-format>
- <artifact
- name="art1"
- abi-group="arm"
- screen-density-group="large"
- locale-group="europe"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>
- <artifact
- name="art2"
- version = "1"
- abi-group="other"
- screen-density-group="alldpi"
- locale-group="north-america"
- android-sdk-group="v19"
- gl-texture-group="dxt1"
- device-feature-group="low-latency"/>
- </artifacts>
- </post-process>)xml";
- auto result = ConfigurationParser::ForContents(configuration).Parse("test.apk");
- ASSERT_FALSE(result);
+ auto& artifact = config.artifacts.back();
+ EXPECT_FALSE(artifact.name); // TODO: make this fail.
+ EXPECT_EQ("arm", artifact.abi_group.value());
+ EXPECT_EQ("large", artifact.screen_density_group.value());
+ EXPECT_EQ("europe", artifact.locale_group.value());
+ EXPECT_EQ("v19", artifact.android_sdk.value());
+ EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+ EXPECT_EQ("low-latency", artifact.device_feature_group.value());
}
TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
@@ -334,10 +410,36 @@
EXPECT_THAT(config.abi_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.abi_groups.count("arm"));
- auto& out = config.abi_groups["arm"];
+ auto& out = config.abi_groups["arm"].entry;
ASSERT_THAT(out, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
}
+TEST_F(ConfigurationParserTest, AbiGroupAction_EmptyGroup) {
+ static constexpr const char* xml = R"xml(<abi-group label="arm64-v8a"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_THAT(config.abi_groups, SizeIs(1ul));
+ ASSERT_EQ(1u, config.abi_groups.count("arm64-v8a"));
+
+ auto& out = config.abi_groups["arm64-v8a"].entry;
+ ASSERT_THAT(out, ElementsAre(Abi::kArm64V8a));
+}
+
+TEST_F(ConfigurationParserTest, AbiGroupAction_InvalidEmptyGroup) {
+ static constexpr const char* xml = R"xml(<abi-group label="arm"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) {
static constexpr const char* xml = R"xml(
<screen-density-group label="large">
@@ -364,10 +466,39 @@
ConfigDescription xxxhdpi;
xxxhdpi.density = ResTable_config::DENSITY_XXXHIGH;
- auto& out = config.screen_density_groups["large"];
+ auto& out = config.screen_density_groups["large"].entry;
ASSERT_THAT(out, ElementsAre(xhdpi, xxhdpi, xxxhdpi));
}
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_EmtpyGroup) {
+ static constexpr const char* xml = R"xml(<screen-density-group label="xhdpi"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_THAT(config.screen_density_groups, SizeIs(1ul));
+ ASSERT_EQ(1u, config.screen_density_groups.count("xhdpi"));
+
+ ConfigDescription xhdpi;
+ xhdpi.density = ResTable_config::DENSITY_XHIGH;
+
+ auto& out = config.screen_density_groups["xhdpi"].entry;
+ ASSERT_THAT(out, ElementsAre(xhdpi));
+}
+
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_InvalidEmtpyGroup) {
+ static constexpr const char* xml = R"xml(<screen-density-group label="really-big-screen"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, LocaleGroupAction) {
static constexpr const char* xml = R"xml(
<locale-group label="europe">
@@ -386,7 +517,7 @@
ASSERT_EQ(1ul, config.locale_groups.size());
ASSERT_EQ(1u, config.locale_groups.count("europe"));
- const auto& out = config.locale_groups["europe"];
+ const auto& out = config.locale_groups["europe"].entry;
ConfigDescription en = test::ParseConfigOrDie("en");
ConfigDescription es = test::ParseConfigOrDie("es");
@@ -396,29 +527,56 @@
ASSERT_THAT(out, ElementsAre(en, es, fr, de));
}
+TEST_F(ConfigurationParserTest, LocaleGroupAction_EmtpyGroup) {
+ static constexpr const char* xml = R"xml(<locale-group label="en"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ ASSERT_EQ(1ul, config.locale_groups.size());
+ ASSERT_EQ(1u, config.locale_groups.count("en"));
+
+ const auto& out = config.locale_groups["en"].entry;
+
+ ConfigDescription en = test::ParseConfigOrDie("en");
+
+ ASSERT_THAT(out, ElementsAre(en));
+}
+
+TEST_F(ConfigurationParserTest, LocaleGroupAction_InvalidEmtpyGroup) {
+ static constexpr const char* xml = R"xml(<locale-group label="arm64"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_FALSE(ok);
+}
+
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk
+ <android-sdk label="v19"
minSdkVersion="19"
targetSdkVersion="24"
maxSdkVersion="25">
<manifest>
<!--- manifest additions here XSLT? TODO -->
</manifest>
- </android-sdk>
- </android-sdk-group>)xml";
+ </android-sdk>)xml";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
AndroidSdk sdk;
sdk.min_sdk_version = 19;
@@ -431,98 +589,86 @@
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_SingleVersion) {
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk minSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml = "<android-sdk label='v19' minSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
- EXPECT_EQ(19, out.min_sdk_version.value());
+ auto& out = config.android_sdks["v19"];
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.max_sdk_version);
EXPECT_FALSE(out.target_sdk_version);
}
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk maxSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml =
+ "<android-sdk label='v19' minSdkVersion='19' maxSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
EXPECT_EQ(19, out.max_sdk_version.value());
- EXPECT_FALSE(out.min_sdk_version);
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.target_sdk_version);
}
{
- static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk targetSdkVersion="19"></android-sdk>
- </android-sdk-group>)xml";
-
+ const char* xml =
+ "<android-sdk label='v19' minSdkVersion='19' targetSdkVersion='19'></android-sdk>";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("v19"));
- auto& out = config.android_sdk_groups["v19"];
+ auto& out = config.android_sdks["v19"];
EXPECT_EQ(19, out.target_sdk_version.value());
- EXPECT_FALSE(out.min_sdk_version);
+ EXPECT_EQ(19, out.min_sdk_version);
EXPECT_FALSE(out.max_sdk_version);
}
}
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_InvalidVersion) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="v19">
- <android-sdk
- minSdkVersion="v19"
- targetSdkVersion="v24"
- maxSdkVersion="v25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>)xml";
+ <android-sdk
+ label="v19"
+ minSdkVersion="v19"
+ targetSdkVersion="v24"
+ maxSdkVersion="v25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>)xml";
auto doc = test::BuildXmlDom(xml);
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_FALSE(ok);
}
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
static constexpr const char* xml = R"xml(
- <android-sdk-group label="P">
<android-sdk
+ label="P"
minSdkVersion="25"
targetSdkVersion="%s"
maxSdkVersion="%s">
- </android-sdk>
- </android-sdk-group>)xml";
+ </android-sdk>)xml";
const auto& dev_sdk = GetDevelopmentSdkCodeNameAndVersion();
const char* codename = dev_sdk.first.data();
@@ -531,13 +677,13 @@
auto doc = test::BuildXmlDom(StringPrintf(xml, codename, codename));
PostProcessingConfiguration config;
- bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
ASSERT_TRUE(ok);
- ASSERT_EQ(1ul, config.android_sdk_groups.size());
- ASSERT_EQ(1u, config.android_sdk_groups.count("P"));
+ ASSERT_EQ(1ul, config.android_sdks.size());
+ ASSERT_EQ(1u, config.android_sdks.count("P"));
- auto& out = config.android_sdk_groups["P"];
+ auto& out = config.android_sdks["P"];
AndroidSdk sdk;
sdk.min_sdk_version = 25;
@@ -567,7 +713,7 @@
EXPECT_THAT(config.gl_texture_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1"));
- auto& out = config.gl_texture_groups["dxt1"];
+ auto& out = config.gl_texture_groups["dxt1"].entry;
GlTexture texture{
std::string("GL_EXT_texture_compression_dxt1"),
@@ -596,7 +742,7 @@
EXPECT_THAT(config.device_feature_groups, SizeIs(1ul));
ASSERT_EQ(1u, config.device_feature_groups.count("low-latency"));
- auto& out = config.device_feature_groups["low-latency"];
+ auto& out = config.device_feature_groups["low-latency"].entry;
DeviceFeature low_latency = "android.hardware.audio.low_latency";
DeviceFeature pro = "android.hardware.audio.pro";
@@ -650,7 +796,7 @@
artifact.device_feature_group = {"df1"};
artifact.gl_texture_group = {"glx1"};
artifact.locale_group = {"en-AU"};
- artifact.android_sdk_group = {"v26"};
+ artifact.android_sdk = {"v26"};
{
auto result = artifact.ToArtifactName(
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
index 134153a..fb2f49b 100644
--- a/tools/aapt2/configuration/aapt2.xsd
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -8,22 +8,52 @@
<xsd:element name="post-process">
<xsd:complexType>
<xsd:sequence>
- <xsd:element name="groups" type="groups"/>
<xsd:element name="artifacts" type="artifacts"/>
+ <xsd:element name="android-sdks" type="android-sdks"/>
+ <xsd:element name="abi-groups" type="abi-groups"/>
+ <xsd:element name="screen-density-groups" type="screen-density-groups"/>
+ <xsd:element name="locale-groups" type="locale-groups"/>
+ <xsd:element name="gl-texture-groups" type="gl-texture-groups"/>
+ <xsd:element name="device-feature-groups" type="device-feature-groups"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
- <xsd:complexType name="groups">
+ <xsd:complexType name="android-sdks">
+ <xsd:sequence>
+ <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="abi-groups">
<xsd:sequence>
<xsd:element name="abi-group" type="abi-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="screen-density-groups">
+ <xsd:sequence>
<xsd:element name="screen-density-group" type="screen-density-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="locale-groups">
+ <xsd:sequence>
<xsd:element name="locale-group" type="locale-group" maxOccurs="unbounded"/>
- <xsd:element name="android-sdk-group" type="android-sdk-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="gl-texture-groups">
+ <xsd:sequence>
<xsd:element
name="gl-texture-group"
type="gl-texture-group"
maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="device-feature-groups">
+ <xsd:sequence>
<xsd:element name="device-feature-group" type="device-feature-group" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
@@ -38,8 +68,6 @@
<!-- Groups output artifacts together by dimension labels. -->
<xsd:complexType name="artifact">
- <xsd:attribute name="name" type="xsd:string"/>
- <xsd:attribute name="version" type="xsd:integer"/>
<xsd:attribute name="abi-group" type="xsd:string"/>
<xsd:attribute name="android-sdk-group" type="xsd:string"/>
<xsd:attribute name="device-feature-group" type="xsd:string"/>
@@ -52,7 +80,7 @@
<xsd:sequence>
<xsd:element name="gl-texture" type="gl-texture" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="gl-texture">
@@ -66,14 +94,14 @@
<xsd:sequence>
<xsd:element name="supports-feature" type="xsd:string" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="abi-group">
<xsd:sequence>
<xsd:element name="abi" type="abi-name" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:simpleType name="abi-name">
@@ -93,7 +121,7 @@
<xsd:sequence>
<xsd:element name="screen-density" type="screen-density" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:simpleType name="screen-density">
@@ -108,20 +136,14 @@
</xsd:restriction>
</xsd:simpleType>
- <xsd:complexType name="android-sdk-group">
- <xsd:sequence>
- <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
- </xsd:complexType>
-
<xsd:complexType name="android-sdk">
<!-- TODO(safarmer): Add permissions to add/remove. -->
<!-- TODO(safarmer): Add option for uncompressed native libs. -->
<xsd:sequence>
<xsd:element name="manifest" type="manifest"/>
</xsd:sequence>
- <xsd:attribute name="minSdkVersion" type="xsd:integer"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="minSdkVersion" type="xsd:integer" use="required"/>
<xsd:attribute name="targetSdkVersion" type="xsd:integer"/>
<xsd:attribute name="maxSdkVersion" type="xsd:integer"/>
</xsd:complexType>
@@ -135,7 +157,7 @@
<xsd:sequence>
<xsd:element name="locale" type="locale" maxOccurs="unbounded"/>
</xsd:sequence>
- <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="locale">
diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml
index ce31e61..d8aba09 100644
--- a/tools/aapt2/configuration/example/config.xml
+++ b/tools/aapt2/configuration/example/config.xml
@@ -1,70 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<post-process xmlns="http://schemas.android.com/tools/aapt">
- <groups>
- <abi-group label="arm">
- <abi>armeabi-v7a</abi>
- <abi>arm64-v8a</abi>
- </abi-group>
-
- <abi-group label="other">
- <abi>x86</abi>
- <abi>mips</abi>
- </abi-group>
-
- <screen-density-group label="large">
- <screen-density>xhdpi</screen-density>
- <screen-density>xxhdpi</screen-density>
- <screen-density>xxxhdpi</screen-density>
- </screen-density-group>
-
- <screen-density-group label="alldpi">
- <screen-density>ldpi</screen-density>
- <screen-density>mdpi</screen-density>
- <screen-density>hdpi</screen-density>
- <screen-density>xhdpi</screen-density>
- <screen-density>xxhdpi</screen-density>
- <screen-density>xxxhdpi</screen-density>
- </screen-density-group>
-
- <locale-group label="europe">
- <locale lang="en"/>
- <locale lang="es"/>
- <locale lang="fr"/>
- <locale lang="de" compressed="true"/>
- </locale-group>
-
- <locale-group label="north-america">
- <locale lang="en"/>
- <locale lang="es" region="MX"/>
- <locale lang="fr" region="CA" compressed="true"/>
- </locale-group>
-
- <locale-group label="all">
- <locale compressed="true"/>
- </locale-group>
-
- <android-sdk-group label="19">
- <android-sdk
- minSdkVersion="19"
- targetSdkVersion="24"
- maxSdkVersion="25">
- <manifest>
- <!--- manifest additions here XSLT? TODO -->
- </manifest>
- </android-sdk>
- </android-sdk-group>
-
- <gl-texture-group label="dxt1">
- <gl-texture name="GL_EXT_texture_compression_dxt1">
- <texture-path>assets/dxt1/*</texture-path>
- </gl-texture>
- </gl-texture-group>
-
- <device-feature-group label="low-latency">
- <supports-feature>android.hardware.audio.low_latency</supports-feature>
- </device-feature-group>
- </groups>
-
<artifacts>
<artifact-format>
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -87,4 +22,79 @@
device-feature-group="low-latency"/>
</artifacts>
+
+ <android-sdks>
+ <android-sdk
+ label="19"
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdks>
+
+ <abi-groups>
+ <abi-group label="arm">
+ <abi>armeabi-v7a</abi>
+ <abi>arm64-v8a</abi>
+ </abi-group>
+
+ <abi-group label="other">
+ <abi>x86</abi>
+ <abi>mips</abi>
+ </abi-group>
+ </abi-groups>
+
+ <screen-density-groups>
+ <screen-density-group label="large">
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+
+ <screen-density-group label="alldpi">
+ <screen-density>ldpi</screen-density>
+ <screen-density>mdpi</screen-density>
+ <screen-density>hdpi</screen-density>
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+ </screen-density-groups>
+
+ <locale-groups>
+ <locale-group label="europe">
+ <locale lang="en"/>
+ <locale lang="es"/>
+ <locale lang="fr"/>
+ <locale lang="de" compressed="true"/>
+ </locale-group>
+
+ <locale-group label="north-america">
+ <locale lang="en"/>
+ <locale lang="es" region="MX"/>
+ <locale lang="fr" region="CA" compressed="true"/>
+ </locale-group>
+
+ <locale-group label="all">
+ <locale compressed="true"/>
+ </locale-group>
+ </locale-groups>
+
+ <gl-texture-groups>
+ <gl-texture-group label="dxt1">
+ <gl-texture name="GL_EXT_texture_compression_dxt1">
+ <texture-path>assets/dxt1/*</texture-path>
+ </gl-texture>
+ </gl-texture-group>
+ </gl-texture-groups>
+
+ <device-feature-groups>
+ <device-feature-group label="low-latency">
+ <supports-feature>android.hardware.audio.low_latency</supports-feature>
+ </device-feature-group>
+ </device-feature-groups>
+
</post-process>
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 5078678..8d079ff 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -223,7 +223,7 @@
break;
case android::RES_TABLE_TYPE_SPEC_TYPE:
- if (!ParseTypeSpec(parser.chunk())) {
+ if (!ParseTypeSpec(package, parser.chunk())) {
return false;
}
break;
@@ -260,7 +260,8 @@
return true;
}
-bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
+bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package,
+ const ResChunk_header* chunk) {
if (type_pool_.getError() != NO_ERROR) {
diag_->Error(DiagMessage(source_) << "missing type string pool");
return false;
@@ -276,6 +277,34 @@
diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id);
return false;
}
+
+ // The data portion of this chunk contains entry_count 32bit entries,
+ // each one representing a set of flags.
+ const size_t entry_count = dtohl(type_spec->entryCount);
+
+ // There can only be 2^16 entries in a type, because that is the ID
+ // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
+ if (entry_count > std::numeric_limits<uint16_t>::max()) {
+ diag_->Error(DiagMessage(source_)
+ << "ResTable_typeSpec has too many entries (" << entry_count << ")");
+ return false;
+ }
+
+ const size_t data_size = util::DeviceToHost32(type_spec->header.size) -
+ util::DeviceToHost16(type_spec->header.headerSize);
+ if (entry_count * sizeof(uint32_t) > data_size) {
+ diag_->Error(DiagMessage(source_) << "ResTable_typeSpec too small to hold entries.");
+ return false;
+ }
+
+ // Record the type_spec_flags for later. We don't know resource names yet, and we need those
+ // to mark resources as overlayable.
+ const uint32_t* type_spec_flags = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<uintptr_t>(type_spec) + util::DeviceToHost16(type_spec->header.headerSize));
+ for (size_t i = 0; i < entry_count; i++) {
+ ResourceId id(package->id.value_or_default(0x0), type_spec->id, static_cast<size_t>(i));
+ entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]);
+ }
return true;
}
@@ -346,18 +375,34 @@
return false;
}
- if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
- diag_)) {
+ if (!table_->AddResourceWithIdMangled(name, res_id, config, {}, std::move(resource_value),
+ diag_)) {
return false;
}
- if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
- Symbol symbol;
- symbol.state = SymbolState::kPublic;
- symbol.source = source_.WithLine(0);
- if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) {
- return false;
+ const uint32_t type_spec_flags = entry_type_spec_flags_[res_id];
+ if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0 ||
+ (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) != 0) {
+ if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+ Visibility visibility;
+ visibility.level = Visibility::Level::kPublic;
+ visibility.source = source_.WithLine(0);
+ if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) {
+ return false;
+ }
}
+
+ if (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) {
+ Overlayable overlayable;
+ overlayable.source = source_.WithLine(0);
+ if (!table_->SetOverlayableMangled(name, overlayable, diag_)) {
+ return false;
+ }
+ }
+
+ // Erase the ID from the map once processed, so that we don't mark the same symbol more than
+ // once.
+ entry_type_spec_flags_.erase(res_id);
}
// Add this resource name->id mapping to the index so
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index 052f806..a1f9f83 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -50,7 +50,7 @@
bool ParseTable(const android::ResChunk_header* chunk);
bool ParsePackage(const android::ResChunk_header* chunk);
- bool ParseTypeSpec(const android::ResChunk_header* chunk);
+ bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
bool ParseLibrary(const android::ResChunk_header* chunk);
@@ -105,6 +105,9 @@
// A mapping of resource ID to resource name. When we finish parsing
// we use this to convert all resource IDs to symbolic references.
std::map<ResourceId, ResourceName> id_index_;
+
+ // A mapping of resource ID to type spec flags.
+ std::unordered_map<ResourceId, uint32_t> entry_type_spec_flags_;
};
} // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index a3034df..24a4112 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -283,7 +283,7 @@
T* result = buffer->NextBlock<T>();
ResTable_entry* out_entry = (ResTable_entry*)result;
- if (entry->entry->symbol_status.state == SymbolState::kPublic) {
+ if (entry->entry->visibility.level == Visibility::Level::kPublic) {
out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
}
@@ -443,10 +443,15 @@
// Populate the config masks for this entry.
- if (entry->symbol_status.state == SymbolState::kPublic) {
+ if (entry->visibility.level == Visibility::Level::kPublic) {
config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
}
+ if (entry->overlayable) {
+ config_masks[entry->id.value()] |=
+ util::HostToDevice32(ResTable_typeSpec::SPEC_OVERLAYABLE);
+ }
+
const size_t config_count = entry->values.size();
for (size_t i = 0; i < config_count; i++) {
const ConfigDescription& config = entry->values[i]->config;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index f0b80d2..51ccdc7 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -26,6 +26,7 @@
using namespace android;
+using ::testing::Gt;
using ::testing::IsNull;
using ::testing::NotNull;
@@ -250,15 +251,15 @@
const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
const auto value =
util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
- CHECK(table->AddResource(name, resid, ConfigDescription::DefaultConfig(), "",
- std::unique_ptr<Value>(value->Clone(nullptr)),
- context->GetDiagnostics()));
+ CHECK(table->AddResourceWithId(name, resid, ConfigDescription::DefaultConfig(), "",
+ std::unique_ptr<Value>(value->Clone(nullptr)),
+ context->GetDiagnostics()));
// Every few entries, write out a sparse_config value. This will give us the desired load.
if (i % stride == 0) {
- CHECK(table->AddResource(name, resid, sparse_config, "",
- std::unique_ptr<Value>(value->Clone(nullptr)),
- context->GetDiagnostics()));
+ CHECK(table->AddResourceWithId(name, resid, sparse_config, "",
+ std::unique_ptr<Value>(value->Clone(nullptr)),
+ context->GetDiagnostics()));
}
}
return table;
@@ -568,4 +569,25 @@
ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
}
+TEST_F(TableFlattenerTest, FlattenOverlayable) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddSimple("com.app.test:integer/overlayable", ResourceId(0x7f020000))
+ .Build();
+
+ ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
+ Overlayable{}, test::GetDiagnostics()));
+
+ ResTable res_table;
+ ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
+
+ const StringPiece16 overlayable_name(u"com.app.test:integer/overlayable");
+ uint32_t spec_flags = 0u;
+ ASSERT_THAT(res_table.identifierForName(overlayable_name.data(), overlayable_name.size(), nullptr,
+ 0u, nullptr, 0u, &spec_flags),
+ Gt(0u));
+ EXPECT_TRUE(spec_flags & android::ResTable_typeSpec::SPEC_OVERLAYABLE);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 0f0bce8..3d6975d 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -358,16 +358,16 @@
out_source->line = static_cast<size_t>(pb_source.position().line_number());
}
-static SymbolState DeserializeVisibilityFromPb(const pb::SymbolStatus_Visibility& pb_visibility) {
- switch (pb_visibility) {
- case pb::SymbolStatus_Visibility_PRIVATE:
- return SymbolState::kPrivate;
- case pb::SymbolStatus_Visibility_PUBLIC:
- return SymbolState::kPublic;
+static Visibility::Level DeserializeVisibilityFromPb(const pb::Visibility::Level& pb_level) {
+ switch (pb_level) {
+ case pb::Visibility::PRIVATE:
+ return Visibility::Level::kPrivate;
+ case pb::Visibility::PUBLIC:
+ return Visibility::Level::kPublic;
default:
break;
}
- return SymbolState::kUndefined;
+ return Visibility::Level::kUndefined;
}
static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
@@ -402,28 +402,48 @@
}
// Deserialize the symbol status (public/private with source and comments).
- if (pb_entry.has_symbol_status()) {
- const pb::SymbolStatus& pb_status = pb_entry.symbol_status();
- if (pb_status.has_source()) {
- DeserializeSourceFromPb(pb_status.source(), src_pool, &entry->symbol_status.source);
+ if (pb_entry.has_visibility()) {
+ const pb::Visibility& pb_visibility = pb_entry.visibility();
+ if (pb_visibility.has_source()) {
+ DeserializeSourceFromPb(pb_visibility.source(), src_pool, &entry->visibility.source);
}
+ entry->visibility.comment = pb_visibility.comment();
- entry->symbol_status.comment = pb_status.comment();
- entry->symbol_status.allow_new = pb_status.allow_new();
-
- const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());
- entry->symbol_status.state = visibility;
- if (visibility == SymbolState::kPublic) {
+ const Visibility::Level level = DeserializeVisibilityFromPb(pb_visibility.level());
+ entry->visibility.level = level;
+ if (level == Visibility::Level::kPublic) {
// Propagate the public visibility up to the Type.
- type->symbol_status.state = SymbolState::kPublic;
- } else if (visibility == SymbolState::kPrivate) {
+ type->visibility_level = Visibility::Level::kPublic;
+ } else if (level == Visibility::Level::kPrivate) {
// Only propagate if no previous state was assigned.
- if (type->symbol_status.state == SymbolState::kUndefined) {
- type->symbol_status.state = SymbolState::kPrivate;
+ if (type->visibility_level == Visibility::Level::kUndefined) {
+ type->visibility_level = Visibility::Level::kPrivate;
}
}
}
+ if (pb_entry.has_allow_new()) {
+ const pb::AllowNew& pb_allow_new = pb_entry.allow_new();
+
+ AllowNew allow_new;
+ if (pb_allow_new.has_source()) {
+ DeserializeSourceFromPb(pb_allow_new.source(), src_pool, &allow_new.source);
+ }
+ allow_new.comment = pb_allow_new.comment();
+ entry->allow_new = std::move(allow_new);
+ }
+
+ if (pb_entry.has_overlayable()) {
+ const pb::Overlayable& pb_overlayable = pb_entry.overlayable();
+
+ Overlayable overlayable;
+ if (pb_overlayable.has_source()) {
+ DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source);
+ }
+ overlayable.comment = pb_overlayable.comment();
+ entry->overlayable = std::move(overlayable);
+ }
+
ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
pb_entry.entry_id().id());
if (resid.is_valid()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 97ce01a..78f1281 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -43,16 +43,16 @@
}
}
-static pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) {
+static pb::Visibility::Level SerializeVisibilityToPb(Visibility::Level state) {
switch (state) {
- case SymbolState::kPrivate:
- return pb::SymbolStatus_Visibility_PRIVATE;
- case SymbolState::kPublic:
- return pb::SymbolStatus_Visibility_PUBLIC;
+ case Visibility::Level::kPrivate:
+ return pb::Visibility::PRIVATE;
+ case Visibility::Level::kPublic:
+ return pb::Visibility::PUBLIC;
default:
break;
}
- return pb::SymbolStatus_Visibility_UNKNOWN;
+ return pb::Visibility::UNKNOWN;
}
void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_config) {
@@ -293,12 +293,26 @@
}
pb_entry->set_name(entry->name);
- // Write the SymbolStatus struct.
- pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status();
- pb_status->set_visibility(SerializeVisibilityToPb(entry->symbol_status.state));
- SerializeSourceToPb(entry->symbol_status.source, &source_pool, pb_status->mutable_source());
- pb_status->set_comment(entry->symbol_status.comment);
- pb_status->set_allow_new(entry->symbol_status.allow_new);
+ // Write the Visibility struct.
+ pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
+ pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
+ SerializeSourceToPb(entry->visibility.source, &source_pool,
+ pb_visibility->mutable_source());
+ pb_visibility->set_comment(entry->visibility.comment);
+
+ if (entry->allow_new) {
+ pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
+ SerializeSourceToPb(entry->allow_new.value().source, &source_pool,
+ pb_allow_new->mutable_source());
+ pb_allow_new->set_comment(entry->allow_new.value().comment);
+ }
+
+ if (entry->overlayable) {
+ pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable();
+ SerializeSourceToPb(entry->overlayable.value().source, &source_pool,
+ pb_overlayable->mutable_source());
+ pb_overlayable->set_comment(entry->overlayable.value().comment);
+ }
for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 9649a4d..d7f83fd 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -44,14 +44,15 @@
.AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main")
.AddString("com.app.a:string/text", {}, "hi")
.AddValue("com.app.a:id/foo", {}, util::make_unique<Id>())
- .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/)
+ .SetSymbolState("com.app.a:bool/foo", {}, Visibility::Level::kUndefined,
+ true /*allow_new*/)
.Build();
- Symbol public_symbol;
- public_symbol.state = SymbolState::kPublic;
- ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"),
- ResourceId(0x7f020000), public_symbol,
- context->GetDiagnostics()));
+ Visibility public_symbol;
+ public_symbol.level = Visibility::Level::kPublic;
+ ASSERT_TRUE(table->SetVisibilityWithId(test::ParseNameOrDie("com.app.a:layout/main"),
+ public_symbol, ResourceId(0x7f020000),
+ context->GetDiagnostics()));
Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
ASSERT_THAT(id, NotNull());
@@ -89,6 +90,10 @@
test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {},
util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
+ // Make an overlayable resource.
+ ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
+ Overlayable{}, test::GetDiagnostics()));
+
pb::ResourceTable pb_table;
SerializeTableToPb(*table, &pb_table);
@@ -110,13 +115,13 @@
new_table.FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
ASSERT_TRUE(result);
- EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic));
- EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+ EXPECT_THAT(result.value().type->visibility_level, Eq(Visibility::Level::kPublic));
+ EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
ASSERT_TRUE(result);
- EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));
- EXPECT_TRUE(result.value().entry->symbol_status.allow_new);
+ EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+ EXPECT_TRUE(result.value().entry->allow_new);
// Find the product-dependent values
BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
@@ -148,6 +153,12 @@
EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b"));
EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u));
EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
+
+ Maybe<ResourceTable::SearchResult> search_result =
+ new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable"));
+ ASSERT_TRUE(search_result);
+ ASSERT_THAT(search_result.value().entry, NotNull());
+ EXPECT_TRUE(search_result.value().entry->overlayable);
}
TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 8c8c254..6b07b1e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -191,14 +191,14 @@
const JavaClassGeneratorOptions& options)
: context_(context), table_(table), options_(options) {}
-bool JavaClassGenerator::SkipSymbol(SymbolState state) {
+bool JavaClassGenerator::SkipSymbol(Visibility::Level level) {
switch (options_.types) {
case JavaClassGeneratorOptions::SymbolTypes::kAll:
return false;
case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
- return state == SymbolState::kUndefined;
+ return level == Visibility::Level::kUndefined;
case JavaClassGeneratorOptions::SymbolTypes::kPublic:
- return state != SymbolState::kPublic;
+ return level != Visibility::Level::kPublic;
}
return true;
}
@@ -444,8 +444,8 @@
AnnotationProcessor* processor = resource_member->GetCommentBuilder();
// Add the comments from any <public> tags.
- if (entry.symbol_status.state != SymbolState::kUndefined) {
- processor->AppendComment(entry.symbol_status.comment);
+ if (entry.visibility.level != Visibility::Level::kUndefined) {
+ processor->AppendComment(entry.visibility.comment);
}
// Add the comments from all configurations of this entry.
@@ -484,7 +484,7 @@
Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
const StringPiece& package_name_to_generate,
const ResourceEntry& entry) {
- if (SkipSymbol(entry.symbol_status.state)) {
+ if (SkipSymbol(entry.visibility.level)) {
return {};
}
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 4992f07..853120b 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -82,7 +82,7 @@
static std::string TransformToFieldName(const android::StringPiece& symbol);
private:
- bool SkipSymbol(SymbolState state);
+ bool SkipSymbol(Visibility::Level state);
bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol);
// Returns the unmangled resource entry name if the unmangled package is the same as
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 02f4cb1..5beb594 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -139,8 +139,8 @@
.AddSimple("android:id/one", ResourceId(0x01020000))
.AddSimple("android:id/two", ResourceId(0x01020001))
.AddSimple("android:id/three", ResourceId(0x01020002))
- .SetSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
- .SetSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
+ .SetSymbolState("android:id/one", ResourceId(0x01020000), Visibility::Level::kPublic)
+ .SetSymbolState("android:id/two", ResourceId(0x01020001), Visibility::Level::kPrivate)
.Build();
std::unique_ptr<IAaptContext> context =
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index a68df1d..da05dc3 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -430,7 +430,10 @@
return false;
}
- if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist, context->GetDiagnostics(), doc)) {
+ xml::XmlActionExecutorPolicy policy = options_.warn_validation
+ ? xml::XmlActionExecutorPolicy::kWhitelistWarning
+ : xml::XmlActionExecutorPolicy::kWhitelist;
+ if (!executor.Execute(policy, context->GetDiagnostics(), doc)) {
return false;
}
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index f5715f6..0caa52e 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -57,6 +57,11 @@
// The version codename of the framework being compiled against to set for
// 'android:compileSdkVersionCodename' in the <manifest> tag.
Maybe<std::string> compile_sdk_version_codename;
+
+ // Wether validation errors should be treated only as warnings. If this is 'true', then an
+ // incorrect node will not result in an error, but only as a warning, and the parsing will
+ // continue.
+ bool warn_validation = false;
};
// Verifies that the manifest is correctly formed and inserts defaults where specified with
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 1320dcd..c6f895b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -112,7 +112,9 @@
}
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
- ManifestFixerOptions options = {std::string("8"), std::string("22")};
+ ManifestFixerOptions options;
+ options.min_sdk_version_default = std::string("8");
+ options.target_sdk_version_default = std::string("22");
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -193,7 +195,9 @@
}
TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) {
- ManifestFixerOptions options = {std::string("8"), std::string("22")};
+ ManifestFixerOptions options;
+ options.min_sdk_version_default = std::string("8");
+ options.target_sdk_version_default = std::string("22");
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
@@ -467,4 +471,27 @@
EXPECT_THAT(attr->value, StrEq("P"));
}
+TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <beep/>
+ </manifest>)";
+ ManifestFixerOptions options;
+ options.warn_validation = true;
+
+ // Unexpected element should result in a warning if the flag is set to 'true'.
+ std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+ ASSERT_THAT(manifest, NotNull());
+
+ // Unexpected element should result in an error if the flag is set to 'false'.
+ options.warn_validation = false;
+ manifest = VerifyWithOptions(input, options);
+ ASSERT_THAT(manifest, IsNull());
+
+ // By default the flag should be set to 'false'.
+ manifest = Verify(input);
+ ASSERT_THAT(manifest, IsNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index eee4b60..675b02a 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -62,7 +62,7 @@
continue;
}
- if (type->symbol_status.state != SymbolState::kPublic) {
+ if (type->visibility_level != Visibility::Level::kPublic) {
// No public attributes, so we can safely leave these private attributes
// where they are.
continue;
@@ -72,7 +72,7 @@
move_if(type->entries, std::back_inserter(private_attr_entries),
[](const std::unique_ptr<ResourceEntry>& entry) -> bool {
- return entry->symbol_status.state != SymbolState::kPublic;
+ return entry->visibility.level != Visibility::Level::kPublic;
});
if (private_attr_entries.empty()) {
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index 7fcf6e7..168234b 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -30,9 +30,9 @@
.AddSimple("android:attr/publicB")
.AddSimple("android:attr/privateB")
.SetSymbolState("android:attr/publicA", ResourceId(0x01010000),
- SymbolState::kPublic)
+ Visibility::Level::kPublic)
.SetSymbolState("android:attr/publicB", ResourceId(0x01010000),
- SymbolState::kPublic)
+ Visibility::Level::kPublic)
.Build();
PrivateAttributeMover mover;
@@ -81,7 +81,7 @@
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.AddSimple("android:attr/pub")
- .SetSymbolState("android:attr/pub", ResourceId(0x01010000), SymbolState::kPublic)
+ .SetSymbolState("android:attr/pub", ResourceId(0x01010000), Visibility::Level::kPublic)
.Build();
ResourceTablePackage* package = table->FindPackage("android");
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index ad7d8b6..b8f8804 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -363,8 +363,8 @@
NameMangler::Unmangle(&name.entry, &name.package);
// Symbol state information may be lost if there is no value for the resource.
- if (entry->symbol_status.state != SymbolState::kUndefined && entry->values.empty()) {
- context->GetDiagnostics()->Error(DiagMessage(entry->symbol_status.source)
+ if (entry->visibility.level != Visibility::Level::kUndefined && entry->values.empty()) {
+ context->GetDiagnostics()->Error(DiagMessage(entry->visibility.source)
<< "no definition for declared symbol '" << name << "'");
error = true;
}
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 58d0607..e819f51 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -83,44 +83,58 @@
static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type,
ResourceTableType* src_type) {
- if (dst_type->symbol_status.state < src_type->symbol_status.state) {
+ if (src_type->visibility_level > dst_type->visibility_level) {
// The incoming type's visibility is stronger, so we should override the visibility.
- if (src_type->symbol_status.state == SymbolState::kPublic) {
+ if (src_type->visibility_level == Visibility::Level::kPublic) {
// Only copy the ID if the source is public, or else the ID is meaningless.
dst_type->id = src_type->id;
}
- dst_type->symbol_status = std::move(src_type->symbol_status);
- } else if (dst_type->symbol_status.state == SymbolState::kPublic &&
- src_type->symbol_status.state == SymbolState::kPublic &&
- dst_type->id && src_type->id &&
- dst_type->id.value() != src_type->id.value()) {
+ dst_type->visibility_level = src_type->visibility_level;
+ } else if (dst_type->visibility_level == Visibility::Level::kPublic &&
+ src_type->visibility_level == Visibility::Level::kPublic && dst_type->id &&
+ src_type->id && dst_type->id.value() != src_type->id.value()) {
// Both types are public and have different IDs.
- context->GetDiagnostics()->Error(DiagMessage(src)
- << "cannot merge type '" << src_type->type
- << "': conflicting public IDs");
+ context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge type '" << src_type->type
+ << "': conflicting public IDs");
return false;
}
return true;
}
-static bool MergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dst_entry,
- ResourceEntry* src_entry) {
- if (dst_entry->symbol_status.state < src_entry->symbol_status.state) {
- // The incoming type's visibility is stronger, so we should override the visibility.
- if (src_entry->symbol_status.state == SymbolState::kPublic) {
- // Only copy the ID if the source is public, or else the ID is meaningless.
+static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
+ ResourceEntry* dst_entry, ResourceEntry* src_entry) {
+ // Copy over the strongest visibility.
+ if (src_entry->visibility.level > dst_entry->visibility.level) {
+ // Only copy the ID if the source is public, or else the ID is meaningless.
+ if (src_entry->visibility.level == Visibility::Level::kPublic) {
dst_entry->id = src_entry->id;
}
- dst_entry->symbol_status = std::move(src_entry->symbol_status);
- } else if (src_entry->symbol_status.state == SymbolState::kPublic &&
- dst_entry->symbol_status.state == SymbolState::kPublic &&
- dst_entry->id && src_entry->id &&
- dst_entry->id.value() != src_entry->id.value()) {
+ dst_entry->visibility = std::move(src_entry->visibility);
+ } else if (src_entry->visibility.level == Visibility::Level::kPublic &&
+ dst_entry->visibility.level == Visibility::Level::kPublic && dst_entry->id &&
+ src_entry->id && src_entry->id != dst_entry->id) {
// Both entries are public and have different IDs.
context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge entry '" << src_entry->name
<< "': conflicting public IDs");
return false;
}
+
+ // Copy over the rest of the properties, if needed.
+ if (src_entry->allow_new) {
+ dst_entry->allow_new = std::move(src_entry->allow_new);
+ }
+
+ if (src_entry->overlayable) {
+ if (dst_entry->overlayable && !overlay) {
+ context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
+ << "duplicate overlayable declaration for resource '"
+ << src_entry->name << "'");
+ context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
+ << "previous declaration here");
+ return false;
+ }
+ dst_entry->overlayable = std::move(src_entry->overlayable);
+ }
return true;
}
@@ -202,7 +216,7 @@
}
ResourceEntry* dst_entry;
- if (allow_new_resources || src_entry->symbol_status.allow_new) {
+ if (allow_new_resources || src_entry->allow_new) {
dst_entry = dst_type->FindOrCreateEntry(entry_name);
} else {
dst_entry = dst_type->FindEntry(entry_name);
@@ -220,7 +234,7 @@
continue;
}
- if (!MergeEntry(context_, src, dst_entry, src_entry.get())) {
+ if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get())) {
error = true;
continue;
}
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 6aab8de..34461c6 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -182,14 +182,12 @@
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
ResourceTable final_table;
@@ -205,14 +203,12 @@
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), Visibility::Level::kPublic)
.Build();
ResourceTable final_table;
@@ -228,14 +224,12 @@
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002),
- SymbolState::kPublic)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), Visibility::Level::kPublic)
.Build();
ResourceTable final_table;
@@ -253,7 +247,7 @@
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("", 0x7f)
- .SetSymbolState("bool/foo", {}, SymbolState::kUndefined, true /*allow new overlay*/)
+ .SetSymbolState("bool/foo", {}, Visibility::Level::kUndefined, true /*allow new overlay*/)
.AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
.Build();
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 16898d6..991faad 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -120,8 +120,6 @@
}
bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
- // TODO(safarmer): Handle APK version codes for the generated APKs.
-
std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
std::unordered_set<std::string> filtered_artifacts;
std::unordered_set<std::string> kept_artifacts;
@@ -237,8 +235,8 @@
splits.config_filter = &axis_filter;
}
- if (artifact.android_sdk && artifact.android_sdk.value().min_sdk_version) {
- wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version.value());
+ if (artifact.android_sdk) {
+ wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version);
}
std::unique_ptr<ResourceTable> table = old_table.Clone();
@@ -301,7 +299,7 @@
if (xml::Attribute* min_sdk_attr =
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
// Populate with a pre-compiles attribute to we don't need to relink etc.
- const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+ const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version);
min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
} else {
// There was no minSdkVersion. This is strange since at this point we should have been
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 0cfc0bd..3cae0e8 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -190,7 +190,7 @@
ResourceTable::SearchResult sr = result.value();
std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
- symbol->is_public = (sr.entry->symbol_status.state == SymbolState::kPublic);
+ symbol->is_public = (sr.entry->visibility.level == Visibility::Level::kPublic);
if (sr.package->id && sr.type->id && sr.entry->id) {
symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 9d49ca6..e991743 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -233,13 +233,13 @@
ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type);
if (!split_type->id) {
split_type->id = type->id;
- split_type->symbol_status = type->symbol_status;
+ split_type->visibility_level = type->visibility_level;
}
ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name);
if (!split_entry->id) {
split_entry->id = entry->id;
- split_entry->symbol_status = entry->symbol_status;
+ split_entry->visibility = entry->visibility;
}
// Copy the selected values into the new Split Entry.
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 88897a8..495a48a 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -26,6 +26,7 @@
using ::aapt::configuration::Abi;
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::ConfiguredArtifact;
+using ::aapt::configuration::GetOrCreateGroup;
using ::aapt::io::StringInputStream;
using ::android::StringPiece;
@@ -116,19 +117,20 @@
const ResourceId& id,
std::unique_ptr<Value> value) {
ResourceName res_name = ParseNameOrDie(name);
- CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value),
- GetDiagnostics()));
+ CHECK(table_->AddResourceWithIdMangled(res_name, id, config, {}, std::move(value),
+ GetDiagnostics()));
return *this;
}
ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
- const ResourceId& id, SymbolState state,
+ const ResourceId& id,
+ Visibility::Level level,
bool allow_new) {
ResourceName res_name = ParseNameOrDie(name);
- Symbol symbol;
- symbol.state = state;
- symbol.allow_new = allow_new;
- CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics()));
+ Visibility visibility;
+ visibility.level = level;
+ CHECK(table_->SetVisibilityWithIdMangled(res_name, visibility, id, GetDiagnostics()));
+ CHECK(table_->SetAllowNewMangled(res_name, AllowNew{}, GetDiagnostics()));
return *this;
}
@@ -226,6 +228,11 @@
return *this;
}
+ArtifactBuilder& ArtifactBuilder::SetVersion(int version) {
+ artifact_.version = version;
+ return *this;
+}
+
ArtifactBuilder& ArtifactBuilder::AddAbi(configuration::Abi abi) {
artifact_.abis.push_back(abi);
return *this;
@@ -250,5 +257,54 @@
return artifact_;
}
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAbiGroup(
+ const std::string& label, std::vector<configuration::Abi> abis) {
+ return AddGroup(label, &config_.abi_groups, std::move(abis));
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDensityGroup(
+ const std::string& label, std::vector<std::string> densities) {
+ std::vector<ConfigDescription> configs;
+ for (const auto& density : densities) {
+ configs.push_back(test::ParseConfigOrDie(density));
+ }
+ return AddGroup(label, &config_.screen_density_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddLocaleGroup(
+ const std::string& label, std::vector<std::string> locales) {
+ std::vector<ConfigDescription> configs;
+ for (const auto& locale : locales) {
+ configs.push_back(test::ParseConfigOrDie(locale));
+ }
+ return AddGroup(label, &config_.locale_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDeviceFeatureGroup(
+ const std::string& label) {
+ return AddGroup(label, &config_.device_feature_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddGlTextureGroup(
+ const std::string& label) {
+ return AddGroup(label, &config_.gl_texture_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAndroidSdk(
+ std::string label, int min_sdk) {
+ config_.android_sdks[label] = AndroidSdk::ForMinSdk(min_sdk);
+ return *this;
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact(
+ configuration::ConfiguredArtifact artifact) {
+ config_.artifacts.push_back(std::move(artifact));
+ return *this;
+}
+
+configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() {
+ return config_;
+}
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 2f83b78..0d7451b 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -68,7 +68,7 @@
ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config,
const ResourceId& id, std::unique_ptr<Value> value);
ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
- SymbolState state, bool allow_new = false);
+ Visibility::Level level, bool allow_new = false);
StringPool* string_pool();
std::unique_ptr<ResourceTable> Build();
@@ -160,6 +160,7 @@
ArtifactBuilder() = default;
ArtifactBuilder& SetName(const std::string& name);
+ ArtifactBuilder& SetVersion(int version);
ArtifactBuilder& AddAbi(configuration::Abi abi);
ArtifactBuilder& AddDensity(const ConfigDescription& density);
ArtifactBuilder& AddLocale(const ConfigDescription& locale);
@@ -167,9 +168,41 @@
configuration::OutputArtifact Build();
private:
+ DISALLOW_COPY_AND_ASSIGN(ArtifactBuilder);
+
configuration::OutputArtifact artifact_;
};
+class PostProcessingConfigurationBuilder {
+ public:
+ PostProcessingConfigurationBuilder() = default;
+
+ PostProcessingConfigurationBuilder& AddAbiGroup(const std::string& label,
+ std::vector<configuration::Abi> abis = {});
+ PostProcessingConfigurationBuilder& AddDensityGroup(const std::string& label,
+ std::vector<std::string> densities = {});
+ PostProcessingConfigurationBuilder& AddLocaleGroup(const std::string& label,
+ std::vector<std::string> locales = {});
+ PostProcessingConfigurationBuilder& AddDeviceFeatureGroup(const std::string& label);
+ PostProcessingConfigurationBuilder& AddGlTextureGroup(const std::string& label);
+ PostProcessingConfigurationBuilder& AddAndroidSdk(std::string label, int min_sdk);
+ PostProcessingConfigurationBuilder& AddArtifact(configuration::ConfiguredArtifact artrifact);
+
+ configuration::PostProcessingConfiguration Build();
+
+ private:
+ template <typename T>
+ inline PostProcessingConfigurationBuilder& AddGroup(const std::string& label,
+ configuration::Group<T>* group,
+ std::vector<T> to_add = {}) {
+ auto& values = GetOrCreateGroup(label, group);
+ values.insert(std::begin(values), std::begin(to_add), std::end(to_add));
+ return *this;
+ }
+
+ configuration::PostProcessingConfiguration config_;
+};
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 602a902..cb844f0 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -66,7 +66,7 @@
continue;
}
- if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ if (policy != XmlActionExecutorPolicy::kNone) {
DiagMessage error_msg(child_el->line_number);
error_msg << "unexpected element ";
PrintElementToDiagMessage(child_el, &error_msg);
@@ -74,8 +74,14 @@
for (const StringPiece& element : *bread_crumb) {
error_msg << "<" << element << ">";
}
- diag->Error(error_msg);
- error = true;
+ if (policy == XmlActionExecutorPolicy::kWhitelistWarning) {
+ // Treat the error only as a warning.
+ diag->Warn(error_msg);
+ } else {
+ // Policy is XmlActionExecutorPolicy::kWhitelist, we should fail.
+ diag->Error(error_msg);
+ error = true;
+ }
}
}
}
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index df70100..f689b2a 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -34,10 +34,15 @@
// Actions are run if elements are matched, errors occur only when actions return false.
kNone,
- // The actions defined must match and run. If an element is found that does
- // not match an action, an error occurs.
+ // The actions defined must match and run. If an element is found that does not match an action,
+ // an error occurs.
// Note: namespaced elements are always ignored.
kWhitelist,
+
+ // The actions defined should match and run. if an element is found that does not match an
+ // action, a warning is printed.
+ // Note: namespaced elements are always ignored.
+ kWhitelistWarning,
};
// Contains the actions to perform at this XML node. This is a recursive data structure that
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 4e9391d..1121ead 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -32,26 +32,6 @@
shared_libs: ["libprotoc"],
}
-cc_library {
- name: "streamingflags",
- host_supported: true,
- proto: {
- export_proto_headers: true,
- include_dirs: ["external/protobuf/src"],
- },
-
- target: {
- host: {
- proto: {
- type: "full",
- },
- srcs: [
- "stream.proto",
- ],
- },
- },
-}
-
cc_binary_host {
name: "protoc-gen-javastream",
srcs: [
@@ -68,5 +48,4 @@
],
defaults: ["protoc-gen-stream-defaults"],
- static_libs: ["streamingflags"],
}
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index 745b3dc..d6b9d81 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -2,8 +2,6 @@
#include "stream_proto_utils.h"
#include "string_utils.h"
-#include <frameworks/base/tools/streaming_proto/stream.pb.h>
-
#include <iomanip>
#include <iostream>
#include <sstream>
@@ -12,18 +10,14 @@
using namespace google::protobuf::io;
using namespace std;
+const bool GENERATE_MAPPING = true;
+
static string
make_filename(const FileDescriptorProto& file_descriptor)
{
return file_descriptor.name() + ".h";
}
-static inline bool
-should_generate_enums_mapping(const EnumDescriptorProto& enu)
-{
- return enu.options().GetExtension(stream_enum).enable_enums_mapping();
-}
-
static void
write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
{
@@ -36,7 +30,7 @@
<< " = " << value.number() << ";" << endl;
}
- if (should_generate_enums_mapping(enu)) {
+ if (GENERATE_MAPPING) {
string name = make_constant_name(enu.name());
string prefix = name + "_";
text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
@@ -79,23 +73,11 @@
text << endl;
}
-static inline bool
-should_generate_fields_mapping(const DescriptorProto& message)
-{
- return message.options().GetExtension(stream_msg).enable_fields_mapping();
-}
-
-static inline bool
-should_generate_fields_mapping_recursively(const DescriptorProto& message) {
- return message.options().GetExtension(stream_msg).enable_fields_mapping_recursively();
-}
-
static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent, bool genMapping)
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
{
int N;
const string indented = indent + INDENT;
- genMapping |= should_generate_fields_mapping_recursively(message);
text << indent << "// message " << message.name() << endl;
text << indent << "namespace " << message.name() << " {" << endl;
@@ -109,7 +91,7 @@
// Nested classes
N = message.nested_type_size();
for (int i=0; i<N; i++) {
- write_message(text, message.nested_type(i), indented, genMapping);
+ write_message(text, message.nested_type(i), indented);
}
// Fields
@@ -118,7 +100,7 @@
write_field(text, message.field(i), indented);
}
- if (genMapping | should_generate_fields_mapping(message)) {
+ if (GENERATE_MAPPING) {
N = message.field_size();
text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
@@ -167,7 +149,7 @@
N = file_descriptor.message_type_size();
for (size_t i=0; i<N; i++) {
- write_message(text, file_descriptor.message_type(i), "", false);
+ write_message(text, file_descriptor.message_type(i), "");
}
for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto
deleted file mode 100644
index e9b24a8..0000000
--- a/tools/streaming_proto/stream.proto
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-
-import "google/protobuf/descriptor.proto";
-
-package android.stream_proto;
-
-// This option tells streaming proto plugin to compile .proto files with extra features.
-message MessageOptions {
- // creates a mapping of field names of the message to its field ids
- optional bool enable_fields_mapping = 1;
-
- // creates mapping between field names to its field ids and recursively for its submessages.
- optional bool enable_fields_mapping_recursively = 2;
-}
-
-extend google.protobuf.MessageOptions {
- // Flags used by streaming proto plugins
- optional MessageOptions stream_msg = 126856794;
-}
-
-message EnumOptions {
- // creates a mapping of enum names to its values, strip its prefix enum type for each value
- optional bool enable_enums_mapping = 1;
-}
-
-extend google.protobuf.EnumOptions {
- // Flags used by streaming proto plugins
- optional EnumOptions stream_enum = 126856794;
-}