Merge changes Ie1eb63dc,Ib7cc4d52
* changes:
Renames setCallbackOnComplete method in ContextHubTransaction
Rename ContextHubTransaction.Result IntDef
diff --git a/api/current.txt b/api/current.txt
index c8a85d4..6bb7584 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5200,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";
@@ -5483,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
}
@@ -7032,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";
@@ -8223,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);
@@ -32259,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";
@@ -44718,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();
@@ -47671,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();
@@ -47732,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
@@ -47826,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;
@@ -48618,6 +48636,7 @@
method public void cancel();
method public void commit();
method public void disableAutofillServices();
+ method public android.content.ComponentName getAutofillServiceComponentName();
method public android.service.autofill.UserData getUserData();
method public boolean hasEnabledAutofillServices();
method public boolean isAutofillSupported();
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 30530a3..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";
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/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..86d0fd6 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/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/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9513b9b..430c28b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -16,6 +16,7 @@
package android.os;
+import android.app.ActivityManager;
import android.app.job.JobParameters;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -33,6 +34,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
@@ -327,7 +329,8 @@
*
* Other types might include times spent in foreground, background etc.
*/
- private final String UID_TIMES_TYPE_ALL = "A";
+ @VisibleForTesting
+ public static final String UID_TIMES_TYPE_ALL = "A";
/**
* State for keeping track of counting information.
@@ -507,6 +510,31 @@
}
/**
+ * Maps the ActivityManager procstate into corresponding BatteryStats procstate.
+ */
+ public static int mapToInternalProcessState(int procState) {
+ if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ return ActivityManager.PROCESS_STATE_NONEXISTENT;
+ } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ return Uid.PROCESS_STATE_TOP;
+ } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ // Persistent and other foreground states go here.
+ return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ // Persistent and other foreground states go here.
+ return Uid.PROCESS_STATE_FOREGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+ return Uid.PROCESS_STATE_BACKGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ return Uid.PROCESS_STATE_TOP_SLEEPING;
+ } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+ return Uid.PROCESS_STATE_HEAVY_WEIGHT;
+ } else {
+ return Uid.PROCESS_STATE_CACHED;
+ }
+ }
+
+ /**
* The statistics associated with a particular uid.
*/
public static abstract class Uid {
@@ -645,6 +673,15 @@
public abstract long[] getCpuFreqTimes(int which);
public abstract long[] getScreenOffCpuFreqTimes(int which);
+ /**
+ * Returns cpu times of an uid at a particular process state.
+ */
+ public abstract long[] getCpuFreqTimes(int which, int procState);
+ /**
+ * Returns cpu times of an uid while the screen if off at a particular process state.
+ */
+ public abstract long[] getScreenOffCpuFreqTimes(int which, int procState);
+
// Note: the following times are disjoint. They can be added together to find the
// total time a uid has had any processes running at all.
@@ -689,11 +726,32 @@
*/
public static final int NUM_PROCESS_STATE = 7;
+ // Used in dump
static final String[] PROCESS_STATE_NAMES = {
"Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
"Cached"
};
+ // Used in checkin dump
+ @VisibleForTesting
+ public static final String[] UID_PROCESS_TYPES = {
+ "T", // TOP
+ "FS", // FOREGROUND_SERVICE
+ "F", // FOREGROUND
+ "B", // BACKGROUND
+ "TS", // TOP_SLEEPING
+ "HW", // HEAVY_WEIGHT
+ "C" // CACHED
+ };
+
+ /**
+ * When the process exits one of these states, we need to make sure cpu time in this state
+ * is not attributed to any non-critical process states.
+ */
+ public static final int[] CRITICAL_PROC_STATES = {
+ PROCESS_STATE_TOP, PROCESS_STATE_FOREGROUND_SERVICE, PROCESS_STATE_FOREGROUND
+ };
+
public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
public abstract Timer getProcessStateTimer(int state);
@@ -4002,6 +4060,29 @@
dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA, UID_TIMES_TYPE_ALL,
cpuFreqTimeMs.length, sb.toString());
}
+
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ final long[] timesMs = u.getCpuFreqTimes(which, procState);
+ if (timesMs != null && timesMs.length == cpuFreqs.length) {
+ sb.setLength(0);
+ for (int i = 0; i < timesMs.length; ++i) {
+ sb.append((i == 0 ? "" : ",") + timesMs[i]);
+ }
+ final long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(
+ which, procState);
+ if (screenOffTimesMs != null) {
+ for (int i = 0; i < screenOffTimesMs.length; ++i) {
+ sb.append("," + screenOffTimesMs[i]);
+ }
+ } else {
+ for (int i = 0; i < timesMs.length; ++i) {
+ sb.append(",0");
+ }
+ }
+ dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA,
+ Uid.UID_PROCESS_TYPES[procState], timesMs.length, sb.toString());
+ }
+ }
}
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
@@ -5612,6 +5693,30 @@
pw.println(sb.toString());
}
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ final long[] cpuTimes = u.getCpuFreqTimes(which, procState);
+ if (cpuTimes != null) {
+ sb.setLength(0);
+ sb.append(" Cpu times per freq at state "
+ + Uid.PROCESS_STATE_NAMES[procState] + ":");
+ for (int i = 0; i < cpuTimes.length; ++i) {
+ sb.append(" " + cpuTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+
+ final long[] screenOffCpuTimes = u.getScreenOffCpuFreqTimes(which, procState);
+ if (screenOffCpuTimes != null) {
+ sb.setLength(0);
+ sb.append(" Screen-off cpu times per freq at state "
+ + Uid.PROCESS_STATE_NAMES[procState] + ":");
+ for (int i = 0; i < screenOffCpuTimes.length; ++i) {
+ sb.append(" " + screenOffCpuTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+ }
+
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
= u.getProcessStats();
for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
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 dd59142..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,8 +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;
+ }
+
+ /**
+ * Create a new {@code WorkChain} associated with this WorkSource and return it.
+ *
+ * @hide
+ */
+ public WorkChain createWorkChain() {
+ if (mChains == null) {
+ mChains = new ArrayList<>(4);
+ }
+
+ 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) {
@@ -648,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;
@@ -658,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
@@ -674,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/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index f2f225d..f88768b 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -29,6 +30,7 @@
import com.android.internal.widget.ILockSettings;
import java.util.List;
+import java.util.Map;
/**
* A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
@@ -43,9 +45,23 @@
public static final int NO_ERROR = KeyStore.NO_ERROR;
public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
- // Too many updates to recovery public key or server parameters.
+ /**
+ * Rate limit is enforced to prevent using too many trusted remote devices, since each device
+ * can have its own number of user secret guesses allowed.
+ *
+ * @hide
+ */
public static final int RATE_LIMIT_EXCEEDED = 21;
+ /** Key has been successfully synced. */
+ public static final int RECOVERY_STATUS_SYNCED = 0;
+ /** Waiting for recovery agent to sync the key. */
+ public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
+ /** Recovery account is not available. */
+ public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
+ /** Key cannot be synced. */
+ public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
+
private final ILockSettings mBinder;
private RecoverableKeyStoreLoader(ILockSettings binder) {
@@ -155,7 +171,7 @@
* @return Data necessary to recover keystore.
* @hide
*/
- public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
+ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
throws RecoverableKeyStoreLoaderException {
try {
KeyStoreRecoveryData recoveryData =
@@ -169,6 +185,50 @@
}
/**
+ * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
+ * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
+ * most one registered listener at any time.
+ *
+ * @param intent triggered when new snapshot is available. Unregisters listener if the value is
+ * {@code null}.
+ * @hide
+ */
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
+ * version. Version zero is used, if no snapshots were created for the account.
+ *
+ * @return Map from recovery agent accounts to snapshot versions.
+ * @see KeyStoreRecoveryData.getSnapshotVersion
+ * @hide
+ */
+ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<byte[], Integer> result =
+ (Map<byte[], Integer>)
+ mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId());
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
* Server parameters used to generate new recovery key blobs. This value will be included in
* {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
* in vaultParams {@link startRecoverySession}
@@ -191,8 +251,8 @@
/**
* Updates recovery status for given keys. It is used to notify keystore that key was
- * successfully stored on the server or there were an error. Returned as a part of KeyInfo data
- * structure.
+ * successfully stored on the server or there were an error. Application can check this value
+ * using {@code getRecoveyStatus}.
*
* @param packageName Application whose recoverable keys' statuses are to be updated.
* @param aliases List of application-specific key aliases. If the array is empty, updates the
@@ -212,6 +272,39 @@
}
/**
+ * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+ * Negative status values are reserved for recovery agent specific codes. List of common codes:
+ *
+ * <ul>
+ * <li>{@link #RECOVERY_STATUS_SYNCED}
+ * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
+ * <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
+ * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
+ * </ul>
+ *
+ * @param packageName Application whose recoverable keys' statuses are to be retrieved. if
+ * {@code null} caller's package will be used.
+ * @return {@code Map} from KeyStore alias to recovery status.
+ * @see #setRecoveryStatus
+ * @hide
+ */
+ public Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> result =
+ (Map<String, Integer>)
+ mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId());
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
* Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
* is necessary to recover data.
*
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/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..2697454 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;
}
@@ -1009,6 +1009,21 @@
}
/**
+ * Returns the component name of the {@link AutofillService} that is enabled for the current
+ * user.
+ */
+ @Nullable
+ public ComponentName getAutofillServiceComponentName() {
+ if (mService == null) return null;
+
+ try {
+ return mService.getAutofillServiceComponentName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the user data used for
* <a href="AutofillService.html#FieldClassification">field classification</a>.
*
@@ -1139,10 +1154,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 +1759,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/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index f49aa5b..38bb311 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -57,4 +57,5 @@
UserData getUserData();
void setUserData(in UserData userData);
boolean isFieldClassificationEnabled();
+ ComponentName getAutofillServiceComponentName();
}
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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6510a70..c1e5af8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -51,6 +51,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Log;
import android.util.LogWriter;
import android.util.LongSparseArray;
@@ -120,7 +121,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 171 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 172 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -188,6 +189,8 @@
@VisibleForTesting
protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
new KernelUidCpuFreqTimeReader();
+ @VisibleForTesting
+ protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
= new KernelMemoryBandwidthStats();
@@ -196,6 +199,18 @@
return mKernelMemoryStats;
}
+ @GuardedBy("this")
+ public boolean mPerProcStateCpuTimesAvailable = true;
+
+ /**
+ * Uids for which per-procstate cpu times need to be updated.
+ *
+ * Contains uid -> procState mappings.
+ */
+ @GuardedBy("this")
+ @VisibleForTesting
+ protected final SparseIntArray mPendingUids = new SparseIntArray();
+
/** Container for Resource Power Manager stats. Updated by updateRpmStatsLocked. */
private final RpmStats mTmpRpmStats = new RpmStats();
/** The soonest the RPM stats can be updated after it was last updated. */
@@ -268,6 +283,138 @@
}
}
+ /**
+ * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+ */
+ public void updateProcStateCpuTimes() {
+ final SparseIntArray uidStates;
+ synchronized (BatteryStatsImpl.this) {
+ if(!initKernelSingleUidTimeReaderLocked()) {
+ return;
+ }
+
+ if (mPendingUids.size() == 0) {
+ return;
+ }
+ uidStates = mPendingUids.clone();
+ mPendingUids.clear();
+ }
+ for (int i = uidStates.size() - 1; i >= 0; --i) {
+ final int uid = uidStates.keyAt(i);
+ final int procState = uidStates.valueAt(i);
+ final int[] isolatedUids;
+ final Uid u;
+ final boolean onBattery;
+ synchronized (BatteryStatsImpl.this) {
+ // It's possible that uid no longer exists and any internal references have
+ // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
+ // creating an UidStats object if it doesn't already exist.
+ u = getAvailableUidStatsLocked(uid);
+ if (u == null) {
+ continue;
+ }
+ if (u.mChildUids == null) {
+ isolatedUids = null;
+ } else {
+ isolatedUids = u.mChildUids.toArray();
+ for (int j = isolatedUids.length - 1; j >= 0; --j) {
+ isolatedUids[j] = u.mChildUids.get(j);
+ }
+ }
+ onBattery = mOnBatteryInternal;
+ }
+ long[] cpuTimesMs = mKernelSingleUidTimeReader.readDeltaMs(uid);
+ if (isolatedUids != null) {
+ for (int j = isolatedUids.length - 1; j >= 0; --j) {
+ cpuTimesMs = addCpuTimes(cpuTimesMs,
+ mKernelSingleUidTimeReader.readDeltaMs(isolatedUids[j]));
+ }
+ }
+ if (onBattery && cpuTimesMs != null) {
+ synchronized (BatteryStatsImpl.this) {
+ u.addProcStateTimesMs(procState, cpuTimesMs);
+ u.addProcStateScreenOffTimesMs(procState, cpuTimesMs);
+ }
+ }
+ }
+ }
+
+ /**
+ * When the battery/screen state changes, we don't attribute the cpu times to any process
+ * but we still need to snapshots of all uids to get correct deltas later on. Since we
+ * already read this data for updating per-freq cpu times, we can use the same data for
+ * per-procstate cpu times.
+ */
+ public void copyFromAllUidsCpuTimes() {
+ synchronized (BatteryStatsImpl.this) {
+ if(!initKernelSingleUidTimeReaderLocked()) {
+ return;
+ }
+
+ final SparseArray<long[]> allUidCpuFreqTimesMs =
+ mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+ for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
+ final int uid = allUidCpuFreqTimesMs.keyAt(i);
+ final Uid u = getAvailableUidStatsLocked(mapUid(uid));
+ if (u == null) {
+ continue;
+ }
+ final long[] cpuTimesMs = allUidCpuFreqTimesMs.valueAt(i);
+ if (cpuTimesMs == null) {
+ continue;
+ }
+ final long[] deltaTimesMs = mKernelSingleUidTimeReader.computeDelta(
+ uid, cpuTimesMs.clone());
+ if (mOnBatteryInternal && deltaTimesMs != null) {
+ final int procState;
+ final int idx = mPendingUids.indexOfKey(uid);
+ if (idx >= 0) {
+ procState = mPendingUids.valueAt(idx);
+ mPendingUids.removeAt(idx);
+ } else {
+ procState = u.mProcessState;
+ }
+ if (procState >= 0 && procState < Uid.NUM_PROCESS_STATE) {
+ u.addProcStateTimesMs(procState, deltaTimesMs);
+ u.addProcStateScreenOffTimesMs(procState, deltaTimesMs);
+ }
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public long[] addCpuTimes(long[] timesA, long[] timesB) {
+ if (timesA != null && timesB != null) {
+ for (int i = timesA.length - 1; i >= 0; --i) {
+ timesA[i] += timesB[i];
+ }
+ return timesA;
+ }
+ return timesA == null ? (timesB == null ? null : timesB) : timesA;
+ }
+
+ @GuardedBy("this")
+ private boolean initKernelSingleUidTimeReaderLocked() {
+ if (mKernelSingleUidTimeReader == null) {
+ if (mPowerProfile == null) {
+ return false;
+ }
+ if (mCpuFreqs == null) {
+ mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+ }
+ if (mCpuFreqs != null) {
+ mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
+ } else {
+ mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable();
+ return false;
+ }
+ }
+ mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable()
+ && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
+ return true;
+ }
+
public interface Clocks {
public long elapsedRealtime();
public long uptimeMillis();
@@ -293,9 +440,11 @@
Future<?> scheduleSync(String reason, int flags);
Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
+ Future<?> scheduleReadProcStateCpuTimes();
+ Future<?> scheduleCopyFromAllUidsCpuTimes();
}
- public final MyHandler mHandler;
+ public Handler mHandler;
private ExternalStatsSync mExternalSync = null;
@VisibleForTesting
protected UserInfoProvider mUserInfoProvider = null;
@@ -3623,6 +3772,7 @@
+ " and battery is " + (unplugged ? "on" : "off"));
}
updateCpuTimeLocked();
+ mExternalSync.scheduleCopyFromAllUidsCpuTimes();
mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
if (updateOnBatteryTimeBase) {
@@ -3652,6 +3802,8 @@
public void addIsolatedUidLocked(int isolatedUid, int appUid) {
mIsolatedUids.put(isolatedUid, appUid);
StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
+ final Uid u = getUidStatsLocked(appUid);
+ u.addIsolatedUid(isolatedUid);
}
/**
@@ -3672,11 +3824,17 @@
* @see #scheduleRemoveIsolatedUidLocked(int, int)
*/
public void removeIsolatedUidLocked(int isolatedUid) {
- StatsLog.write(
- StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
- mIsolatedUids.delete(isolatedUid);
- mKernelUidCpuTimeReader.removeUid(isolatedUid);
- mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+ StatsLog.write(
+ StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+ final int idx = mIsolatedUids.indexOfKey(isolatedUid);
+ if (idx >= 0) {
+ final int ownerUid = mIsolatedUids.valueAt(idx);
+ final Uid u = getUidStatsLocked(ownerUid);
+ u.removeIsolatedUid(isolatedUid);
+ mIsolatedUids.removeAt(idx);
+ }
+ mKernelUidCpuTimeReader.removeUid(isolatedUid);
+ mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
}
public int mapUid(int uid) {
@@ -5899,6 +6057,11 @@
LongSamplingCounterArray mCpuFreqTimeMs;
LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
+ LongSamplingCounterArray[] mProcStateTimeMs;
+ LongSamplingCounterArray[] mProcStateScreenOffTimeMs;
+
+ IntArray mChildUids;
+
/**
* The statistics we have collected for this uid's wake locks.
*/
@@ -5984,40 +6147,107 @@
mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
}
+ @VisibleForTesting
+ public void setProcessStateForTest(int procState) {
+ mProcessState = procState;
+ }
+
@Override
public long[] getCpuFreqTimes(int which) {
- if (mCpuFreqTimeMs == null) {
+ return nullIfAllZeros(mCpuFreqTimeMs, which);
+ }
+
+ @Override
+ public long[] getScreenOffCpuFreqTimes(int which) {
+ return nullIfAllZeros(mScreenOffCpuFreqTimeMs, which);
+ }
+
+ @Override
+ public long[] getCpuFreqTimes(int which, int procState) {
+ if (which < 0 || which >= NUM_PROCESS_STATE) {
return null;
}
- final long[] cpuFreqTimes = mCpuFreqTimeMs.getCountsLocked(which);
- if (cpuFreqTimes == null) {
+ if (mProcStateTimeMs == null) {
return null;
}
- // Return cpuFreqTimes only if atleast one of the elements in non-zero.
- for (int i = 0; i < cpuFreqTimes.length; ++i) {
- if (cpuFreqTimes[i] != 0) {
- return cpuFreqTimes;
+ if (!mBsi.mPerProcStateCpuTimesAvailable) {
+ mProcStateTimeMs = null;
+ return null;
+ }
+ return nullIfAllZeros(mProcStateTimeMs[procState], which);
+ }
+
+ @Override
+ public long[] getScreenOffCpuFreqTimes(int which, int procState) {
+ if (which < 0 || which >= NUM_PROCESS_STATE) {
+ return null;
+ }
+ if (mProcStateScreenOffTimeMs == null) {
+ return null;
+ }
+ if (!mBsi.mPerProcStateCpuTimesAvailable) {
+ mProcStateScreenOffTimeMs = null;
+ return null;
+ }
+ return nullIfAllZeros(mProcStateScreenOffTimeMs[procState], which);
+ }
+
+ public void addIsolatedUid(int isolatedUid) {
+ if (mChildUids == null) {
+ mChildUids = new IntArray();
+ } else if (mChildUids.indexOf(isolatedUid) >= 0) {
+ return;
+ }
+ mChildUids.add(isolatedUid);
+ }
+
+ public void removeIsolatedUid(int isolatedUid) {
+ final int idx = mChildUids == null ? -1 : mChildUids.indexOf(isolatedUid);
+ if (idx < 0) {
+ return;
+ }
+ mChildUids.remove(idx);
+ }
+
+ private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
+ if (cpuTimesMs == null) {
+ return null;
+ }
+ final long[] counts = cpuTimesMs.getCountsLocked(which);
+ if (counts == null) {
+ return null;
+ }
+ // Return counts only if at least one of the elements is non-zero.
+ for (int i = counts.length - 1; i >= 0; --i) {
+ if (counts[i] != 0) {
+ return counts;
}
}
return null;
}
- @Override
- public long[] getScreenOffCpuFreqTimes(int which) {
- if (mScreenOffCpuFreqTimeMs == null) {
- return null;
+ private void addProcStateTimesMs(int procState, long[] cpuTimesMs) {
+ if (mProcStateTimeMs == null) {
+ mProcStateTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
}
- final long[] cpuFreqTimes = mScreenOffCpuFreqTimeMs.getCountsLocked(which);
- if (cpuFreqTimes == null) {
- return null;
+ if (mProcStateTimeMs[procState] == null
+ || mProcStateTimeMs[procState].getSize() != cpuTimesMs.length) {
+ mProcStateTimeMs[procState] = new LongSamplingCounterArray(
+ mBsi.mOnBatteryTimeBase);
}
- // Return cpuFreqTimes only if atleast one of the elements in non-zero.
- for (int i = 0; i < cpuFreqTimes.length; ++i) {
- if (cpuFreqTimes[i] != 0) {
- return cpuFreqTimes;
- }
+ mProcStateTimeMs[procState].addCountLocked(cpuTimesMs);
+ }
+
+ private void addProcStateScreenOffTimesMs(int procState, long[] cpuTimesMs) {
+ if (mProcStateScreenOffTimeMs == null) {
+ mProcStateScreenOffTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
}
- return null;
+ if (mProcStateScreenOffTimeMs[procState] == null
+ || mProcStateScreenOffTimeMs[procState].getSize() != cpuTimesMs.length) {
+ mProcStateScreenOffTimeMs[procState] = new LongSamplingCounterArray(
+ mBsi.mOnBatteryScreenOffTimeBase);
+ }
+ mProcStateScreenOffTimeMs[procState].addCountLocked(cpuTimesMs);
}
@Override
@@ -6999,6 +7229,21 @@
mScreenOffCpuFreqTimeMs.reset(false);
}
+ if (mProcStateTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ if (counters != null) {
+ counters.reset(false);
+ }
+ }
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ if (counters != null) {
+ counters.reset(false);
+ }
+ }
+ }
+
resetLongCounterIfNotNull(mMobileRadioApWakeupCount, false);
resetLongCounterIfNotNull(mWifiRadioApWakeupCount, false);
@@ -7189,6 +7434,20 @@
mScreenOffCpuFreqTimeMs.detach();
}
+ if (mProcStateTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ if (counters != null) {
+ counters.detach();
+ }
+ }
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ if (counters != null) {
+ counters.detach();
+ }
+ }
+ }
detachLongCounterIfNotNull(mMobileRadioApWakeupCount);
detachLongCounterIfNotNull(mWifiRadioApWakeupCount);
}
@@ -7449,6 +7708,22 @@
LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
+ if (mProcStateTimeMs != null) {
+ out.writeInt(mProcStateTimeMs.length);
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ LongSamplingCounterArray.writeToParcel(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ out.writeInt(mProcStateScreenOffTimeMs.length);
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ LongSamplingCounterArray.writeToParcel(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
if (mMobileRadioApWakeupCount != null) {
out.writeInt(1);
@@ -7750,6 +8025,27 @@
mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
in, mBsi.mOnBatteryScreenOffTimeBase);
+ int length = in.readInt();
+ if (length == NUM_PROCESS_STATE) {
+ mProcStateTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ mProcStateTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+ in, mBsi.mOnBatteryTimeBase);
+ }
+ } else {
+ mProcStateTimeMs = null;
+ }
+ length = in.readInt();
+ if (length == NUM_PROCESS_STATE) {
+ mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ mProcStateScreenOffTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+ in, mBsi.mOnBatteryScreenOffTimeBase);
+ }
+ } else {
+ mProcStateScreenOffTimeMs = null;
+ }
+
if (in.readInt() != 0) {
mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
} else {
@@ -8671,25 +8967,7 @@
// Make special note of Foreground Services
final boolean userAwareService =
(procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
- uidRunningState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
- uidRunningState = PROCESS_STATE_TOP;
- } 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_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;
- }
+ uidRunningState = BatteryStats.mapToInternalProcessState(procState);
if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
return;
@@ -8701,6 +8979,18 @@
if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
+
+ if (mBsi.mPerProcStateCpuTimesAvailable) {
+ if (mBsi.mPendingUids.size() == 0) {
+ mBsi.mExternalSync.scheduleReadProcStateCpuTimes();
+ }
+ if (mBsi.mPendingUids.indexOfKey(mUid) < 0
+ || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
+ mBsi.mPendingUids.put(mUid, mProcessState);
+ }
+ } else {
+ mBsi.mPendingUids.clear();
+ }
}
mProcessState = uidRunningState;
if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
@@ -11769,11 +12059,23 @@
return u;
}
+ /**
+ * Retrieve the statistics object for a particular uid. Returns null if the object is not
+ * available.
+ */
+ public Uid getAvailableUidStatsLocked(int uid) {
+ Uid u = mUidStats.get(uid);
+ return u;
+ }
+
public void onCleanupUserLocked(int userId) {
final int firstUidForUser = UserHandle.getUid(userId, 0);
final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
mKernelUidCpuFreqTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
mKernelUidCpuTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+ if (mKernelSingleUidTimeReader != null) {
+ mKernelSingleUidTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+ }
}
public void onUserRemovedLocked(int userId) {
@@ -11792,6 +12094,9 @@
public void removeUidStatsLocked(int uid) {
mKernelUidCpuTimeReader.removeUid(uid);
mKernelUidCpuFreqTimeReader.removeUid(uid);
+ if (mKernelSingleUidTimeReader != null) {
+ mKernelSingleUidTimeReader.removeUid(uid);
+ }
mUidStats.remove(uid);
}
@@ -12393,6 +12698,28 @@
in, mOnBatteryTimeBase);
u.mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
in, mOnBatteryScreenOffTimeBase);
+ int length = in.readInt();
+ if (length == Uid.NUM_PROCESS_STATE) {
+ u.mProcStateTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ u.mProcStateTimeMs[procState]
+ = LongSamplingCounterArray.readSummaryFromParcelLocked(
+ in, mOnBatteryTimeBase);
+ }
+ } else {
+ u.mProcStateTimeMs = null;
+ }
+ length = in.readInt();
+ if (length == Uid.NUM_PROCESS_STATE) {
+ u.mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ u.mProcStateScreenOffTimeMs[procState]
+ = LongSamplingCounterArray.readSummaryFromParcelLocked(
+ in, mOnBatteryScreenOffTimeBase);
+ }
+ } else {
+ u.mProcStateScreenOffTimeMs = null;
+ }
if (in.readInt() != 0) {
u.mMobileRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase);
@@ -12846,6 +13173,23 @@
LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mCpuFreqTimeMs);
LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mScreenOffCpuFreqTimeMs);
+ if (u.mProcStateTimeMs != null) {
+ out.writeInt(u.mProcStateTimeMs.length);
+ for (LongSamplingCounterArray counters : u.mProcStateTimeMs) {
+ LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mProcStateScreenOffTimeMs != null) {
+ out.writeInt(u.mProcStateScreenOffTimeMs.length);
+ for (LongSamplingCounterArray counters : u.mProcStateScreenOffTimeMs) {
+ LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+
if (u.mMobileRadioApWakeupCount != null) {
out.writeInt(1);
u.mMobileRadioApWakeupCount.writeSummaryFromParcelLocked(out);
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
new file mode 100644
index 0000000..ca635a4
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -0,0 +1,204 @@
+/*
+ * 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.internal.os;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+@VisibleForTesting(visibility = PACKAGE)
+public class KernelSingleUidTimeReader {
+ private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
+ private final boolean DBG = false;
+
+ private final String PROC_FILE_DIR = "/proc/uid/";
+ private final String PROC_FILE_NAME = "/time_in_state";
+
+ @VisibleForTesting
+ public static final int TOTAL_READ_ERROR_COUNT = 5;
+
+ @GuardedBy("this")
+ private final int mCpuFreqsCount;
+
+ @GuardedBy("this")
+ private final SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
+
+ @GuardedBy("this")
+ private int mReadErrorCounter;
+ @GuardedBy("this")
+ private boolean mSingleUidCpuTimesAvailable = true;
+
+ private final Injector mInjector;
+
+ KernelSingleUidTimeReader(int cpuFreqsCount) {
+ this(cpuFreqsCount, new Injector());
+ }
+
+ public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
+ mInjector = injector;
+ mCpuFreqsCount = cpuFreqsCount;
+ if (mCpuFreqsCount == 0) {
+ mSingleUidCpuTimesAvailable = false;
+ }
+ }
+
+ public boolean singleUidCpuTimesAvailable() {
+ return mSingleUidCpuTimesAvailable;
+ }
+
+ public long[] readDeltaMs(int uid) {
+ synchronized (this) {
+ if (!mSingleUidCpuTimesAvailable) {
+ return null;
+ }
+ // Read total cpu times from the proc file.
+ final String procFile = new StringBuilder(PROC_FILE_DIR)
+ .append(uid)
+ .append(PROC_FILE_NAME).toString();
+ final long[] cpuTimesMs = new long[mCpuFreqsCount];
+ try {
+ final byte[] data = mInjector.readData(procFile);
+ final ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(ByteOrder.nativeOrder());
+ for (int i = 0; i < mCpuFreqsCount; ++i) {
+ // Times read will be in units of 10ms
+ cpuTimesMs[i] = buffer.getLong() * 10;
+ }
+ } catch (Exception e) {
+ if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ mSingleUidCpuTimesAvailable = false;
+ }
+ if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
+ return null;
+ }
+
+ return computeDelta(uid, cpuTimesMs);
+ }
+ }
+
+ /**
+ * Compute and return cpu times delta of an uid using previously read cpu times and
+ * {@param latestCpuTimesMs}.
+ *
+ * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
+ */
+ public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
+ synchronized (this) {
+ if (!mSingleUidCpuTimesAvailable) {
+ return null;
+ }
+ // Subtract the last read cpu times to get deltas.
+ final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
+ final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
+ if (deltaTimesMs == null) {
+ if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
+ + "; last=" + Arrays.toString(lastCpuTimesMs)
+ + "; latest=" + Arrays.toString(latestCpuTimesMs));
+ return null;
+ }
+ // If all elements are zero, return null to avoid unnecessary work on the caller side.
+ boolean hasNonZero = false;
+ for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
+ if (deltaTimesMs[i] > 0) {
+ hasNonZero = true;
+ break;
+ }
+ }
+ if (hasNonZero) {
+ mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
+ return deltaTimesMs;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns null if the latest cpu times are not valid**, otherwise delta of
+ * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
+ *
+ * **latest cpu times are considered valid if all the cpu times are +ve and
+ * greater than or equal to previously read cpu times.
+ */
+ @GuardedBy("this")
+ @VisibleForTesting(visibility = PACKAGE)
+ public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
+ for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+ if (latestCpuTimesMs[i] < 0) {
+ return null;
+ }
+ }
+ if (lastCpuTimesMs == null) {
+ return latestCpuTimesMs;
+ }
+ final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
+ for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+ deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
+ if (deltaTimesMs[i] < 0) {
+ return null;
+ }
+ }
+ return deltaTimesMs;
+ }
+
+ public void removeUid(int uid) {
+ synchronized (this) {
+ mLastUidCpuTimeMs.delete(uid);
+ }
+ }
+
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ return;
+ }
+ synchronized (this) {
+ mLastUidCpuTimeMs.put(startUid, null);
+ mLastUidCpuTimeMs.put(endUid, null);
+ final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
+ final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
+ mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
+ }
+ }
+
+ @VisibleForTesting
+ public static class Injector {
+ public byte[] readData(String procFile) throws IOException {
+ return Files.readAllBytes(Paths.get(procFile));
+ }
+ }
+
+ @VisibleForTesting
+ public SparseArray<long[]> getLastUidCpuTimeMs() {
+ return mLastUidCpuTimeMs;
+ }
+
+ @VisibleForTesting
+ public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
+ mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index a39997d..b8982cc 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -66,13 +66,21 @@
// start reading) and if it is not available, we simply ignore further read requests.
private static final int TOTAL_READ_ERROR_COUNT = 5;
private int mReadErrorCounter;
- private boolean mProcFileAvailable;
private boolean mPerClusterTimesAvailable;
+ private boolean mAllUidTimesAvailable = true;
public boolean perClusterTimesAvailable() {
return mPerClusterTimesAvailable;
}
+ public boolean allUidTimesAvailable() {
+ return mAllUidTimesAvailable;
+ }
+
+ public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
+ return mLastUidCpuFreqTimeMs;
+ }
+
public long[] readFreqs(@NonNull PowerProfile powerProfile) {
checkNotNull(powerProfile);
@@ -80,15 +88,16 @@
// No need to read cpu freqs more than once.
return mCpuFreqs;
}
- if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ if (!mAllUidTimesAvailable) {
return null;
}
final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mProcFileAvailable = true;
return readFreqs(reader, powerProfile);
} catch (IOException e) {
- mReadErrorCounter++;
+ if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ mAllUidTimesAvailable = false;
+ }
Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
return null;
} finally {
@@ -107,7 +116,7 @@
}
public void readDelta(@Nullable Callback callback) {
- if (!mProcFileAvailable) {
+ if (mCpuFreqs == null) {
return;
}
final int oldMask = StrictMode.allowThreadDiskReadsMask();
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/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 164a745..43536a5 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import android.app.PendingIntent;
import android.app.trust.IStrongAuthTracker;
import android.os.Bundle;
import android.security.recoverablekeystore.KeyEntryRecoveryData;
@@ -24,6 +25,8 @@
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.VerifyCredentialResponse;
+import java.util.Map;
+
/** {@hide} */
interface ILockSettings {
void setBoolean(in String key, in boolean value, in int userId);
@@ -63,8 +66,11 @@
void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList,
int userId);
KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId);
+ void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId);
+ Map getRecoverySnapshotVersions(int userId);
void setServerParameters(long serverParameters, int userId);
void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId);
+ Map getRecoveryStatus(in String packageName, int userId);
void setRecoverySecretTypes(in int[] secretTypes, int userId);
int[] getRecoverySecretTypes(int userId);
int[] getPendingRecoverySecretTypes(int userId);
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 79c5a34..79aa5ac 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -385,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) {
- peeker.scale(scale, scale, scaledWidth, scaledHeight);
+ peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight);
}
size_t ninePatchArraySize = peeker.mPatch->serializedSize();
@@ -419,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) {
@@ -456,7 +456,7 @@
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);
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 066d47b..9171fc6 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -76,13 +76,18 @@
if (!mPatch) {
return;
}
- mPatch->paddingLeft = int(mPatch->paddingLeft * scaleX + 0.5f);
- mPatch->paddingTop = int(mPatch->paddingTop * scaleY + 0.5f);
- mPatch->paddingRight = int(mPatch->paddingRight * scaleX + 0.5f);
- mPatch->paddingBottom = int(mPatch->paddingBottom * scaleY + 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(mPatch->getXDivs(), mPatch->numXDivs, scaleX, scaledWidth - 1);
- scaleDivRange(mPatch->getYDivs(), mPatch->numYDivs, scaleY, scaledHeight - 1);
+ 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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 13fedfe..e303927 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3632,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/raw/color_fade_vert.vert b/core/res/res/raw/color_fade_vert.vert
index d17437f..b501b06 100644
--- a/core/res/res/raw/color_fade_vert.vert
+++ b/core/res/res/raw/color_fade_vert.vert
@@ -1,6 +1,5 @@
uniform mat4 proj_matrix;
uniform mat4 tex_matrix;
-uniform float scale;
attribute vec2 position;
attribute vec2 uv;
varying vec2 UV;
@@ -9,5 +8,5 @@
{
vec4 transformed_uv = tex_matrix * vec4(uv.x, uv.y, 1.0, 1.0);
UV = transformed_uv.st / transformed_uv.q;
- gl_Position = vec4(scale, scale, 1.0, 1.0) * (proj_matrix * vec4(position.x, position.y, 0.0, 1.0));
+ gl_Position = proj_matrix * vec4(position.x, position.y, 0.0, 1.0);
}
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/BstatsTestApp/AndroidManifest.xml b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
index 0cb5498..1e6bdc6 100644
--- a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
+++ b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
@@ -17,9 +17,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.coretests.apps.bstatstestapp">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25"/>
+
<application>
<activity android:name=".TestActivity"
android:exported="true" />
+ <service android:name=".TestService"
+ android:exported="true" />
<service android:name=".IsolatedTestService"
android:exported="true"
android:isolatedProcess="true" />
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
new file mode 100644
index 0000000..2601f35
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * 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.coretests.apps.bstatstestapp;
+
+import android.os.RemoteException;
+
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+public class BaseCmdReceiver extends ICmdReceiver.Stub {
+ @Override
+ public void doSomeWork(int durationMs) {}
+ @Override
+ public void showApplicationOverlay() throws RemoteException {}
+ @Override
+ public void finishHost() {}
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
new file mode 100644
index 0000000..d192fbd
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
@@ -0,0 +1,60 @@
+/*
+ * 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.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class Common {
+ private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+ public static void doSomeWork(int durationMs) {
+ final long endTime = SystemClock.currentThreadTimeMillis() + durationMs;
+ double x;
+ double y;
+ double z;
+ while (SystemClock.currentThreadTimeMillis() <= endTime) {
+ x = 0.02;
+ x *= 1000;
+ y = x % 5;
+ z = Math.sqrt(y / 100);
+ }
+ }
+
+ public static void notifyLaunched(Intent intent, IBinder binder, String tag) {
+ if (intent == null) {
+ return;
+ }
+
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+ final ICmdCallback callback = ICmdCallback.Stub.asInterface(
+ extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
+ try {
+ callback.onLaunched(binder);
+ } catch (RemoteException e) {
+ Log.e(tag, "Error occured while notifying the test: " + e);
+ }
+ }
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
index 1f5f397..892f60e 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
@@ -15,17 +15,14 @@
*/
package com.android.coretests.apps.bstatstestapp;
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
-import android.os.SystemClock;
import android.util.Log;
public class IsolatedTestService extends Service {
- private static final String TAG = IsolatedTestService.class.getName();
+ private static final String TAG = IsolatedTestService.class.getSimpleName();
@Override
public void onCreate() {
@@ -34,23 +31,20 @@
@Override
public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind called. myUid=" + Process.myUid());
return mReceiver.asBinder();
}
- private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+ }
+
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
@Override
public void doSomeWork(int durationMs) {
- final long endTime = SystemClock.uptimeMillis() + durationMs;
- double x;
- double y;
- double z;
- while (SystemClock.uptimeMillis() <= endTime) {
- x = 0.02;
- x *= 1000;
- y = x % 5;
- z = Math.sqrt(y / 100);
- }
- };
+ Common.doSomeWork(durationMs);
+ }
@Override
public void finishHost() {
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
index 87b14d9..5c551d5 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
@@ -15,19 +15,12 @@
*/
package com.android.coretests.apps.bstatstestapp;
-import com.android.frameworks.coretests.aidl.ICmdCallback;
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
import android.app.Activity;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
public class TestActivity extends Activity {
- private static final String TAG = TestActivity.class.getName();
-
- private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+ private static final String TAG = TestActivity.class.getSimpleName();
@Override
public void onCreate(Bundle icicle) {
@@ -37,21 +30,7 @@
}
private void notifyActivityLaunched() {
- if (getIntent() == null) {
- return;
- }
-
- final Bundle extras = getIntent().getExtras();
- if (extras == null) {
- return;
- }
- final ICmdCallback callback = ICmdCallback.Stub.asInterface(
- extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
- try {
- callback.onActivityLaunched(mReceiver.asBinder());
- } catch (RemoteException e) {
- Log.e(TAG, "Error occured while notifying the test: " + e);
- }
+ Common.notifyLaunched(getIntent(), mReceiver.asBinder(), TAG);
}
@Override
@@ -60,24 +39,17 @@
Log.d(TAG, "finish called");
}
- private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
@Override
public void doSomeWork(int durationMs) {
- final long endTime = SystemClock.uptimeMillis() + durationMs;
- double x;
- double y;
- double z;
- while (SystemClock.uptimeMillis() <= endTime) {
- x = 0.02;
- x *= 1000;
- y = x % 5;
- z = Math.sqrt(y / 100);
- }
- };
+ Common.doSomeWork(durationMs);
+ }
@Override
public void finishHost() {
- finish();
+ if (!isFinishing()) {
+ finish();
+ }
}
};
}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
new file mode 100644
index 0000000..8a22aca
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
@@ -0,0 +1,154 @@
+/*
+ * 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.coretests.apps.bstatstestapp;
+
+import android.R;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestService extends Service {
+ private static final String TAG = TestService.class.getSimpleName();
+
+ private static final int FLAG_START_FOREGROUND = 1;
+
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
+ private static final int NOTIFICATION_ID = 42;
+
+ private static final int TIMEOUT_OVERLAY_SEC = 2;
+
+ private View mOverlay;
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "onCreate called. myUid=" + Process.myUid());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "onStartCommand called. myUid=" + Process.myUid());
+ if (intent != null && (intent.getFlags() & FLAG_START_FOREGROUND) != 0) {
+ startForeground();
+ }
+ notifyServiceLaunched(intent);
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind called. myUid=" + Process.myUid());
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+ removeOverlays();
+ }
+
+ private void notifyServiceLaunched(Intent intent) {
+ Common.notifyLaunched(intent, mReceiver.asBinder(), TAG);
+ }
+
+ private void startForeground() {
+ final NotificationManager noMan = getSystemService(NotificationManager.class);
+ noMan.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ Log.d(TAG, "Starting foreground. myUid=" + Process.myUid());
+ startForeground(NOTIFICATION_ID,
+ new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_dialog_alert)
+ .build());
+ }
+
+ private void removeOverlays() {
+ if (mOverlay != null) {
+ final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+ wm.removeView(mOverlay);
+ mOverlay = null;
+ }
+ }
+
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
+ @Override
+ public void doSomeWork(int durationMs) {
+ Common.doSomeWork(durationMs);
+ }
+
+ @Override
+ public void showApplicationOverlay() throws RemoteException {
+ final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+ final Point size = new Point();
+ wm.getDefaultDisplay().getSize(size);
+
+ final WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ wmlp.width = size.x / 2;
+ wmlp.height = size.y / 2;
+ wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
+ wmlp.setTitle(TAG);
+
+ final ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ mOverlay = new View(TestService.this);
+ mOverlay.setBackgroundColor(Color.GREEN);
+ mOverlay.setLayoutParams(vglp);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Handler handler = new Handler(TestService.this.getMainLooper());
+ handler.post(() -> {
+ wm.addView(mOverlay, wmlp);
+ latch.countDown();
+ });
+ try {
+ if (!latch.await(TIMEOUT_OVERLAY_SEC, TimeUnit.SECONDS)) {
+ throw new RemoteException("Timed out waiting for the overlay");
+ }
+ } catch (InterruptedException e) {
+ throw new RemoteException("Error while adding overlay: " + e.toString());
+ }
+ Log.d(TAG, "Overlay displayed, myUid=" + Process.myUid());
+ }
+
+ @Override
+ public void finishHost() {
+ removeOverlays();
+ stopSelf();
+ }
+ };
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
index 53a181a..6d0239b 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
@@ -17,5 +17,5 @@
package com.android.frameworks.coretests.aidl;
interface ICmdCallback {
- void onActivityLaunched(IBinder receiver);
+ void onLaunched(IBinder receiver);
}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
index c406570..cce8e28 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
@@ -18,5 +18,6 @@
interface ICmdReceiver {
void doSomeWork(int durationMs);
+ void showApplicationOverlay();
void finishHost();
}
\ No newline at end of file
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/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/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/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 6ff0ab2..b5a7bec 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -960,7 +960,7 @@
}
@Test
- public void testReadKernelUiidCpuFreqTimesLocked_invalidUid() {
+ public void testReadKernelUidCpuFreqTimesLocked_invalidUid() {
// PRECONDITIONS
updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
new file mode 100644
index 0000000..3794b5f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.internal.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryStats;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsImplTest {
+ @Mock
+ private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+ @Mock
+ private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+
+ private MockBatteryStatsImpl mBatteryStatsImpl;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
+ when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
+ mBatteryStatsImpl = new MockBatteryStatsImpl()
+ .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+ .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
+ }
+
+ @Test
+ public void testUpdateProcStateCpuTimes() {
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+ final int[] testUids = {10032, 10048, 10145, 10139};
+ final int[] testProcStates = {
+ PROCESS_STATE_BACKGROUND,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_TOP,
+ PROCESS_STATE_CACHED
+ };
+ addPendingUids(testUids, testProcStates);
+ final long[][] cpuTimes = {
+ {349734983, 394982394832l, 909834, 348934, 9838},
+ {7498, 1239890, 988, 13298, 98980},
+ {989834, 384098, 98483, 23809, 4984},
+ {4859048, 348903, 4578967, 5973894, 298549}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(cpuTimes[i]);
+
+ // Verify there are no cpu times initially.
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ assertArrayEquals("Uid=" + testUids[i], cpuTimes[i],
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ final long[][] delta1 = {
+ {9589, 148934, 309894, 3098493, 98754},
+ {21983, 94983, 4983, 9878493, 84854},
+ {945894, 9089432, 19478, 3834, 7845},
+ {843895, 43948, 949582, 99, 384}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta1[i]);
+ }
+ addPendingUids(testUids, testProcStates);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j];
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ final long[][] delta2 = {
+ {95932, 2943, 49834, 89034, 139},
+ {349, 89605, 5896, 845, 98444},
+ {678, 7498, 9843, 889, 4894},
+ {488, 998, 8498, 394, 574}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta2[i]);
+ }
+ addPendingUids(testUids, testProcStates);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j] + delta2[i][j];
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertArrayEquals("Uid=" + testUids[i], delta2[i],
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+
+ final long[][] delta3 = {
+ {98545, 95768795, 76586, 548945, 57846},
+ {788876, 586, 578459, 8776984, 9578923},
+ {3049509483598l, 4597834, 377654, 94589035, 7854},
+ {9493, 784, 99895, 8974893, 9879843}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(
+ delta3[i].clone());
+ }
+ addPendingUids(testUids, testProcStates);
+ final int parentUid = testUids[1];
+ final int childUid = 99099;
+ addIsolatedUid(parentUid, childUid);
+ final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
+ when(mKernelSingleUidTimeReader.readDeltaMs(childUid)).thenReturn(isolatedUidCpuTimes);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j] + delta2[i][j] + delta3[i][j]
+ + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ long[] expectedScreenOffTimes = delta2[i].clone();
+ for (int j = 0; j < expectedScreenOffTimes.length; ++j) {
+ expectedScreenOffTimes[j] += delta3[i][j]
+ + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedScreenOffTimes,
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testCopyFromAllUidsCpuTimes() {
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+ final int[] testUids = {10032, 10048, 10145, 10139};
+ final int[] testProcStates = {
+ PROCESS_STATE_BACKGROUND,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_TOP,
+ PROCESS_STATE_CACHED
+ };
+ final int[] pendingUidIdx = {1, 2};
+ updateProcessStates(testUids, testProcStates, pendingUidIdx);
+
+ final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
+ long[][] allCpuTimes = {
+ {938483, 4985984, 439893},
+ {499, 94904, 27694},
+ {302949085, 39789473, 34792839},
+ {9809485, 9083475, 347889834},
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ allUidCpuTimes.put(testUids[i], allCpuTimes[i]);
+ }
+ when(mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs()).thenReturn(allUidCpuTimes);
+ long[][] expectedCpuTimes = {
+ {843598745, 397843, 32749, 99854},
+ {9834, 5885, 487589, 394},
+ {203984, 439, 9859, 30948},
+ {9389, 858, 239, 349}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ final int idx = i;
+ final ArgumentMatcher<long[]> matcher = times -> Arrays.equals(times, allCpuTimes[idx]);
+ when(mKernelSingleUidTimeReader.computeDelta(eq(testUids[i]), argThat(matcher)))
+ .thenReturn(expectedCpuTimes[i]);
+ }
+
+ mBatteryStatsImpl.copyFromAllUidsCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes[i],
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+
+ @Test
+ public void testAddCpuTimes() {
+ long[] timesA = null;
+ long[] timesB = null;
+ assertNull(mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ timesA = new long[] {34, 23, 45, 24};
+ assertArrayEquals(timesA, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ timesB = timesA;
+ timesA = null;
+ assertArrayEquals(timesB, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ final long[] expected = {434, 6784, 34987, 9984};
+ timesA = new long[timesB.length];
+ for (int i = 0; i < timesA.length; ++i) {
+ timesA[i] = expected[i] - timesB[i];
+ }
+ assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+ }
+
+ private void addIsolatedUid(int parentUid, int childUid) {
+ final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
+ u.addIsolatedUid(childUid);
+ }
+
+ private void addPendingUids(int[] uids, int[] procStates) {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ for (int i = 0; i < uids.length; ++i) {
+ pendingUids.put(uids[i], procStates[i]);
+ }
+ }
+
+ private void updateProcessStates(int[] uids, int[] procStates,
+ int[] pendingUidsIdx) {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ for (int i = 0; i < uids.length; ++i) {
+ final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(uids[i]);
+ if (ArrayUtils.contains(pendingUidsIdx, i)) {
+ u.setProcessStateForTest(PROCESS_STATE_TOP);
+ pendingUids.put(uids[i], procStates[i]);
+ } else {
+ u.setProcessStateForTest(procStates[i]);
+ }
+ }
+ }
+
+ private void verifyNoPendingUids() {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ assertEquals("There shouldn't be any pending uids left: " + pendingUids,
+ 0, pendingUids.size());
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 4e83221..0afec34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -170,7 +170,6 @@
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
- + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HEAVY_WEIGHT)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 12f5b70..e8f2456 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -26,6 +26,7 @@
BatteryStatsDualTimerTest.class,
BatteryStatsDurationTimerTest.class,
BatteryStatsHelperTest.class,
+ BatteryStatsImplTest.class,
BatteryStatsNoteTest.class,
BatteryStatsSamplingTimerTest.class,
BatteryStatsSensorTest.class,
@@ -36,6 +37,7 @@
BatteryStatsUidTest.class,
BatteryStatsUserLifecycleTests.class,
KernelMemoryBandwidthStatsTest.class,
+ KernelSingleUidTimeReaderTest.class,
KernelUidCpuFreqTimeReaderTest.class,
KernelWakelockReaderTest.class,
LongSamplingCounterArrayTest.class
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 4b197e4..e54fe7d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -15,19 +15,25 @@
*/
package com.android.internal.os;
-import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.os.BatteryStats.UID_TIMES_TYPE_ALL;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
+import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
-import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
import com.android.frameworks.coretests.aidl.ICmdCallback;
import com.android.frameworks.coretests.aidl.ICmdReceiver;
+import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
@@ -36,6 +42,7 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.BatteryManager;
+import android.os.BatteryStats;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
@@ -45,9 +52,9 @@
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
+import android.util.DebugUtils;
import android.util.Log;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,22 +68,26 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class BstatsCpuTimesValidationTest {
- private static final String TAG = BstatsCpuTimesValidationTest.class.getName();
+ private static final String TAG = BstatsCpuTimesValidationTest.class.getSimpleName();
private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity";
+ private static final String TEST_SERVICE = TEST_PKG + ".TestService";
private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService";
private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+ private static final int FLAG_START_FOREGROUND = 1;
private static final int BATTERY_STATE_TIMEOUT_MS = 2000;
private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200;
private static final int START_ACTIVITY_TIMEOUT_MS = 2000;
+ private static final int START_FG_SERVICE_TIMEOUT_MS = 2000;
+ private static final int START_SERVICE_TIMEOUT_MS = 2000;
private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
- private static final int GENERAL_TIMEOUT_MS = 1000;
- private static final int GENERAL_INTERVAL_MS = 100;
+ private static final int GENERAL_TIMEOUT_MS = 4000;
+ private static final int GENERAL_INTERVAL_MS = 200;
private static final int WORK_DURATION_MS = 2000;
@@ -84,6 +95,7 @@
private static UiDevice sUiDevice;
private static int sTestPkgUid;
private static boolean sCpuFreqTimesAvailable;
+ private static boolean sPerProcStateTimesAvailable;
@BeforeClass
public static void setupOnce() throws Exception {
@@ -92,14 +104,20 @@
sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
- sCpuFreqTimesAvailable = cpuFreqTimesAvailable();
+ checkCpuTimesAvailability();
}
// Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state
- // kernel node is available.
- private static boolean cpuFreqTimesAvailable() throws Exception {
- final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
- return cpuTimes != null;
+ // and /proc/uid/<uid>/time_in_state kernel nodes are available.
+ private static void checkCpuTimesAvailability() throws Exception {
+ batteryOn();
+ SystemClock.sleep(GENERAL_TIMEOUT_MS);
+ batteryOff();
+ final long[] totalCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
+ sCpuFreqTimesAvailable = totalCpuTimes != null;
+ final long[] fgSvcCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE);
+ sPerProcStateTimesAvailable = fgSvcCpuTimes != null;
}
@Test
@@ -112,7 +130,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWork();
forceStop();
@@ -136,7 +155,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWork();
forceStop();
@@ -166,7 +186,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWorkInIsolatedProcess();
forceStop();
@@ -180,6 +201,224 @@
batteryOffScreenOn();
}
+ @Test
+ public void testCpuFreqTimes_stateTop() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+ doSomeWork(PROCESS_STATE_TOP);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testIsolatedCpuFreqTimes_stateService() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+ final ICmdReceiver activityReceiver = ICmdReceiver.Stub.asInterface(startActivity());
+ final ICmdReceiver isolatedReceiver = ICmdReceiver.Stub.asInterface(startIsolatedService());
+ try {
+ assertProcState(PROCESS_STATE_TOP);
+ isolatedReceiver.doSomeWork(WORK_DURATION_MS);
+ } finally {
+ activityReceiver.finishHost();
+ isolatedReceiver.finishHost();
+ }
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateTopSleeping() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING));
+
+ doSomeWork(PROCESS_STATE_TOP_SLEEPING);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateFgService() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE));
+
+ doSomeWork(PROCESS_STATE_FOREGROUND_SERVICE);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateFg() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND));
+
+ doSomeWork(PROCESS_STATE_FOREGROUND);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOff();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateBg() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND));
+
+ doSomeWork(PROCESS_STATE_BACKGROUND);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateCached() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED));
+
+ doSomeWork(PROCESS_STATE_CACHED);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
private void assertCpuTimesValid(long[] cpuTimes) {
assertNotNull(cpuTimes);
for (int i = 0; i < cpuTimes.length; ++i) {
@@ -219,6 +458,66 @@
receiver.finishHost();
}
+ private void doSomeWork(int procState) throws Exception {
+ final ICmdReceiver receiver;
+ switch (procState) {
+ case PROCESS_STATE_TOP:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ break;
+ case PROCESS_STATE_TOP_SLEEPING:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ break;
+ case PROCESS_STATE_FOREGROUND_SERVICE:
+ receiver = ICmdReceiver.Stub.asInterface(startForegroundService());
+ break;
+ case PROCESS_STATE_FOREGROUND:
+ receiver = ICmdReceiver.Stub.asInterface(startService());
+ receiver.showApplicationOverlay();
+ break;
+ case PROCESS_STATE_BACKGROUND:
+ receiver = ICmdReceiver.Stub.asInterface(startService());
+ break;
+ case PROCESS_STATE_CACHED:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ receiver.finishHost();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown state: " + procState);
+ }
+ try {
+ assertProcState(procState);
+ receiver.doSomeWork(WORK_DURATION_MS);
+ } finally {
+ receiver.finishHost();
+ }
+ }
+
+ private void assertProcState(String state) throws Exception {
+ final String expectedState = "(" + state + ")";
+ assertDelayedCondition("", () -> {
+ final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+ final String actualState = uidStateStr.split(" ")[1];
+ return expectedState.equals(actualState) ? null
+ : "expected=" + expectedState + ", actual" + actualState;
+ });
+ }
+
+ private void assertProcState(int expectedState) throws Exception {
+ assertDelayedCondition("Unexpected proc state", () -> {
+ final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+ final int amProcState = Integer.parseInt(uidStateStr.split(" ")[0]);
+ final int actualState = BatteryStats.mapToInternalProcessState(amProcState);
+ return (actualState == expectedState) ? null
+ : "expected=" + getStateName(BatteryStats.Uid.class, expectedState)
+ + ", actual=" + getStateName(BatteryStats.Uid.class, actualState)
+ + ", amState=" + getStateName(ActivityManager.class, amProcState);
+ });
+ }
+
+ private String getStateName(Class clazz, int procState) {
+ return DebugUtils.valueToString(clazz, "PROCESS_STATE_", procState);
+ }
+
private IBinder startIsolatedService() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final IBinder[] binders = new IBinder[1];
@@ -248,6 +547,59 @@
return null;
}
+ private IBinder startForegroundService() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent()
+ .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE))
+ .setFlags(FLAG_START_FOREGROUND);
+ final Bundle extras = new Bundle();
+ final IBinder[] binders = new IBinder[1];
+ extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+ @Override
+ public void onLaunched(IBinder receiver) {
+ binders[0] = receiver;
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ sContext.startForegroundService(launchIntent);
+ if (latch.await(START_FG_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (binders[0] == null) {
+ fail("Receiver binder should not be null");
+ }
+ return binders[0];
+ } else {
+ fail("Timed out waiting for the test fg service to start; testUid=" + sTestPkgUid);
+ }
+ return null;
+ }
+
+ private IBinder startService() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent()
+ .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE));
+ final Bundle extras = new Bundle();
+ final IBinder[] binders = new IBinder[1];
+ extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+ @Override
+ public void onLaunched(IBinder receiver) {
+ binders[0] = receiver;
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ sContext.startService(launchIntent);
+ if (latch.await(START_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (binders[0] == null) {
+ fail("Receiver binder should not be null");
+ }
+ return binders[0];
+ } else {
+ fail("Timed out waiting for the test service to start; testUid=" + sTestPkgUid);
+ }
+ return null;
+ }
+
private IBinder startActivity() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = new Intent()
@@ -256,7 +608,7 @@
final IBinder[] binders = new IBinder[1];
extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
@Override
- public void onActivityLaunched(IBinder receiver) {
+ public void onLaunched(IBinder receiver) {
binders[0] = receiver;
latch.countDown();
}
@@ -274,21 +626,63 @@
return null;
}
+ private static String getAllCpuTimesMsg() throws Exception {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("uid=" + sTestPkgUid + ";");
+ sb.append(UID_TIMES_TYPE_ALL + "=" + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid)));
+ for (int i = 0; i < NUM_PROCESS_STATE; ++i) {
+ sb.append("|");
+ sb.append(UID_PROCESS_TYPES[i] + "="
+ + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid, i)));
+ }
+ return sb.toString();
+ }
+
+ private static String getMsgCpuTimesSum(long[] cpuTimes) throws Exception {
+ if (cpuTimes == null) {
+ return "(0,0)";
+ }
+ long totalTime = 0;
+ for (int i = 0; i < cpuTimes.length / 2; ++i) {
+ totalTime += cpuTimes[i];
+ }
+ long screenOffTime = 0;
+ for (int i = cpuTimes.length / 2; i < cpuTimes.length; ++i) {
+ screenOffTime += cpuTimes[i];
+ }
+ return "(" + totalTime + "," + screenOffTime + ")";
+ }
+
private static long[] getAllCpuFreqTimes(int uid) throws Exception {
final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
- final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
+ final Pattern pattern = Pattern.compile(uid + ",l,ctf," + UID_TIMES_TYPE_ALL + ",(.*?)\n");
final Matcher matcher = pattern.matcher(checkinDump);
if (!matcher.find()) {
return null;
}
- final String[] uidTimesStr = matcher.group(1).split(",");
- final int freqCount = Integer.parseInt(uidTimesStr[0]);
- if (uidTimesStr.length != (2 * freqCount + 1)) {
- fail("Malformed data: " + Arrays.toString(uidTimesStr));
+ return parseCpuTimesStr(matcher.group(1));
+ }
+
+ private static long[] getAllCpuFreqTimes(int uid, int procState) throws Exception {
+ final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
+ final Pattern pattern = Pattern.compile(
+ uid + ",l,ctf," + UID_PROCESS_TYPES[procState] + ",(.*?)\n");
+ final Matcher matcher = pattern.matcher(checkinDump);
+ if (!matcher.find()) {
+ return null;
+ }
+ return parseCpuTimesStr(matcher.group(1));
+ }
+
+ private static long[] parseCpuTimesStr(String str) {
+ final String[] cpuTimesStr = str.split(",");
+ final int freqCount = Integer.parseInt(cpuTimesStr[0]);
+ if (cpuTimesStr.length != (2 * freqCount + 1)) {
+ fail("Malformed data: " + Arrays.toString(cpuTimesStr));
}
final long[] cpuTimes = new long[freqCount * 2];
for (int i = 0; i < cpuTimes.length; ++i) {
- cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
+ cpuTimes[i] = Long.parseLong(cpuTimesStr[i + 1]);
}
return cpuTimes;
}
@@ -312,12 +706,12 @@
screenOn();
}
- private void batteryOn() throws Exception {
+ private static void batteryOn() throws Exception {
executeCmd("dumpsys battery unplug");
assertBatteryState(false);
}
- private void batteryOff() throws Exception {
+ private static void batteryOff() throws Exception {
executeCmd("dumpsys battery reset");
assertBatteryState(true);
}
@@ -336,43 +730,41 @@
private void forceStop() throws Exception {
executeCmd("cmd activity force-stop " + TEST_PKG);
- assertUidState(PROCESS_STATE_NONEXISTENT);
+ assertProcState("NONEXISTENT");
}
- private void assertUidState(int state) throws Exception {
- final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
- final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]);
- assertEquals(state, uidState);
- }
-
- private void assertKeyguardUnLocked() {
+ private void assertKeyguardUnLocked() throws Exception {
final KeyguardManager keyguardManager =
(KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE);
- assertDelayedCondition("Keyguard should be unlocked",
- () -> !keyguardManager.isKeyguardLocked());
+ assertDelayedCondition("Unexpected Keyguard state", () ->
+ keyguardManager.isKeyguardLocked() ? "expected=unlocked" : null
+ );
}
- private void assertScreenInteractive(boolean interactive) {
+ private void assertScreenInteractive(boolean interactive) throws Exception {
final PowerManager powerManager =
(PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
- assertDelayedCondition("Unexpected screen interactive state",
- () -> interactive == powerManager.isInteractive());
+ assertDelayedCondition("Unexpected screen interactive state", () ->
+ interactive == powerManager.isInteractive() ? null : "expected=" + interactive
+ );
}
- private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) {
+ private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
+ throws Exception {
final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
while (SystemClock.uptimeMillis() <= endTime) {
- if (condition.isTrue()) {
+ if (condition.getErrIfNotTrue() == null) {
return;
}
SystemClock.sleep(GENERAL_INTERVAL_MS);
}
- if (!condition.isTrue()) {
- fail(errorMsg);
+ final String errMsg = condition.getErrIfNotTrue();
+ if (errMsg != null) {
+ fail(errMsgPrefix + ": " + errMsg);
}
}
- private void assertBatteryState(boolean pluggedIn) throws Exception {
+ private static void assertBatteryState(boolean pluggedIn) throws Exception {
final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS;
while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) {
Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
@@ -383,13 +775,13 @@
}
}
- private boolean isDevicePluggedIn() {
+ private static boolean isDevicePluggedIn() {
final Intent batteryIntent = sContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
}
- private String executeCmd(String cmd) throws Exception {
+ private static String executeCmd(String cmd) throws Exception {
final String result = sUiDevice.executeShellCommand(cmd).trim();
Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
return result;
@@ -400,6 +792,6 @@
}
private interface ExpectedCondition {
- boolean isTrue();
+ String getErrIfNotTrue() throws Exception;
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
new file mode 100644
index 0000000..5d72942
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.os.KernelSingleUidTimeReader.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelSingleUidTimeReaderTest {
+ private final static int TEST_UID = 2222;
+ private final static int TEST_FREQ_COUNT = 5;
+
+ private KernelSingleUidTimeReader mReader;
+ private TestInjector mInjector;
+
+ @Before
+ public void setUp() {
+ mInjector = new TestInjector();
+ mReader = new KernelSingleUidTimeReader(TEST_FREQ_COUNT, mInjector);
+ }
+
+ @Test
+ public void readDelta() {
+ final SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+ long[] latestCpuTimes = new long[] {120, 130, 140, 150, 160};
+ mInjector.setData(latestCpuTimes);
+ long[] deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ long[] expectedDeltaTimes = new long[] {200, 340, 1230, 490, 4890};
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ latestCpuTimes[i] += expectedDeltaTimes[i];
+ }
+ mInjector.setData(latestCpuTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // delta should be null if cpu times haven't changed
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed data (-ve)
+ long[] malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = -4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ mInjector.setData(malformedLatestTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed data (decreased)
+ malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ mInjector.setData(malformedLatestTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+ }
+
+ @Test
+ public void readDelta_fileNotAvailable() {
+ mInjector.letReadDataThrowException(true);
+
+ for (int i = 0; i < KernelSingleUidTimeReader.TOTAL_READ_ERROR_COUNT; ++i) {
+ assertTrue(mReader.singleUidCpuTimesAvailable());
+ mReader.readDeltaMs(TEST_UID);
+ }
+ assertFalse(mReader.singleUidCpuTimesAvailable());
+ }
+
+ @Test
+ public void testComputeDelta() {
+ // proc file not available
+ mReader.setSingleUidCpuTimesAvailable(false);
+ long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+ long[] deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ // cpu times have changed
+ mReader.setSingleUidCpuTimesAvailable(true);
+ SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+ long[] lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ allLastCpuTimes.put(TEST_UID, lastCpuTimes);
+ long[] expectedDeltaTimes = new long[] {123, 324, 43, 989, 80};
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ latestCpuTimes[i] = lastCpuTimes[i] + expectedDeltaTimes[i];
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // no change in cpu times
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed cpu times (-ve)
+ long[] malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = -4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed cpu times (decreased)
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+ }
+
+ @Test
+ public void testGetDelta() {
+ // No last cpu times
+ long[] lastCpuTimes = null;
+ long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+ long[] deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+
+ // Latest cpu times are -ve
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {15, -10, 19, 21, 23};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ // Latest cpu times are less than last cpu times
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {15, 11, 21, 34, 171};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {112, 213, 314, 415, 516};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(new long[] {100, 200, 300, 400, 500}, deltaCpuTimes);
+ }
+
+ @Test
+ public void testRemoveUid() {
+ final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+ lastUidCpuTimes.put(12, new long[] {});
+ lastUidCpuTimes.put(16, new long[] {});
+
+ mReader.removeUid(12);
+ assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+ lastUidCpuTimes.indexOfKey(12) >= 0);
+ mReader.removeUid(16);
+ assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+ lastUidCpuTimes.indexOfKey(16) >= 0);
+ }
+
+ @Test
+ public void testRemoveUidsRange() {
+ final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+ final int startUid = 12;
+ final int endUid = 24;
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid, endUid);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid - 1, endUid);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid, endUid + 1);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid - 1, endUid + 1);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+ }
+
+ private void assertCpuTimesEqual(long[] expected, long[] actual) {
+ assertArrayEquals("Expected=" + Arrays.toString(expected)
+ + ", Actual=" + Arrays.toString(actual), expected, actual);
+ }
+
+ class TestInjector extends Injector {
+ private byte[] mData;
+ private boolean mThrowExcpetion;
+
+ @Override
+ public byte[] readData(String procFile) throws IOException {
+ if (mThrowExcpetion) {
+ throw new IOException("In the test");
+ } else {
+ return mData;
+ }
+ }
+
+ public void setData(long[] cpuTimes) {
+ final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
+ buffer.order(ByteOrder.nativeOrder());
+ for (long time : cpuTimes) {
+ buffer.putLong(time / 10);
+ }
+ mData = buffer.array();
+ }
+
+ public void letReadDataThrowException(boolean throwException) {
+ mThrowExcpetion = throwException;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 63d1e5a..de2fd12 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,7 +16,10 @@
package com.android.internal.os;
+import android.util.SparseIntArray;
+
import java.util.ArrayList;
+import java.util.concurrent.Future;
/**
* Mocks a BatteryStatsImpl object.
@@ -33,6 +36,7 @@
mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
mOnBatteryTimeBase);
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
+ setExternalStatsSyncLocked(new DummyExternalStatsSync());
}
MockBatteryStatsImpl() {
@@ -78,6 +82,11 @@
return this;
}
+ public MockBatteryStatsImpl setKernelSingleUidTimeReader(KernelSingleUidTimeReader reader) {
+ mKernelSingleUidTimeReader = reader;
+ return this;
+ }
+
public MockBatteryStatsImpl setKernelCpuSpeedReaders(KernelCpuSpeedReader[] readers) {
mKernelCpuSpeedReaders = readers;
return this;
@@ -102,5 +111,32 @@
mOnBatteryInternal = onBatteryInternal;
return this;
}
+
+ public SparseIntArray getPendingUids() {
+ return mPendingUids;
+ }
+
+ private class DummyExternalStatsSync implements ExternalStatsSync {
+ @Override
+ public Future<?> scheduleSync(String reason, int flags) {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleReadProcStateCpuTimes() {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+ return null;
+ }
+
+ }
}
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/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/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..91cae0a 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/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/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 690c45b..e1cb154 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -568,13 +568,12 @@
@Override
public FillEventHistory getFillEventHistory() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getFillEventHistory(uid);
+ return service.getFillEventHistory(getCallingUid());
}
}
@@ -583,13 +582,12 @@
@Override
public UserData getUserData() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getUserData(uid);
+ return service.getUserData(getCallingUid());
}
}
@@ -598,26 +596,24 @@
@Override
public void setUserData(UserData userData) throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- service.setUserData(uid, userData);
+ service.setUserData(getCallingUid(), userData);
}
}
}
@Override
public boolean isFieldClassificationEnabled() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.isFieldClassificationEnabled();
+ return service.isFieldClassificationEnabled(getCallingUid());
}
}
@@ -625,6 +621,20 @@
}
@Override
+ public ComponentName getAutofillServiceComponentName() throws RemoteException {
+ final int userId = UserHandle.getCallingUserId();
+
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ return service.getServiceComponentName();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
throws RemoteException {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 3361824..4e92108 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -835,7 +835,7 @@
pw.println(mContext.getString(R.string.config_defaultAutofillService));
pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
pw.print(prefix); pw.print("Field classification enabled: ");
- pw.println(isFieldClassificationEnabled());
+ pw.println(isFieldClassificationEnabledLocked());
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
@@ -1095,7 +1095,18 @@
return false;
}
- boolean isFieldClassificationEnabled() {
+ // Called by AutofillManager, checks UID.
+ boolean isFieldClassificationEnabled(int uid) {
+ synchronized (mLock) {
+ if (!isCalledByServiceLocked("isFieldClassificationEnabled", uid)) {
+ return false;
+ }
+ return isFieldClassificationEnabledLocked();
+ }
+ }
+
+ // Called by internally, no need to check UID.
+ boolean isFieldClassificationEnabledLocked() {
return Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 0,
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7b85a6c..ad43ec2 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -500,6 +500,8 @@
@Override
public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
@NonNull String servicePackageName) {
+ final AutofillId[] fieldClassificationIds;
+
synchronized (mLock) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
@@ -510,13 +512,13 @@
processNullResponseLocked(requestFlags);
return;
}
- }
- final AutofillId[] fieldClassificationIds = response.getFieldClassificationIds();
- if (fieldClassificationIds != null && !mService.isFieldClassificationEnabled()) {
- Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
- processNullResponseLocked(requestFlags);
- return;
+ fieldClassificationIds = response.getFieldClassificationIds();
+ if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
+ Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
+ processNullResponseLocked(requestFlags);
+ return;
+ }
}
mService.setLastResponse(id, response);
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/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index f3ccba5..4582430 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -116,6 +116,50 @@
return scheduleSyncLocked("remove-uid", UPDATE_CPU);
}
+ @Override
+ public Future<?> scheduleReadProcStateCpuTimes() {
+ synchronized (mStats) {
+ if (!mStats.mPerProcStateCpuTimesAvailable) {
+ return null;
+ }
+ }
+ synchronized (BatteryExternalStatsWorker.this) {
+ if (!mExecutorService.isShutdown()) {
+ return mExecutorService.submit(mReadProcStateCpuTimesTask);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+ synchronized (mStats) {
+ if (!mStats.mPerProcStateCpuTimesAvailable) {
+ return null;
+ }
+ }
+ synchronized (BatteryExternalStatsWorker.this) {
+ if (!mExecutorService.isShutdown()) {
+ return mExecutorService.submit(mCopyFromAllUidsCpuTimesTask);
+ }
+ }
+ return null;
+ }
+
+ private final Runnable mReadProcStateCpuTimesTask = new Runnable() {
+ @Override
+ public void run() {
+ mStats.updateProcStateCpuTimes();
+ }
+ };
+
+ private final Runnable mCopyFromAllUidsCpuTimesTask = new Runnable() {
+ @Override
+ public void run() {
+ mStats.copyFromAllUidsCpuTimes();
+ }
+ };
+
public synchronized Future<?> scheduleWrite() {
if (mExecutorService.isShutdown()) {
return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
@@ -185,6 +229,10 @@
}
}
+ if ((updateFlags & UPDATE_CPU) != 0) {
+ mStats.copyFromAllUidsCpuTimes();
+ }
+
// Clean up any UIDs if necessary.
synchronized (mStats) {
for (int uid : uidsToRemove) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 93fb3e3..a057a99 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -207,6 +207,10 @@
}
}
+ private void syncStats(String reason, int flags) {
+ awaitUninterruptibly(mWorker.scheduleSync(reason, flags));
+ }
+
/**
* At the time when the constructor runs, the power manager has not yet been
* initialized. So we initialize the low power observer later.
@@ -225,7 +229,7 @@
public void shutdown() {
Slog.w("BatteryStats", "Writing battery stats before shutdown...");
- awaitUninterruptibly(mWorker.scheduleSync("shutdown", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("shutdown", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.shutdownLocked();
@@ -357,7 +361,7 @@
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
@@ -372,7 +376,7 @@
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
@@ -1237,8 +1241,7 @@
}
mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL);
} else if ("--write".equals(arg)) {
- awaitUninterruptibly(mWorker.scheduleSync("dump",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeSyncLocked();
pw.println("Battery stats written.");
@@ -1302,7 +1305,7 @@
flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
}
// Fetch data from external sources and update the BatteryStatsImpl object with them.
- awaitUninterruptibly(mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1415,8 +1418,7 @@
}
long ident = Binder.clearCallingIdentity();
try {
- awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
return getHealthStatsForUidLocked(requestUid);
}
@@ -1440,8 +1442,7 @@
long ident = Binder.clearCallingIdentity();
int i=-1;
try {
- awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
final int N = requestUids.length;
final HealthStatsParceler[] results = new HealthStatsParceler[N];
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index c2167eb..85686ae 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -99,7 +99,7 @@
private final float mProjMatrix[] = new float[16];
private final int[] mGLBuffers = new int[2];
private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc;
- private int mOpacityLoc, mScaleLoc, mGammaLoc, mSaturationLoc;
+ private int mOpacityLoc, mGammaLoc, mSaturationLoc;
private int mProgram;
// Vertex and corresponding texture coordinates.
@@ -246,7 +246,6 @@
mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity");
mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma");
mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation");
- mScaleLoc = GLES20.glGetUniformLocation(mProgram, "scale");
mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit");
GLES20.glUseProgram(mProgram);
@@ -395,9 +394,8 @@
double sign = cos < 0 ? -1 : 1;
float opacity = (float) -Math.pow(one_minus_level, 2) + 1;
float saturation = (float) Math.pow(level, 4);
- float scale = (float) ((-Math.pow(one_minus_level, 2) + 1) * 0.1d + 0.9d);
float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d);
- drawFaded(opacity, 1.f / gamma, saturation, scale);
+ drawFaded(opacity, 1.f / gamma, saturation);
if (checkGlErrors("drawFrame")) {
return false;
}
@@ -409,10 +407,10 @@
return showSurface(1.0f);
}
- private void drawFaded(float opacity, float gamma, float saturation, float scale) {
+ private void drawFaded(float opacity, float gamma, float saturation) {
if (DEBUG) {
Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma +
- ", saturation=" + saturation + ", scale=" + scale);
+ ", saturation=" + saturation);
}
// Use shaders
GLES20.glUseProgram(mProgram);
@@ -423,7 +421,6 @@
GLES20.glUniform1f(mOpacityLoc, opacity);
GLES20.glUniform1f(mGammaLoc, gamma);
GLES20.glUniform1f(mSaturationLoc, saturation);
- GLES20.glUniform1f(mScaleLoc, scale);
// Use textures
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index aa55930..0481dab 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1943,6 +1943,15 @@
return mRecoverableKeyStoreManager.getRecoveryData(account, userId);
}
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+ throws RemoteException {
+ mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId);
+ }
+
+ public Map getRecoverySnapshotVersions(int userId) throws RemoteException {
+ return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId);
+ }
+
@Override
public void setServerParameters(long serverParameters, int userId) throws RemoteException {
mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId);
@@ -1954,6 +1963,10 @@
mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId);
}
+ public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException {
+ return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId);
+ }
+
@Override
public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
int[] secretTypes, int userId) throws RemoteException {
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 e459f28..48f4626 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
@@ -30,9 +31,16 @@
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;
/**
* Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
@@ -44,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.
@@ -53,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(
@@ -77,7 +97,7 @@
* @return recovery data
* @hide
*/
- public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
@@ -100,6 +120,24 @@
RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
}
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application
+ * keys, but it still needs to be synced, if previous versions were not empty.
+ *
+ * @return Map from Recovery agent account to snapshot version.
+ */
+ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions(int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
public void setServerParameters(long serverParameters, int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
@@ -113,6 +151,21 @@
}
/**
+ * Gets recovery status for keys {@code packageName}.
+ *
+ * @param packageName which recoverable keys statuses will be returned
+ * @return Map from KeyStore alias to recovery status
+ */
+ public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId)
+ throws RemoteException {
+ // Any application should be able to check status for its own keys.
+ // If caller is a recovery agent it can check statuses for other packages, but
+ // only for recoverable keys it manages.
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Sets recovery secrets list used by all recovery agents for given {@code userId}
*
* @hide
@@ -130,7 +183,7 @@
* @return secret types
* @hide
*/
- public int[] getRecoverySecretTypes(int userId) throws RemoteException {
+ public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
}
@@ -141,7 +194,7 @@
* @return secret types
* @hide
*/
- public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+ public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
}
@@ -161,10 +214,16 @@
/**
* 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 byte[] startRecoverySession(
+ public @NonNull byte[] startRecoverySession(
@NonNull String sessionId,
@NonNull byte[] verifierPublicKey,
@NonNull byte[] vaultParams,
@@ -173,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/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/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/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/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/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/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/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/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index a3e2f80..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;
@@ -227,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;
@@ -251,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 b456908..0d7451b 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -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