Merge "Update hidden WifiManager API."
diff --git a/api/current.txt b/api/current.txt
index 5018ecf..8f969c1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -879,6 +879,7 @@
field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
field public static final int matchOrder = 16843855; // 0x101044f
field public static final int max = 16843062; // 0x1010136
+ field public static final int maxAspectRatio = 16844131; // 0x1010563
field public static final int maxButtonHeight = 16844029; // 0x10104fd
field public static final int maxDate = 16843584; // 0x1010340
field public static final int maxEms = 16843095; // 0x1010157
@@ -14463,7 +14464,7 @@
method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
method public final void unlock();
- field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+ field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
@@ -34249,6 +34250,7 @@
method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
+ method public static void ejectRoot(android.content.ContentResolver, android.net.Uri);
method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static java.lang.String getDocumentId(android.net.Uri);
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -34317,6 +34319,7 @@
field public static final java.lang.String COLUMN_TITLE = "title";
field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+ field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -34330,6 +34333,7 @@
method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+ method public void ejectRoot(java.lang.String);
method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
@@ -39933,13 +39937,17 @@
method public int getDataActivity();
method public int getDataNetworkType();
method public int getDataState();
- method public java.lang.String getDeviceId();
- method public java.lang.String getDeviceId(int);
+ method public deprecated java.lang.String getDeviceId();
+ method public deprecated java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
+ method public java.lang.String getImei();
+ method public java.lang.String getImei(int);
method public java.lang.String getLine1Number();
+ method public java.lang.String getMeid();
+ method public java.lang.String getMeid(int);
method public java.lang.String getMmsUAProfUrl();
method public java.lang.String getMmsUserAgent();
method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
@@ -43228,7 +43236,6 @@
method public E get(long, E);
method public int indexOfKey(long);
method public int indexOfValue(E);
- method public int indexOfValueByValue(E);
method public long keyAt(int);
method public void put(long, E);
method public void remove(long);
@@ -43431,7 +43438,6 @@
method public E get(int, E);
method public int indexOfKey(int);
method public int indexOfValue(E);
- method public int indexOfValueByValue(E);
method public int keyAt(int);
method public void put(int, E);
method public void remove(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 2bf01d1..48b878e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -992,6 +992,7 @@
field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
field public static final int matchOrder = 16843855; // 0x101044f
field public static final int max = 16843062; // 0x1010136
+ field public static final int maxAspectRatio = 16844131; // 0x1010563
field public static final int maxButtonHeight = 16844029; // 0x10104fd
field public static final int maxDate = 16843584; // 0x1010340
field public static final int maxEms = 16843095; // 0x1010157
@@ -15188,7 +15189,7 @@
method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
method public final void unlock();
- field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+ field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
@@ -37149,6 +37150,7 @@
method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
+ method public static void ejectRoot(android.content.ContentResolver, android.net.Uri);
method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static java.lang.String getDocumentId(android.net.Uri);
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -37217,6 +37219,7 @@
field public static final java.lang.String COLUMN_TITLE = "title";
field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+ field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -37230,6 +37233,7 @@
method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+ method public void ejectRoot(java.lang.String);
method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
@@ -43329,8 +43333,8 @@
method public deprecated boolean getDataEnabled(int);
method public int getDataNetworkType();
method public int getDataState();
- method public java.lang.String getDeviceId();
- method public java.lang.String getDeviceId(int);
+ method public deprecated java.lang.String getDeviceId();
+ method public deprecated java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
@@ -43338,6 +43342,8 @@
method public java.lang.String getImei();
method public java.lang.String getImei(int);
method public java.lang.String getLine1Number();
+ method public java.lang.String getMeid();
+ method public java.lang.String getMeid(int);
method public java.lang.String getMmsUAProfUrl();
method public java.lang.String getMmsUserAgent();
method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
@@ -46685,7 +46691,6 @@
method public E get(long, E);
method public int indexOfKey(long);
method public int indexOfValue(E);
- method public int indexOfValueByValue(E);
method public long keyAt(int);
method public void put(long, E);
method public void remove(long);
@@ -46888,7 +46893,6 @@
method public E get(int, E);
method public int indexOfKey(int);
method public int indexOfValue(E);
- method public int indexOfValueByValue(E);
method public int keyAt(int);
method public void put(int, E);
method public void remove(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index da25aa9..4d8d7f2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -879,6 +879,7 @@
field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
field public static final int matchOrder = 16843855; // 0x101044f
field public static final int max = 16843062; // 0x1010136
+ field public static final int maxAspectRatio = 16844131; // 0x1010563
field public static final int maxButtonHeight = 16844029; // 0x10104fd
field public static final int maxDate = 16843584; // 0x1010340
field public static final int maxEms = 16843095; // 0x1010157
@@ -14512,7 +14513,7 @@
method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
method public final void unlock();
- field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+ field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
@@ -34387,6 +34388,7 @@
method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
+ method public static void ejectRoot(android.content.ContentResolver, android.net.Uri);
method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static java.lang.String getDocumentId(android.net.Uri);
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -34455,6 +34457,7 @@
field public static final java.lang.String COLUMN_TITLE = "title";
field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+ field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -34468,6 +34471,7 @@
method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+ method public void ejectRoot(java.lang.String);
method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
@@ -40134,13 +40138,17 @@
method public int getDataActivity();
method public int getDataNetworkType();
method public int getDataState();
- method public java.lang.String getDeviceId();
- method public java.lang.String getDeviceId(int);
+ method public deprecated java.lang.String getDeviceId();
+ method public deprecated java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
+ method public java.lang.String getImei();
+ method public java.lang.String getImei(int);
method public java.lang.String getLine1Number();
+ method public java.lang.String getMeid();
+ method public java.lang.String getMeid(int);
method public java.lang.String getMmsUAProfUrl();
method public java.lang.String getMmsUserAgent();
method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
@@ -43434,7 +43442,6 @@
method public E get(long, E);
method public int indexOfKey(long);
method public int indexOfValue(E);
- method public int indexOfValueByValue(E);
method public long keyAt(int);
method public void put(long, E);
method public void remove(long);
@@ -43637,7 +43644,6 @@
method public E get(int, E);
method public int indexOfKey(int);
method public int indexOfValue(E);
- method public int indexOfValueByValue(E);
method public int keyAt(int);
method public void put(int, E);
method public void remove(int);
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 0d859a1..d710d8b 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -552,6 +552,7 @@
int[] mAdded;
BackStackState[] mBackStack;
int mPrimaryNavActiveIndex = -1;
+ int mNextFragmentIndex;
public FragmentManagerState() {
}
@@ -561,6 +562,7 @@
mAdded = in.createIntArray();
mBackStack = in.createTypedArray(BackStackState.CREATOR);
mPrimaryNavActiveIndex = in.readInt();
+ mNextFragmentIndex = in.readInt();
}
public int describeContents() {
@@ -572,6 +574,7 @@
dest.writeIntArray(mAdded);
dest.writeTypedArray(mBackStack, flags);
dest.writeInt(mPrimaryNavActiveIndex);
+ dest.writeInt(mNextFragmentIndex);
}
public static final Parcelable.Creator<FragmentManagerState> CREATOR
@@ -638,10 +641,10 @@
ArrayList<OpGenerator> mPendingActions;
boolean mExecutingActions;
-
- ArrayList<Fragment> mActive;
+
+ int mNextFragmentIndex = 0;
+ SparseArray<Fragment> mActive;
ArrayList<Fragment> mAdded;
- ArrayList<Integer> mAvailIndices;
ArrayList<BackStackRecord> mBackStack;
ArrayList<Fragment> mCreatedMenus;
@@ -839,6 +842,7 @@
}
doPendingDeferredStart();
+ burpActive();
return executePop;
}
@@ -882,10 +886,6 @@
if (index == -1) {
return null;
}
- if (index >= mActive.size()) {
- throwException(new IllegalStateException("Fragment no longer exists for key "
- + key + ": index " + index));
- }
Fragment f = mActive.get(index);
if (f == null) {
throwException(new IllegalStateException("Fragment no longer exists for key "
@@ -939,7 +939,7 @@
writer.print(Integer.toHexString(System.identityHashCode(this)));
writer.println(":");
for (int i=0; i<N; i++) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
writer.print(prefix); writer.print(" #"); writer.print(i);
writer.print(": "); writer.println(f);
if (f != null) {
@@ -1034,10 +1034,6 @@
writer.print(prefix); writer.print(" mNoTransactionsBecause=");
writer.println(mNoTransactionsBecause);
}
- if (mAvailIndices != null && mAvailIndices.size() > 0) {
- writer.print(prefix); writer.print(" mAvailIndices: ");
- writer.println(Arrays.toString(mAvailIndices.toArray()));
- }
}
Animator loadAnimator(Fragment fragment, int transit, boolean enter,
@@ -1164,7 +1160,7 @@
// If we have a target fragment, push it along to at least CREATED
// so that this one can rely on it as an initialized dependency.
if (f.mTarget != null) {
- if (!mActive.contains(f.mTarget)) {
+ if (mActive.get(f.mTarget.mIndex) != f.mTarget) {
throw new IllegalStateException("Fragment " + f
+ " declared target fragment " + f.mTarget
+ " that does not belong to this FragmentManager!");
@@ -1557,7 +1553,7 @@
// and detached.
final int numActive = mActive.size();
for (int i = 0; i < numActive; i++) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
moveFragmentToExpectedState(f);
if (f.mLoaderManager != null) {
@@ -1581,7 +1577,7 @@
if (mActive == null) return;
for (int i=0; i<mActive.size(); i++) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
if (f != null) {
performPendingDeferredStart(f);
}
@@ -1592,18 +1588,12 @@
if (f.mIndex >= 0) {
return;
}
-
- if (mAvailIndices == null || mAvailIndices.size() <= 0) {
- if (mActive == null) {
- mActive = new ArrayList<Fragment>();
- }
- f.setIndex(mActive.size(), mParent);
- mActive.add(f);
-
- } else {
- f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
- mActive.set(f.mIndex, f);
+
+ f.setIndex(mNextFragmentIndex++, mParent);
+ if (mActive == null) {
+ mActive = new SparseArray<>();
}
+ mActive.put(f.mIndex, f);
if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);
}
@@ -1613,11 +1603,9 @@
}
if (DEBUG) Log.v(TAG, "Freeing fragment index " + f);
- mActive.set(f.mIndex, null);
- if (mAvailIndices == null) {
- mAvailIndices = new ArrayList<Integer>();
- }
- mAvailIndices.add(f.mIndex);
+ // Don't remove yet. That happens in burpActive(). This prevents
+ // concurrent modification while iterating over mActive
+ mActive.put(f.mIndex, null);
mHost.inactivateFragment(f.mWho);
f.initState();
}
@@ -1754,7 +1742,7 @@
if (mActive != null) {
// Now for any known fragment.
for (int i=mActive.size()-1; i>=0; i--) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
if (f != null && f.mFragmentId == id) {
return f;
}
@@ -1776,7 +1764,7 @@
if (mActive != null && tag != null) {
// Now for any known fragment.
for (int i=mActive.size()-1; i>=0; i--) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
@@ -1788,7 +1776,7 @@
public Fragment findFragmentByWho(String who) {
if (mActive != null && who != null) {
for (int i=mActive.size()-1; i>=0; i--) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
if (f != null && (f=f.findFragmentByWho(who)) != null) {
return f;
}
@@ -1953,6 +1941,7 @@
}
doPendingDeferredStart();
+ burpActive();
}
/**
@@ -1983,6 +1972,7 @@
}
doPendingDeferredStart();
+ burpActive();
return didSomething;
}
@@ -2248,7 +2238,7 @@
for (int i = 0; i < numActive; i++) {
// Allow added fragments to be removed during the pop since we aren't going
// to move them to the final state with moveToState(mCurState).
- Fragment fragment = mActive.get(i);
+ Fragment fragment = mActive.valueAt(i);
if (fragment != null && fragment.mView != null && fragment.mIsNewlyAdded
&& record.interactsWith(fragment.mContainerId)) {
fragment.mIsNewlyAdded = false;
@@ -2360,7 +2350,7 @@
private void endAnimatingAwayFragments() {
final int numFragments = mActive == null ? 0 : mActive.size();
for (int i = 0; i < numFragments; i++) {
- Fragment fragment = mActive.get(i);
+ Fragment fragment = mActive.valueAt(i);
if (fragment != null && fragment.getAnimatingAway() != null) {
// Give up waiting for the animation and just end it.
fragment.getAnimatingAway().end();
@@ -2400,7 +2390,7 @@
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
for (int i=0; i<mActive.size(); i++) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
if (f != null && f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
@@ -2489,7 +2479,7 @@
ArrayList<FragmentManagerNonConfig> childFragments = null;
if (mActive != null) {
for (int i=0; i<mActive.size(); i++) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
if (f != null) {
if (f.mRetainInstance) {
if (fragments == null) {
@@ -2594,7 +2584,7 @@
FragmentState[] active = new FragmentState[N];
boolean haveFragments = false;
for (int i=0; i<N; i++) {
- Fragment f = mActive.get(i);
+ Fragment f = mActive.valueAt(i);
if (f != null) {
if (f.mIndex < 0) {
throwException(new IllegalStateException(
@@ -2680,6 +2670,7 @@
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
+ fms.mNextFragmentIndex = mNextFragmentIndex;
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
}
@@ -2722,10 +2713,7 @@
// Build the full list of active fragments, instantiating them from
// their saved state.
- mActive = new ArrayList<>(fms.mActive.length);
- if (mAvailIndices != null) {
- mAvailIndices.clear();
- }
+ mActive = new SparseArray<>(fms.mActive.length);
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
@@ -2735,18 +2723,11 @@
}
Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
- mActive.add(f);
+ mActive.put(f.mIndex, f);
// Now that the fragment is instantiated (or came from being
// retained above), clear mInstance in case we end up re-restoring
// from this FragmentState again.
fs.mInstance = null;
- } else {
- mActive.add(null);
- if (mAvailIndices == null) {
- mAvailIndices = new ArrayList<>();
- }
- if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
- mAvailIndices.add(i);
}
}
@@ -2757,9 +2738,8 @@
for (int i = 0; i < count; i++) {
Fragment f = nonConfigFragments.get(i);
if (f.mTargetIndex >= 0) {
- if (f.mTargetIndex < mActive.size()) {
- f.mTarget = mActive.get(f.mTargetIndex);
- } else {
+ f.mTarget = mActive.get(f.mTargetIndex);
+ if (f.mTarget == null) {
Log.w(TAG, "Re-attaching retained fragment " + f
+ " target no longer exists: " + f.mTargetIndex);
f.mTarget = null;
@@ -2813,8 +2793,25 @@
if (fms.mPrimaryNavActiveIndex >= 0) {
mPrimaryNav = mActive.get(fms.mPrimaryNavActiveIndex);
}
+
+ mNextFragmentIndex = fms.mNextFragmentIndex;
}
-
+
+ /**
+ * To prevent list modification errors, mActive sets values to null instead of
+ * removing them when the Fragment becomes inactive. This cleans up the list at the
+ * end of executing the transactions.
+ */
+ private void burpActive() {
+ if (mActive != null) {
+ for (int i = mActive.size() - 1; i >= 0; i--) {
+ if (mActive.valueAt(i) == null) {
+ mActive.delete(mActive.keyAt(i));
+ }
+ }
+ }
+ }
+
public void attachController(FragmentHostCallback<?> host, FragmentContainer container,
Fragment parent) {
if (mHost != null) throw new IllegalStateException("Already attached");
@@ -3051,8 +3048,8 @@
}
public void setPrimaryNavigationFragment(Fragment f) {
- if (f != null && (f.getFragmentManager() != this || f.mIndex >= mActive.size()
- || mActive.get(f.mIndex) != f)) {
+ if (f != null && (mActive.get(f.mIndex) != f
+ || (f.mHost != null && f.getFragmentManager() != this))) {
throw new IllegalArgumentException("Fragment " + f
+ " is not an active fragment of FragmentManager " + this);
}
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 7f0b6fb..cae54b6 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -53,21 +53,6 @@
int getNightMode();
/**
- * Sets whith theme overlays to use within /vendor/overlay.
- */
- void setTheme(String theme);
-
- /**
- * Gets which theme overlays to use within /vendor/overlay.
- */
- String getTheme();
-
- /**
- * Gets the themes available in /vendor/overlay.
- */
- String[] getAvailableThemes();
-
- /**
* Tells if UI mode is locked or not.
*/
boolean isUiModeLocked();
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index af41a7d..07e2570 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -242,50 +242,6 @@
}
/**
- * Sets the vendor theme overlay property, then triggers a reboot.
- * @hide
- */
- public void setTheme(String theme) {
- if (mService != null) {
- try {
- mService.setTheme(theme);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
-
- /**
- * Gets the vendor theme overlay property.
- * @hide
- */
- public String getTheme() {
- if (mService != null) {
- try {
- return mService.getTheme();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return null;
- }
-
- /**
- * Gets the valid inputs to {@link #setTheme(String)}.
- * @hide
- */
- public String[] getAvailableThemes() {
- if (mService != null) {
- try {
- return mService.getAvailableThemes();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return null;
- }
-
- /**
* Returns the currently configured night mode.
* <p>
* May be one of:
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index a0b0eea..4dd578e 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -6,6 +6,8 @@
import android.os.RemoteException;
import android.service.vr.IVrManager;
+import java.io.FileDescriptor;
+
/**
* Used to control aspects of a devices Virtual Reality (VR) capabilities.
* <p>
@@ -41,4 +43,32 @@
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Initiate connection for system controller data.
+ *
+ * @param fd Controller data file descriptor.
+ *
+ * {@hide}
+ */
+ public void connectController(FileDescriptor fd) {
+ try {
+ mService.connectController(fd);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sever connection for system controller data.
+ *
+ * {@hide}
+ */
+ public void disconnectController() {
+ try {
+ mService.disconnectController();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 391885d..6d8d5e9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7207,7 +7207,7 @@
* @return {@code true} if security logging is enabled by device owner, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner.
*/
- public boolean isSecurityLoggingEnabled(@NonNull ComponentName admin) {
+ public boolean isSecurityLoggingEnabled(@Nullable ComponentName admin) {
throwIfParentInstance("isSecurityLoggingEnabled");
try {
return mService.isSecurityLoggingEnabled(admin);
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 6b3ef4a..9e2eb84 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -141,6 +141,7 @@
* Application interface registered - app is ready to go
* @hide
*/
+ @Override
public void onClientRegistered(int status, int clientIf) {
if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status
+ " clientIf=" + clientIf);
@@ -210,6 +211,7 @@
* Client connection state changed
* @hide
*/
+ @Override
public void onClientConnectionState(int status, int clientIf,
boolean connected, String address) {
if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
@@ -245,6 +247,7 @@
* we are done at this point.
* @hide
*/
+ @Override
public void onSearchComplete(String address, List<BluetoothGattService> services,
int status) {
if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
@@ -288,6 +291,7 @@
* Updates the internal value.
* @hide
*/
+ @Override
public void onCharacteristicRead(String address, int status, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
+ " handle=" + handle + " Status=" + status);
@@ -336,6 +340,7 @@
* Let the app know how we did...
* @hide
*/
+ @Override
public void onCharacteristicWrite(String address, int status, int handle) {
if (VDBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
+ " handle=" + handle + " Status=" + status);
@@ -380,6 +385,7 @@
* Updates the internal value.
* @hide
*/
+ @Override
public void onNotify(String address, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle);
@@ -403,6 +409,7 @@
* Descriptor has been read.
* @hide
*/
+ @Override
public void onDescriptorRead(String address, int status, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " handle=" + handle);
@@ -446,6 +453,7 @@
* Descriptor write operation complete.
* @hide
*/
+ @Override
public void onDescriptorWrite(String address, int status, int handle) {
if (VDBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " handle=" + handle);
@@ -488,6 +496,7 @@
* Prepared write transaction completed (or aborted)
* @hide
*/
+ @Override
public void onExecuteWrite(String address, int status) {
if (VDBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
+ " status=" + status);
@@ -510,6 +519,7 @@
* Remote device RSSI has been read
* @hide
*/
+ @Override
public void onReadRemoteRssi(String address, int rssi, int status) {
if (VDBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
" rssi=" + rssi + " status=" + status);
@@ -527,6 +537,7 @@
* Callback invoked when the MTU for a given connection changes
* @hide
*/
+ @Override
public void onConfigureMTU(String address, int mtu, int status) {
if (DBG) Log.d(TAG, "onConfigureMTU() - Device=" + address +
" mtu=" + mtu + " status=" + status);
@@ -539,6 +550,27 @@
Log.w(TAG, "Unhandled exception in callback", ex);
}
}
+
+ /**
+ * Callback invoked when the given connection is updated
+ * @hide
+ */
+ @Override
+ public void onConnectionUpdated(String address, int interval, int latency,
+ int timeout, int status) {
+ if (DBG) Log.d(TAG, "onConnectionUpdated() - Device=" + address +
+ " interval=" + interval + " latency=" + latency +
+ " timeout=" + timeout + " status=" + status);
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ try {
+ mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
+ timeout, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception in callback", ex);
+ }
+ }
};
/*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index be69df9..11a15c6 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -179,4 +179,22 @@
*/
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
}
+
+ /**
+ * Callback indicating the connection parameters were updated.
+ *
+ * @param gatt GATT client involved
+ * @param interval Connection interval used on this connection, 1.25ms unit. Valid
+ * range is from 6 (7.5ms) to 3200 (4000ms).
+ * @param latency Slave latency for the connection in number of connection events. Valid
+ * range is from 0 to 499
+ * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is
+ * from 10 (0.1s) to 3200 (32s)
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
+ * successfully
+ * @hide
+ */
+ public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout,
+ int status) {
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 1bd7bd4..c991e2f 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -65,6 +65,7 @@
* Application interface registered - app is ready to go
* @hide
*/
+ @Override
public void onServerRegistered(int status, int serverIf) {
if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status
+ " serverIf=" + serverIf);
@@ -80,18 +81,10 @@
}
/**
- * Callback reporting an LE scan result.
- * @hide
- */
- public void onScanResult(String address, int rssi, byte[] advData) {
- if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
- // no op
- }
-
- /**
* Server connection state changed
* @hide
*/
+ @Override
public void onServerConnectionState(int status, int serverIf,
boolean connected, String address) {
if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status
@@ -109,6 +102,7 @@
* Service has been added
* @hide
*/
+ @Override
public void onServiceAdded(int status, BluetoothGattService service) {
if (DBG) Log.d(TAG, "onServiceAdded() - handle=" + service.getInstanceId()
+ " uuid=" + service.getUuid() + " status=" + status);
@@ -149,6 +143,7 @@
* Remote client characteristic read request.
* @hide
*/
+ @Override
public void onCharacteristicReadRequest(String address, int transId,
int offset, boolean isLong, int handle) {
if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
@@ -171,6 +166,7 @@
* Remote client descriptor read request.
* @hide
*/
+ @Override
public void onDescriptorReadRequest(String address, int transId,
int offset, boolean isLong, int handle) {
if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
@@ -193,6 +189,7 @@
* Remote client characteristic write request.
* @hide
*/
+ @Override
public void onCharacteristicWriteRequest(String address, int transId,
int offset, int length, boolean isPrep, boolean needRsp,
int handle, byte[] value) {
@@ -218,6 +215,7 @@
* Remote client descriptor write request.
* @hide
*/
+ @Override
public void onDescriptorWriteRequest(String address, int transId, int offset,
int length, boolean isPrep, boolean needRsp, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - handle=" + handle);
@@ -241,6 +239,7 @@
* Execute pending writes.
* @hide
*/
+ @Override
public void onExecuteWrite(String address, int transId,
boolean execWrite) {
if (DBG) Log.d(TAG, "onExecuteWrite() - "
@@ -261,6 +260,7 @@
* A notification/indication has been sent.
* @hide
*/
+ @Override
public void onNotificationSent(String address, int status) {
if (VDBG) Log.d(TAG, "onNotificationSent() - "
+ "device=" + address + ", status=" + status);
@@ -279,6 +279,7 @@
* The MTU for a connection has changed
* @hide
*/
+ @Override
public void onMtuChanged(String address, int mtu) {
if (DBG) Log.d(TAG, "onMtuChanged() - "
+ "device=" + address + ", mtu=" + mtu);
@@ -297,6 +298,7 @@
* The PHY for a connection was updated
* @hide
*/
+ @Override
public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
if (DBG) Log.d(TAG, "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
+ ", rxPHy=" + rxPhy);
@@ -315,6 +317,7 @@
* The PHY for a connection was read
* @hide
*/
+ @Override
public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
if (DBG) Log.d(TAG, "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
+ ", rxPHy=" + rxPhy);
@@ -328,6 +331,28 @@
Log.w(TAG, "Unhandled exception: " + ex);
}
}
+
+ /**
+ * Callback invoked when the given connection is updated
+ * @hide
+ */
+ @Override
+ public void onConnectionUpdated(String address, int interval, int latency,
+ int timeout, int status) {
+ if (DBG) Log.d(TAG, "onConnectionUpdated() - Device=" + address +
+ " interval=" + interval + " latency=" + latency +
+ " timeout=" + timeout + " status=" + status);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ if (device == null) return;
+
+ try {
+ mCallback.onConnectionUpdated(device, interval, latency,
+ timeout, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
};
/**
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 0a89072..3b8f962 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -184,4 +184,23 @@
*/
public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
}
+
+ /**
+ * Callback indicating the connection parameters were updated.
+ *
+ * @param device The remote device involved
+ * @param interval Connection interval used on this connection, 1.25ms unit. Valid
+ * range is from 6 (7.5ms) to 3200 (4000ms).
+ * @param latency Slave latency for the connection in number of connection events. Valid
+ * range is from 0 to 499
+ * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is
+ * from 10 (0.1s) to 3200 (32s)
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
+ * successfully
+ * @hide
+ */
+ public void onConnectionUpdated(BluetoothDevice gatt, int interval, int latency, int timeout,
+ int status) {
+ }
+
}
diff --git a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
index 736f4b2..ed69e54 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
@@ -37,4 +37,6 @@
void onNotify(in String address, in int handle, in byte[] value);
void onReadRemoteRssi(in String address, in int rssi, in int status);
void onConfigureMTU(in String address, in int mtu, in int status);
+ void onConnectionUpdated(in String address, in int interval, in int latency,
+ in int timeout, in int status);
}
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
index 091ffb3..267e882 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
@@ -42,4 +42,6 @@
void onMtuChanged(in String address, in int mtu);
void onPhyUpdate(in String address, in int txPhy, in int rxPhy, in int status);
void onPhyRead(in String address, in int txPhy, in int rxPhy, in int status);
+ void onConnectionUpdated(in String address, in int interval, in int latency,
+ in int timeout, in int status);
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index ecdc0ce..78e3de4 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -214,7 +214,7 @@
}
private boolean checkFeaturePresent() {
- boolean featurePresent = mService == null;
+ boolean featurePresent = mService != null;
if (!featurePresent && DEBUG) {
Log.d(LOG_TAG, "Feature " + PackageManager.FEATURE_COMPANION_DEVICE_SETUP
+ " not available");
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 6dbe5fb..b01e6a1 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -223,6 +223,15 @@
public int resizeMode = RESIZE_MODE_RESIZEABLE;
/**
+ * Value indicating the maximum aspect ratio the activity supports.
+ * <p>
+ * 0 means unset.
+ * @See {@link android.R.attr#maxAspectRatio}.
+ * @hide
+ */
+ public float maxAspectRatio;
+
+ /**
* Name of the VrListenerService component to run for this activity.
* @see android.R.attr#enableVrMode
* @hide
@@ -922,6 +931,7 @@
requestedVrComponent = orig.requestedVrComponent;
rotationAnimation = orig.rotationAnimation;
colorMode = orig.colorMode;
+ maxAspectRatio = orig.maxAspectRatio;
}
/**
@@ -1064,6 +1074,9 @@
if (requestedVrComponent != null) {
pw.println(prefix + "requestedVrComponent=" + requestedVrComponent);
}
+ if (maxAspectRatio != 0) {
+ pw.println(prefix + "maxAspectRatio=" + maxAspectRatio);
+ }
super.dumpBack(pw, prefix, flags);
}
@@ -1110,6 +1123,7 @@
dest.writeString(requestedVrComponent);
dest.writeInt(rotationAnimation);
dest.writeInt(colorMode);
+ dest.writeFloat(maxAspectRatio);
}
public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -1146,6 +1160,7 @@
requestedVrComponent = source.readString();
rotationAnimation = source.readInt();
colorMode = source.readInt();
+ maxAspectRatio = source.readFloat();
}
/**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index ffc7719..939e20f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -603,6 +603,15 @@
public int largestWidthLimitDp = 0;
/**
+ * Value indicating the maximum aspect ratio the application supports.
+ * <p>
+ * 0 means unset.
+ * @See {@link android.R.attr#maxAspectRatio}.
+ * @hide
+ */
+ public float maxAspectRatio;
+
+ /**
* UUID of the storage volume on which this application is being hosted. For
* apps hosted on the default internal storage at
* {@link Environment#getDataDirectory()}, the UUID value is {@code null}.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index fb699866..1fafe65 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -37,6 +37,7 @@
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
@@ -147,6 +148,8 @@
public static final int APK_SIGNING_V1 = 1;
public static final int APK_SIGNING_V2 = 2;
+ private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
+
// TODO: switch outError users to PackageParserException
// TODO: refactor "codePath" to "apkPath"
@@ -3465,6 +3468,8 @@
ai.privateFlags |= PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION;
}
+ ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
+
ai.networkSecurityConfigRes = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig,
0);
@@ -4161,6 +4166,8 @@
a.info.flags |= FLAG_ALWAYS_FOCUSABLE;
}
+ setActivityMaxAspectRatio(a.info, sa, owner);
+
a.info.lockTaskLaunchMode =
sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
@@ -4376,6 +4383,27 @@
}
}
+ private void setActivityMaxAspectRatio(ActivityInfo aInfo, TypedArray sa, Package owner) {
+ if (aInfo.resizeMode == RESIZE_MODE_RESIZEABLE
+ || aInfo.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // Resizeable activities can be put in any aspect ratio.
+ aInfo.maxAspectRatio = 0;
+ return;
+ }
+
+ // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
+ // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
+ float defaultMaxAspectRatio = owner.applicationInfo.targetSdkVersion < O
+ ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
+ if (owner.applicationInfo.maxAspectRatio != 0 ) {
+ // Use the application max aspect ration as default if set.
+ defaultMaxAspectRatio = owner.applicationInfo.maxAspectRatio;
+ }
+
+ aInfo.maxAspectRatio = Math.max(1.0f, sa.getFloat(
+ R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio));
+ }
+
/**
* @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
* @param restartOnConfigChanges The bit mask restartOnConfigChanges fetched from
@@ -4512,6 +4540,7 @@
info.maxRecents = target.info.maxRecents;
info.windowLayout = target.info.windowLayout;
info.resizeMode = target.info.resizeMode;
+ info.maxAspectRatio = target.info.maxAspectRatio;
info.encryptionAware = info.directBootAware = target.info.directBootAware;
Activity a = new Activity(mParseActivityAliasArgs, info);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index acf0677..061346c 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -178,24 +178,39 @@
private static final int NO_ERROR = 0;
/**
- * @deprecated This broadcast is no longer delivered by the system; use
- * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
- * instead.
* Broadcast Action: A new picture is taken by the camera, and the entry of
* the picture has been added to the media store.
* {@link android.content.Intent#getData} is URI of the picture.
+ *
+ * <p>In {@link android.os.Build.VERSION_CODES#N Android N} this broadcast was removed, and
+ * applications are recommended to use
+ * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
+ * instead.</p>
+ *
+ * <p>In {@link android.os.Build.VERSION_CODES#O Android O} this broadcast has been brought
+ * back, but only for <em>registered</em> receivers. Apps that are actively running can
+ * against listen to the broadcast if they want an immediate clear signal about a picture
+ * being taken, however anything doing heavy work (or needing to be launched) as a result of
+ * this should still use JobScheduler.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- @Deprecated
public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
/**
- * @deprecated This broadcast is no longer delivered by the system; use
- * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
- * instead.
* Broadcast Action: A new video is recorded by the camera, and the entry
* of the video has been added to the media store.
* {@link android.content.Intent#getData} is URI of the video.
+ *
+ * <p>In {@link android.os.Build.VERSION_CODES#N Android N} this broadcast was removed, and
+ * applications are recommended to use
+ * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
+ * instead.</p>
+ *
+ * <p>In {@link android.os.Build.VERSION_CODES#O Android O} this broadcast has been brought
+ * back, but only for <em>registered</em> receivers. Apps that are actively running can
+ * against listen to the broadcast if they want an immediate clear signal about a video
+ * being taken, however anything doing heavy work (or needing to be launched) as a result of
+ * this should still use JobScheduler.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@Deprecated
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 29884b1..dd11f68 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -24,14 +24,12 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Predicate;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.telephony.SignalStrength;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.Log;
import android.util.LongSparseArray;
import android.util.MutableBoolean;
import android.util.Pair;
@@ -184,7 +182,7 @@
* New in version 19:
* - Wakelock data (wl) gets current and max times.
* New in version 20:
- * - Sensor gets a background counter.
+ * - Sensor, BluetoothScan, WifiScan get background timers and counter.
*/
static final String CHECKIN_VERSION = "20";
@@ -363,7 +361,7 @@
/**
* Returns the max duration if it is being tracked.
- * Not all Timer subclasses track the max duration and the current duration.
+ * Not all Timer subclasses track the max, total, current durations.
*/
public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
@@ -372,13 +370,28 @@
/**
* Returns the current time the timer has been active, if it is being tracked.
- * Not all Timer subclasses track the max duration and the current duration.
+ * Not all Timer subclasses track the max, total, current durations.
*/
public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
return -1;
}
/**
+ * Returns the current time the timer has been active, if it is being tracked.
+ *
+ * Returns the total cumulative duration (i.e. sum of past durations) that this timer has
+ * been on since reset.
+ * This may differ from getTotalTimeLocked(elapsedRealtimeUs, STATS_SINCE_CHARGED)/1000 since,
+ * depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
+ * time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
+ * the actual total time.
+ * Not all Timer subclasses track the max, total, current durations.
+ */
+ public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
+ return -1;
+ }
+
+ /**
* Returns whether the timer is currently running. Some types of timers
* (e.g. BatchTimers) don't know whether the event is currently active,
* and report false.
@@ -477,6 +490,9 @@
public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
public abstract int getWifiScanCount(int which);
+ public abstract int getWifiScanBackgroundCount(int which);
+ public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
+ public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
public abstract int getWifiBatchedScanCount(int csphBin, int which);
public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
@@ -486,6 +502,7 @@
public abstract Timer getCameraTurnedOnTimer();
public abstract Timer getForegroundActivityTimer();
public abstract Timer getBluetoothScanTimer();
+ public abstract Timer getBluetoothScanBackgroundTimer();
// 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.
@@ -609,8 +626,8 @@
public abstract Timer getSensorTime();
- /** Returns a counter for usage count when in the background. */
- public abstract Counter getSensorBgCount();
+ /** Returns a Timer for sensor usage when app is in the background. */
+ public abstract Timer getSensorBackgroundTime();
}
public class Pid {
@@ -2652,7 +2669,7 @@
* @param pw a PrintWriter object to print to.
* @param sb a StringBuilder object.
* @param timer a Timer object contining the wakelock times.
- * @param rawRealtime the current on-battery time in microseconds.
+ * @param rawRealtimeUs the current on-battery time in microseconds.
* @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
* @param prefix a String to be prepended to each line of output.
* @param type the name of the timer.
@@ -3284,19 +3301,41 @@
final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
final long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
final int wifiScanCount = u.getWifiScanCount(which);
+ final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
+ // Note that 'ActualTime' are unpooled and always since reset (regardless of 'which')
+ final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
+ final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
+ || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
|| uidWifiRunningTime != 0) {
dumpLine(pw, uid, category, WIFI_DATA, fullWifiLockOnTime, wifiScanTime,
uidWifiRunningTime, wifiScanCount,
- /* legacy fields follow, keep at 0 */ 0, 0, 0);
+ /* legacy fields follow, keep at 0 */ 0, 0, 0,
+ wifiScanCountBg, wifiScanActualTime, wifiScanActualTimeBg);
}
dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA,
u.getWifiControllerActivity(), which);
- dumpTimer(pw, uid, category, BLUETOOTH_MISC_DATA, u.getBluetoothScanTimer(),
- rawRealtime, which);
+ final Timer bleTimer = u.getBluetoothScanTimer();
+ if (bleTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ final long totalTime = (bleTimer.getTotalTimeLocked(rawRealtime, which) + 500)
+ / 1000;
+ if (totalTime != 0) {
+ final int count = bleTimer.getCountLocked(which);
+ final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
+ final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long actualTime = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
+ final long actualTimeBg = bleTimerBg != null ?
+ bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+ dumpLine(pw, uid, category, BLUETOOTH_MISC_DATA, totalTime, count,
+ countBg, actualTime, actualTimeBg);
+ }
+ }
dumpControllerActivityLine(pw, uid, category, BLUETOOTH_CONTROLLER_DATA,
u.getBluetoothControllerActivity(), which);
@@ -3375,16 +3414,21 @@
final Uid.Sensor se = sensors.valueAt(ise);
final int sensorNumber = sensors.keyAt(ise);
final Timer timer = se.getSensorTime();
- final Counter bgCounter = se.getSensorBgCount();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
/ 1000;
- final int count = timer.getCountLocked(which);
- final int bgCount = bgCounter != null ? bgCounter.getCountLocked(which) : 0;
if (totalTime != 0) {
- dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count,
- bgCount);
+ final int count = timer.getCountLocked(which);
+ final Timer bgTimer = se.getSensorBackgroundTime();
+ final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
+ final long bgActualTime = bgTimer != null ?
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+ dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime,
+ count, bgCount, actualTime, bgActualTime);
}
}
}
@@ -4294,6 +4338,10 @@
final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
final long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
final int wifiScanCount = u.getWifiScanCount(which);
+ final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
+ final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
final long mobileWakeup = u.getMobileRadioApWakeupCount(which);
@@ -4344,6 +4392,7 @@
}
if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
+ || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
|| uidWifiRunningTime != 0) {
sb.setLength(0);
sb.append(prefix); sb.append(" Wifi Running: ");
@@ -4354,11 +4403,26 @@
formatTimeMs(sb, fullWifiLockOnTime / 1000);
sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
whichBatteryRealtime)); sb.append(")\n");
- sb.append(prefix); sb.append(" Wifi Scan: ");
+ sb.append(prefix); sb.append(" Wifi Scan (blamed): ");
formatTimeMs(sb, wifiScanTime / 1000);
sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
whichBatteryRealtime)); sb.append(") ");
sb.append(wifiScanCount);
+ sb.append("x\n");
+ // actual and background times are unpooled and since reset (regardless of 'which')
+ sb.append(prefix); sb.append(" Wifi Scan (actual): ");
+ formatTimeMs(sb, wifiScanActualTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(wifiScanActualTime,
+ computeBatteryRealtime(rawRealtime, STATS_SINCE_CHARGED)));
+ sb.append(") ");
+ sb.append(wifiScanCount);
+ sb.append("x\n");
+ sb.append(prefix); sb.append(" Background Wifi Scan: ");
+ formatTimeMs(sb, wifiScanActualTimeBg / 1000);
+ sb.append("("); sb.append(formatRatioLocked(wifiScanActualTimeBg,
+ computeBatteryRealtime(rawRealtime, STATS_SINCE_CHARGED)));
+ sb.append(") ");
+ sb.append(wifiScanCountBg);
sb.append("x");
pw.println(sb.toString());
}
@@ -4381,8 +4445,50 @@
pw.println(" sent");
}
- uidActivity |= printTimer(pw, sb, u.getBluetoothScanTimer(), rawRealtime, which, prefix,
- "Bluetooth Scan");
+ final Timer bleTimer = u.getBluetoothScanTimer();
+ if (bleTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ final long totalTimeMs = (bleTimer.getTotalTimeLocked(rawRealtime, which) + 500)
+ / 1000;
+ if (totalTimeMs != 0) {
+ final int count = bleTimer.getCountLocked(which);
+ final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
+ final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long actualTimeMs = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
+ final long actualTimeMsBg = bleTimerBg != null ?
+ bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append("Bluetooth Scan");
+ sb.append(": ");
+ if (actualTimeMs != totalTimeMs) {
+ formatTimeMs(sb, totalTimeMs);
+ sb.append("blamed realtime, ");
+ }
+ formatTimeMs(sb, actualTimeMs); // since reset, regardless of 'which'
+ sb.append("realtime (");
+ sb.append(count);
+ sb.append(" times)");
+ if (bleTimer.isRunningLocked()) {
+ sb.append(" (running)");
+ }
+ if (actualTimeMsBg != 0 || countBg > 0) {
+ sb.append(", ");
+ formatTimeMs(sb, actualTimeMsBg); // since reset, regardless of 'which'
+ sb.append("background (");
+ sb.append(countBg);
+ sb.append(" times)");
+ }
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
+
if (u.hasUserActivity()) {
boolean hasData = false;
@@ -4553,25 +4659,38 @@
sb.append(": ");
final Timer timer = se.getSensorTime();
- final Counter bgCounter = se.getSensorBgCount();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- final long totalTime = (timer.getTotalTimeLocked(
- rawRealtime, which) + 500) / 1000;
+ final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
+ / 1000;
final int count = timer.getCountLocked(which);
- final int bgCount = bgCounter != null ? bgCounter.getCountLocked(which) : 0;
+ final Timer bgTimer = se.getSensorBackgroundTime();
+ final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
+ final long bgActualTime = bgTimer != null ?
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+
//timer.logState();
if (totalTime != 0) {
- formatTimeMs(sb, totalTime);
+ if (actualTime != totalTime) {
+ formatTimeMs(sb, totalTime);
+ sb.append("blamed realtime, ");
+ }
+
+ formatTimeMs(sb, actualTime); // since reset, regardless of 'which'
sb.append("realtime (");
sb.append(count);
- sb.append(" times");
- if (bgCount > 0) {
+ sb.append(" times)");
+
+ if (bgActualTime != 0 || bgCount > 0) {
sb.append(", ");
+ formatTimeMs(sb, bgActualTime); // since reset, regardless of 'which'
+ sb.append("background (");
sb.append(bgCount);
- sb.append(" bg");
+ sb.append(" times)");
}
- sb.append(")");
} else {
sb.append("(not used)");
}
diff --git a/core/java/android/os/PowerSaveState.java b/core/java/android/os/PowerSaveState.java
index 9269e76..7058a1d 100644
--- a/core/java/android/os/PowerSaveState.java
+++ b/core/java/android/os/PowerSaveState.java
@@ -24,7 +24,17 @@
* @hide
*/
public class PowerSaveState implements Parcelable {
+ /**
+ * Whether we should enable battery saver for this service.
+ *
+ * @see com.android.server.power.BatterySaverPolicy.ServiceType
+ */
public final boolean batterySaverEnabled;
+ /**
+ * Whether the battery saver is enabled globally, which means the data we get from
+ * {@link PowerManager#isPowerSaveMode()}
+ */
+ public final boolean globalBatterySaverEnabled;
public final int gpsMode;
public final float brightnessFactor;
@@ -32,10 +42,12 @@
batterySaverEnabled = builder.mBatterySaverEnabled;
gpsMode = builder.mGpsMode;
brightnessFactor = builder.mBrightnessFactor;
+ globalBatterySaverEnabled = builder.mGlobalBatterySaverEnabled;
}
public PowerSaveState(Parcel in) {
batterySaverEnabled = in.readByte() != 0;
+ globalBatterySaverEnabled = in.readByte() != 0;
gpsMode = in.readInt();
brightnessFactor = in.readFloat();
}
@@ -48,12 +60,14 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (batterySaverEnabled ? 1 : 0));
+ dest.writeByte((byte) (globalBatterySaverEnabled ? 1 : 0));
dest.writeInt(gpsMode);
dest.writeFloat(brightnessFactor);
}
public static final class Builder {
private boolean mBatterySaverEnabled = false;
+ private boolean mGlobalBatterySaverEnabled = false;
private int mGpsMode = 0;
private float mBrightnessFactor = 0.5f;
@@ -64,6 +78,11 @@
return this;
}
+ public Builder setGlobalBatterySaverEnabled(boolean enabled) {
+ mGlobalBatterySaverEnabled = enabled;
+ return this;
+ }
+
public Builder setGpsMode(int mode) {
mGpsMode = mode;
return this;
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 9e56e01..fa90384 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -594,6 +594,15 @@
public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
/**
+ * Flag indicating that this root can be ejected.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#ejectRoot(ContentResolver, Uri)
+ * @see DocumentsProvider#ejectRoot(String)
+ */
+ public static final int FLAG_SUPPORTS_EJECT = 1 << 5;
+
+ /**
* Flag indicating that this root is currently empty. This may be used
* to hide the root when opening documents, but the root will still be
* shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
@@ -641,9 +650,6 @@
* @hide
*/
public static final int FLAG_REMOVABLE_USB = 1 << 20;
-
- /** {@hide} */
- public static final int FLAG_SUPPORTS_EJECT = 1 << 21;
}
/**
@@ -1345,35 +1351,30 @@
client.call(METHOD_REMOVE_DOCUMENT, null, in);
}
- /** {@hide} */
- public static boolean ejectRoot(ContentResolver resolver, Uri rootUri) {
+ /**
+ * Ejects the given root. It throws {@link IllegalStateException} when ejection failed.
+ *
+ * @param rootUri root with {@link Root#FLAG_SUPPORTS_EJECT} to be ejected
+ */
+ public static void ejectRoot(ContentResolver resolver, Uri rootUri) {
final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
rootUri.getAuthority());
try {
- return ejectRoot(client, rootUri);
- } catch (Exception e) {
- Log.w(TAG, "Failed to eject root", e);
- return false;
+ ejectRoot(client, rootUri);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
} finally {
ContentProviderClient.releaseQuietly(client);
}
}
/** {@hide} */
- public static boolean ejectRoot(ContentProviderClient client, Uri rootUri)
+ public static void ejectRoot(ContentProviderClient client, Uri rootUri)
throws RemoteException {
final Bundle in = new Bundle();
in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
- final Bundle out = client.call(METHOD_EJECT_ROOT, null, in);
-
- if (out == null) {
- throw new RemoteException("Failed to get a reponse from ejectRoot.");
- }
- if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
- throw new RemoteException("Response did not include result field..");
- }
- return out.getBoolean(DocumentsContract.EXTRA_RESULT);
+ client.call(METHOD_EJECT_ROOT, null, in);
}
/**
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 9e68afb..2a83c4b 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -616,9 +616,14 @@
throw new UnsupportedOperationException("Search not supported");
}
- /** {@hide} */
+ /**
+ * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
+ *
+ * @param rootId the root to be ejected.
+ * @see Root#FLAG_SUPPORTS_EJECT
+ */
@SuppressWarnings("unused")
- public boolean ejectRoot(String rootId) {
+ public void ejectRoot(String rootId) {
throw new UnsupportedOperationException("Eject not supported");
}
@@ -947,14 +952,12 @@
if (METHOD_EJECT_ROOT.equals(method)) {
// Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
// signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
- // MANAGE_DOCUMENTS here instead
- getContext().enforceCallingPermission(
- android.Manifest.permission.MANAGE_DOCUMENTS, null);
+ // MANAGE_DOCUMENTS or associated URI permission here instead
final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
- final String rootId = DocumentsContract.getRootId(rootUri);
- final boolean ejected = ejectRoot(rootId);
+ enforceWritePermissionInner(rootUri, getCallingPackage(), null);
- out.putBoolean(DocumentsContract.EXTRA_RESULT, ejected);
+ final String rootId = DocumentsContract.getRootId(rootUri);
+ ejectRoot(rootId);
return out;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 146d2d3..58595c2 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5482,6 +5482,20 @@
public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
/**
+ * Setting specifying if the accessibility shortcut is enabled.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_ENABLED =
+ "accessibility_shortcut_enabled";
+
+ /**
+ * Setting specifying if the accessibility shortcut is enabled.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN =
+ "accessibility_shortcut_on_lock_screen";
+
+ /**
* Setting specifying if the accessibility shortcut dialog has been shown to this user.
* @hide
*/
@@ -5489,7 +5503,7 @@
"accessibility_shortcut_dialog_shown";
/**
- * Setting specifying the the accessibility service to be toggled via the accessibility
+ * Setting specifying the accessibility service to be toggled via the accessibility
* shortcut. Must be its flattened {@link ComponentName}.
* @hide
*/
@@ -6984,6 +6998,8 @@
ACCESSIBILITY_ENABLED,
ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ ACCESSIBILITY_SHORTCUT_ENABLED,
+ ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
ACCESSIBILITY_SPEAK_PASSWORD,
ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
ACCESSIBILITY_CAPTIONING_PRESET,
@@ -9741,6 +9757,16 @@
public static final String DATABASE_DOWNGRADE_REASON = "database_downgrade_reason";
/**
+ * The build id of when the settings database was first created (or re-created due it
+ * being missing).
+ *
+ * Type: string
+ *
+ * @hide
+ */
+ public static final String DATABASE_CREATION_BUILDID = "database_creation_buildid";
+
+ /**
* Flag to toggle journal mode WAL on or off for the contacts database. WAL is enabled by
* default. Set to 0 to disable.
*
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index fce06d6d..6eea07d 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -73,5 +73,17 @@
* currently, else return the display id of the virtual display
*/
int getCompatibilityDisplayId();
+
+ /**
+ * Initiate connection for system controller data.
+ *
+ * @param fd Controller data file descriptor.
+ */
+ void connectController(in FileDescriptor fd);
+
+ /**
+ * Sever connection for system controller data.
+ */
+ void disconnectController();
}
diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java
index 58ad2fd..9ba0f5d 100644
--- a/core/java/android/util/LongSparseArray.java
+++ b/core/java/android/util/LongSparseArray.java
@@ -315,6 +315,7 @@
* and that multiple keys can map to the same value and this will
* find only one of them.
* <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
+ * @hide
*/
public int indexOfValueByValue(E value) {
if (mGarbage) {
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index c766660..b3400ef 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -363,6 +363,7 @@
* and that multiple keys can map to the same value and this will
* find only one of them.
* <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
+ * @hide
*/
public int indexOfValueByValue(E value) {
if (mGarbage) {
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index f9d7332..f78d622 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -22,6 +22,7 @@
import android.os.SystemService;
import android.os.ZygoteProcess;
import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -53,6 +54,13 @@
private static ZygoteProcess sZygote;
/**
+ * Variable that allows us to determine whether the WebView zygote Service has already been
+ * started.
+ */
+ @GuardedBy("sLock")
+ private static boolean sStartedService = false;
+
+ /**
* Information about the selected WebView package. This is set from #onWebViewProviderChanged().
*/
@GuardedBy("sLock")
@@ -67,7 +75,9 @@
public static ZygoteProcess getProcess() {
synchronized (sLock) {
- connectToZygoteIfNeededLocked();
+ if (sZygote != null) return sZygote;
+
+ waitForServiceStartAndConnect();
return sZygote;
}
}
@@ -95,17 +105,20 @@
final String serviceName = getServiceNameLocked();
if (serviceName == null) return;
- if (enabled && sZygote == null) {
- SystemService.start(serviceName);
+ if (enabled) {
+ if (!sStartedService) {
+ SystemService.start(serviceName);
+ sStartedService = true;
+ }
} else {
SystemService.stop(serviceName);
+ sStartedService = false;
sZygote = null;
}
}
}
public static void onWebViewProviderChanged(PackageInfo packageInfo) {
- String serviceName;
synchronized (sLock) {
sPackage = packageInfo;
@@ -114,7 +127,7 @@
return;
}
- serviceName = getServiceNameLocked();
+ final String serviceName = getServiceNameLocked();
sZygote = null;
// The service may enter the RUNNING state before it opens the socket,
@@ -124,14 +137,28 @@
} else {
SystemService.restart(serviceName);
}
+ sStartedService = true;
+ }
+ }
- try {
- SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
- } catch (TimeoutException e) {
- Log.e(LOGTAG, "Timed out waiting for " + serviceName);
- return;
- }
+ private static void waitForServiceStartAndConnect() {
+ if (!sStartedService) {
+ throw new AndroidRuntimeException("Tried waiting for the WebView Zygote Service to " +
+ "start running without first starting the service.");
+ }
+ String serviceName;
+ synchronized (sLock) {
+ serviceName = getServiceNameLocked();
+ }
+ try {
+ SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
+ } catch (TimeoutException e) {
+ Log.e(LOGTAG, "Timed out waiting for " + serviceName);
+ return;
+ }
+
+ synchronized (sLock) {
connectToZygoteIfNeededLocked();
}
}
@@ -151,8 +178,9 @@
@GuardedBy("sLock")
private static void connectToZygoteIfNeededLocked() {
- if (sZygote != null)
+ if (sZygote != null) {
return;
+ }
if (sPackage == null) {
Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index fae5742..8f662ba 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -498,18 +498,30 @@
public void onProvideAutofillStructure(ViewStructure structure, int flags) {
super.onProvideAutofillStructure(structure, flags);
- if (getAdapter() == null) return;
+ final SpinnerAdapter adapter = getAdapter();
+
+ if (adapter == null) return;
// TODO(b/33197203): implement sanitization so initial value is only sanitized when coming
// from resources.
- final int count = getAdapter().getCount();
+ final int count = adapter.getCount();
+ int size = 0;
if (count > 0) {
final String[] options = new String[count];
for (int i = 0; i < count; i++) {
- options[i] = getAdapter().getItem(i).toString();
+ final Object item = adapter.getItem(i);
+ if (item != null) {
+ options[size++] = item.toString();
+ }
}
- structure.setAutofillOptions(options);
+ if (size == count) {
+ structure.setAutofillOptions(options);
+ } else {
+ final String[] validOptions = new String[size];
+ System.arraycopy(options, 0, validOptions, 0, size);
+ structure.setAutofillOptions(validOptions);
+ }
}
}
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 7b99d07..78d18fd 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -75,6 +75,7 @@
private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
private boolean mDropDownVerticalOffsetSet;
private boolean mIsAnimatedFromAnchor = true;
+ private boolean mOverlapAnchor;
private int mDropDownGravity = Gravity.NO_GRAVITY;
@@ -672,6 +673,7 @@
mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
mPopup.setTouchInterceptor(mTouchInterceptor);
mPopup.setEpicenterBounds(mEpicenterBounds);
+ mPopup.setOverlapAnchor(mOverlapAnchor);
mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
@@ -1245,6 +1247,13 @@
return listContent + otherHeights;
}
+ /**
+ * @hide
+ */
+ public void setOverlapAnchor(boolean overlap) {
+ mOverlapAnchor = overlap;
+ }
+
private class PopupDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 26b3ae2..9f10531 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -55,6 +55,7 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.WindowManagerGlobal;
import com.android.internal.R;
@@ -137,6 +138,7 @@
private final int[] mTmpDrawingLocation = new int[2];
private final int[] mTmpScreenLocation = new int[2];
+ private final int[] mTmpAppLocation = new int[2];
private final Rect mTempRect = new Rect();
private Context mContext;
@@ -242,6 +244,9 @@
private final OnScrollChangedListener mOnScrollChangedListener = this::alignToAnchor;
+ private final View.OnLayoutChangeListener mOnLayoutChangeListener =
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> alignToAnchor();
+
private int mAnchorXoff;
private int mAnchorYoff;
private int mAnchoredGravity;
@@ -1238,7 +1243,8 @@
mIsShowing = true;
mIsDropdown = true;
- final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
+ final WindowManager.LayoutParams p =
+ createPopupLayoutParams(anchor.getApplicationWindowToken());
preparePopup(p);
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
@@ -1547,13 +1553,21 @@
}
// Initially, align to the bottom-left corner of the anchor plus offsets.
+ final int[] appScreenLocation = mTmpAppLocation;
+ final View appRootView = getAppRootView(anchor);
+ appRootView.getLocationOnScreen(appScreenLocation);
+
+ final int[] screenLocation = mTmpScreenLocation;
+ anchor.getLocationOnScreen(screenLocation);
+
final int[] drawingLocation = mTmpDrawingLocation;
- anchor.getLocationInWindow(drawingLocation);
+ drawingLocation[0] = screenLocation[0] - appScreenLocation[0];
+ drawingLocation[1] = screenLocation[1] - appScreenLocation[1];
outParams.x = drawingLocation[0] + xOffset;
outParams.y = drawingLocation[1] + anchorHeight + yOffset;
final Rect displayFrame = new Rect();
- anchor.getWindowVisibleDisplayFrame(displayFrame);
+ appRootView.getWindowVisibleDisplayFrame(displayFrame);
if (width == MATCH_PARENT) {
width = displayFrame.right - displayFrame.left;
}
@@ -1574,9 +1588,6 @@
outParams.x -= width - anchorWidth;
}
- final int[] screenLocation = mTmpScreenLocation;
- anchor.getLocationOnScreen(screenLocation);
-
// First, attempt to fit the popup vertically without resizing.
final boolean fitsVertical = tryFitVertical(outParams, yOffset, height,
anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top,
@@ -1595,7 +1606,9 @@
scrollY + height + anchorHeight + yOffset);
if (allowScroll && anchor.requestRectangleOnScreen(r, true)) {
// Reset for the new anchor position.
- anchor.getLocationInWindow(drawingLocation);
+ anchor.getLocationOnScreen(screenLocation);
+ drawingLocation[0] = screenLocation[0] - appScreenLocation[0];
+ drawingLocation[1] = screenLocation[1] - appScreenLocation[1];
outParams.x = drawingLocation[0] + xOffset;
outParams.y = drawingLocation[1] + anchorHeight + yOffset;
@@ -1793,7 +1806,8 @@
Rect displayFrame = null;
final Rect visibleDisplayFrame = new Rect();
- anchor.getWindowVisibleDisplayFrame(visibleDisplayFrame);
+ final View appView = getAppRootView(anchor);
+ appView.getWindowVisibleDisplayFrame(visibleDisplayFrame);
if (ignoreBottomDecorations) {
// In the ignore bottom decorations case we want to
// still respect all other decorations so we use the inset visible
@@ -2240,6 +2254,7 @@
final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
if (anchorRoot != null) {
anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+ anchorRoot.removeOnLayoutChangeListener(mOnLayoutChangeListener);
}
mAnchor = null;
@@ -2258,6 +2273,7 @@
final View anchorRoot = anchor.getRootView();
anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+ anchorRoot.addOnLayoutChangeListener(mOnLayoutChangeListener);
mAnchor = new WeakReference<>(anchor);
mAnchorRoot = new WeakReference<>(anchorRoot);
@@ -2281,6 +2297,15 @@
}
}
+ private View getAppRootView(View anchor) {
+ final View appWindowView = WindowManagerGlobal.getInstance().getWindowView(
+ anchor.getApplicationWindowToken());
+ if (appWindowView != null) {
+ return appWindowView;
+ }
+ return anchor.getRootView();
+ }
+
private class PopupDecorView extends FrameLayout {
private TransitionListenerAdapter mPendingExitListener;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 8d0ea08..bc7c79d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2031,6 +2031,8 @@
Typeface tf = fontTypeface;
if (tf == null && familyName != null) {
tf = Typeface.create(familyName, styleIndex);
+ } else if (tf != null && tf.getStyle() != styleIndex) {
+ tf = Typeface.create(tf, styleIndex);
}
if (tf != null) {
setTypeface(tf);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9d2141d..3d0d6bf 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -482,7 +482,8 @@
new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS];
int mBluetoothScanNesting;
- StopwatchTimer mBluetoothScanTimer;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected StopwatchTimer mBluetoothScanTimer;
int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
long mMobileRadioActiveStartTime;
@@ -1589,19 +1590,28 @@
long mStartTimeMs = -1;
/**
- * The longest time period (in ms) that the timer has been active.
+ * The longest time period (in ms) that the timer has been active. Not pooled.
*/
long mMaxDurationMs;
/**
- * The total time (in ms) that that the timer has been active since reset().
+ * The time (in ms) that that the timer has been active since most recent
+ * stopRunningLocked() or reset(). Not pooled.
*/
long mCurrentDurationMs;
+ /**
+ * The total time (in ms) that that the timer has been active since most recent reset()
+ * prior to the current startRunningLocked. This is the sum of all past currentDurations
+ * (but not including the present currentDuration) since reset. Not pooled.
+ */
+ long mTotalDurationMs;
+
public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
TimeBase timeBase, Parcel in) {
super(clocks, uid, type, timerPool, timeBase, in);
mMaxDurationMs = in.readLong();
+ mTotalDurationMs = in.readLong();
}
public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
@@ -1613,6 +1623,7 @@
public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
super.writeToParcel(out, elapsedRealtimeUs);
out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000));
+ out.writeLong(getTotalDurationMsLocked(elapsedRealtimeUs / 1000));
}
/**
@@ -1620,12 +1631,13 @@
*
* Since the time base is probably meaningless after we come back, reading
* from this will have the effect of stopping the timer. So here all we write
- * is the max duration.
+ * is the max and total durations.
*/
@Override
public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000));
+ out.writeLong(getTotalDurationMsLocked(elapsedRealtimeUs / 1000));
}
/**
@@ -1637,6 +1649,7 @@
public void readSummaryFromParcelLocked(Parcel in) {
super.readSummaryFromParcelLocked(in);
mMaxDurationMs = in.readLong();
+ mTotalDurationMs = in.readLong();
mStartTimeMs = -1;
mCurrentDurationMs = 0;
}
@@ -1692,6 +1705,7 @@
public void stopRunningLocked(long elapsedRealtimeMs) {
if (mNesting == 1) {
final long durationMs = getCurrentDurationMsLocked(elapsedRealtimeMs);
+ mTotalDurationMs += durationMs;
if (durationMs > mMaxDurationMs) {
mMaxDurationMs = durationMs;
}
@@ -1707,6 +1721,7 @@
public boolean reset(boolean detachIfReset) {
boolean result = super.reset(detachIfReset);
mMaxDurationMs = 0;
+ mTotalDurationMs = 0;
mCurrentDurationMs = 0;
if (mNesting > 0) {
mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000;
@@ -1735,6 +1750,7 @@
/**
* Returns the time since the timer was started.
+ * Returns 0 if the timer is not currently running.
*
* Note that this time is NOT split between the timers in the timer group that
* this timer is attached to. It is the TOTAL time.
@@ -1748,6 +1764,20 @@
}
return durationMs;
}
+
+ /**
+ * Returns the total cumulative duration that this timer has been on since reset().
+ * If mTimerPool == null, this should be the same
+ * as getTotalTimeLocked(elapsedRealtimeMs*1000, STATS_SINCE_CHARGED)/1000.
+ *
+ * Note that this time is NOT split between the timers in the timer group that
+ * this timer is attached to. It is the TOTAL time. For this reason, if mTimerPool != null,
+ * the result will not be equivalent to getTotalTimeLocked.
+ */
+ @Override
+ public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
+ return mTotalDurationMs + getCurrentDurationMsLocked(elapsedRealtimeMs);
+ }
}
/**
@@ -1972,6 +2002,116 @@
}
}
+ /**
+ * State for keeping track of two DurationTimers with different TimeBases, presumably where one
+ * TimeBase is effectively a subset of the other.
+ */
+ public static class DualTimer {
+ // mMainTimer typically tracks the total time. May be pooled (but since it's a durationTimer,
+ // it also has the unpooled getTotalDurationMsLocked() for STATS_SINCE_CHARGED).
+ private final DurationTimer mMainTimer;
+ // mSubTimer typically tracks only part of the total time, such as background time, as
+ // determined by a subTimeBase. It is NOT pooled.
+ private final DurationTimer mSubTimer;
+
+ /**
+ * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
+ * The mMainTimer is based on the given timeBase and timerPool.
+ * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
+ * the mMainTimer is.
+ */
+ public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+ TimeBase timeBase, TimeBase subTimeBase, Parcel in) {
+ mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase, in);
+ mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase, in);
+ }
+
+ /**
+ * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
+ * The mMainTimer is based on the given timeBase and timerPool.
+ * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
+ * the mMainTimer is.
+ */
+ public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+ TimeBase timeBase, TimeBase subTimeBase) {
+ mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase);
+ mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase);
+ }
+
+ /** Get the main timer. */
+ public DurationTimer getMainTimer() {
+ return mMainTimer;
+ }
+
+ /** Get the secondary timer. */
+ public DurationTimer getSubTimer() {
+ return mSubTimer;
+ }
+
+ public void startRunningLocked(long elapsedRealtimeMs) {
+ mMainTimer.startRunningLocked(elapsedRealtimeMs);
+ mSubTimer.startRunningLocked(elapsedRealtimeMs);
+ }
+
+ public void stopRunningLocked(long elapsedRealtimeMs) {
+ mMainTimer.stopRunningLocked(elapsedRealtimeMs);
+ mSubTimer.stopRunningLocked(elapsedRealtimeMs);
+ }
+
+ public void stopAllRunningLocked(long elapsedRealtimeMs) {
+ mMainTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ mSubTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ }
+
+ public void setMark(long elapsedRealtimeMs) {
+ mMainTimer.setMark(elapsedRealtimeMs);
+ mSubTimer.setMark(elapsedRealtimeMs);
+ }
+
+ public boolean reset(boolean detachIfReset) {
+ boolean active = false;
+ active |= !mMainTimer.reset(detachIfReset);
+ active |= !mSubTimer.reset(detachIfReset);
+ return !active;
+ }
+
+ public void detach() {
+ mMainTimer.detach();
+ mSubTimer.detach();
+ }
+
+ /**
+ * Writes a possibly null DualTimer to a Parcel.
+ *
+ * @param out the Parcel to which to write.
+ * @param t a DualTimer, or null.
+ */
+ public static void writeDualTimerToParcel(Parcel out, DualTimer t, long elapsedRealtimeUs) {
+ if (t != null) {
+ out.writeInt(1);
+ t.writeToParcel(out, elapsedRealtimeUs);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ mMainTimer.writeToParcel(out, elapsedRealtimeUs);
+ mSubTimer.writeToParcel(out, elapsedRealtimeUs);
+ }
+
+ public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ mMainTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+ mSubTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+ }
+
+ public void readSummaryFromParcelLocked(Parcel in) {
+ mMainTimer.readSummaryFromParcelLocked(in);
+ mSubTimer.readSummaryFromParcelLocked(in);
+ }
+ }
+
+
public abstract class OverflowArrayMap<T> {
private static final String OVERFLOW_NAME = "*overflow*";
@@ -3149,7 +3289,13 @@
public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
long realtime) {
- mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
+ boolean batteryStatusChanged = mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
+
+ if (batteryStatusChanged) {
+ for (int i=0; i<mUidStats.size(); i++) {
+ mUidStats.valueAt(i).updateBgTimeBase(uptime, realtime);
+ }
+ }
boolean unpluggedScreenOff = unplugged && screenOff;
if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) {
@@ -4499,8 +4645,8 @@
private void noteBluetoothScanStartedLocked(int uid) {
uid = mapUid(uid);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- final long uptime = SystemClock.uptimeMillis();
+ final long elapsedRealtime = mClocks.elapsedRealtime();
+ final long uptime = mClocks.uptimeMillis();
if (mBluetoothScanNesting == 0) {
mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: "
@@ -4521,8 +4667,8 @@
private void noteBluetoothScanStoppedLocked(int uid) {
uid = mapUid(uid);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- final long uptime = SystemClock.uptimeMillis();
+ final long elapsedRealtime = mClocks.elapsedRealtime();
+ final long uptime = mClocks.uptimeMillis();
mBluetoothScanNesting--;
if (mBluetoothScanNesting == 0) {
mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
@@ -4543,8 +4689,8 @@
public void noteResetBluetoothScanLocked() {
if (mBluetoothScanNesting > 0) {
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- final long uptime = SystemClock.uptimeMillis();
+ final long elapsedRealtime = mClocks.elapsedRealtime();
+ final long uptime = mClocks.uptimeMillis();
mBluetoothScanNesting = 0;
mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: "
@@ -5218,6 +5364,13 @@
return true;
}
+ private static boolean resetTimerIfNotNull(DualTimer timer, boolean detachIfReset) {
+ if (timer != null) {
+ return timer.reset(detachIfReset);
+ }
+ return true;
+ }
+
private static void detachLongCounterIfNotNull(LongSamplingCounter counter) {
if (counter != null) {
counter.detach();
@@ -5242,6 +5395,10 @@
final int mUid;
+ /** TimeBase for when uid is in background and device is on battery. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public final TimeBase mOnBatteryBackgroundTimeBase;
+
boolean mWifiRunning;
StopwatchTimer mWifiRunningTimer;
@@ -5249,7 +5406,7 @@
StopwatchTimer mFullWifiLockTimer;
boolean mWifiScanStarted;
- StopwatchTimer mWifiScanTimer;
+ DualTimer mWifiScanTimer;
static final int NO_BATCHED_SCAN_STARTED = -1;
int mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
@@ -5263,7 +5420,7 @@
StopwatchTimer mFlashlightTurnedOnTimer;
StopwatchTimer mCameraTurnedOnTimer;
StopwatchTimer mForegroundActivityTimer;
- StopwatchTimer mBluetoothScanTimer;
+ DualTimer mBluetoothScanTimer;
int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
StopwatchTimer[] mProcessStateTimer;
@@ -5357,6 +5514,10 @@
mBsi = bsi;
mUid = uid;
+ mOnBatteryBackgroundTimeBase = new TimeBase();
+ mOnBatteryBackgroundTimeBase.init(mBsi.mClocks.uptimeMillis() * 1000,
+ mBsi.mClocks.elapsedRealtime() * 1000);
+
mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
mCpuPower = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
@@ -5383,8 +5544,8 @@
mBsi.mWifiRunningTimers, mBsi.mOnBatteryTimeBase);
mFullWifiLockTimer = new StopwatchTimer(mBsi.mClocks, this, FULL_WIFI_LOCK,
mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
- mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_SCAN,
- mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase);
+ mWifiScanTimer = new DualTimer(mBsi.mClocks, this, WIFI_SCAN,
+ mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS];
mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_MULTICAST_ENABLED,
mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase);
@@ -5471,8 +5632,9 @@
if (!mWifiScanStarted) {
mWifiScanStarted = true;
if (mWifiScanTimer == null) {
- mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
- mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase);
+ mWifiScanTimer = new DualTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
+ mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase,
+ mOnBatteryBackgroundTimeBase);
}
mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
}
@@ -5679,10 +5841,11 @@
return mForegroundActivityTimer;
}
- public StopwatchTimer createBluetoothScanTimerLocked() {
+ public DualTimer createBluetoothScanTimerLocked() {
if (mBluetoothScanTimer == null) {
- mBluetoothScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
- mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase);
+ mBluetoothScanTimer = new DualTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
+ mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase,
+ mOnBatteryBackgroundTimeBase);
}
return mBluetoothScanTimer;
}
@@ -5755,7 +5918,7 @@
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ return mWifiScanTimer.getMainTimer().getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
@@ -5763,7 +5926,33 @@
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getCountLocked(which);
+ return mWifiScanTimer.getMainTimer().getCountLocked(which);
+ }
+
+ @Override
+ public int getWifiScanBackgroundCount(int which) {
+ if (mWifiScanTimer == null) {
+ return 0;
+ }
+ return mWifiScanTimer.getSubTimer().getCountLocked(which);
+ }
+
+ @Override
+ public long getWifiScanActualTime(final long elapsedRealtimeUs) {
+ if (mWifiScanTimer == null) {
+ return 0;
+ }
+ final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
+ return mWifiScanTimer.getMainTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
+ }
+
+ @Override
+ public long getWifiScanBackgroundTime(final long elapsedRealtimeUs) {
+ if (mWifiScanTimer == null) {
+ return 0;
+ }
+ final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
+ return mWifiScanTimer.getSubTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
}
@Override
@@ -5819,7 +6008,18 @@
@Override
public Timer getBluetoothScanTimer() {
- return mBluetoothScanTimer;
+ if (mBluetoothScanTimer == null) {
+ return null;
+ }
+ return mBluetoothScanTimer.getMainTimer();
+ }
+
+ @Override
+ public Timer getBluetoothScanBackgroundTimer() {
+ if (mBluetoothScanTimer == null) {
+ return null;
+ }
+ return mBluetoothScanTimer.getSubTimer();
}
void makeProcessState(int i, Parcel in) {
@@ -6216,6 +6416,9 @@
mLastStepUserTime = mLastStepSystemTime = 0;
mCurStepUserTime = mCurStepSystemTime = 0;
+ mOnBatteryBackgroundTimeBase.reset(mBsi.mClocks.elapsedRealtime() * 1000,
+ mBsi.mClocks.uptimeMillis() * 1000);
+
if (!active) {
if (mWifiRunningTimer != null) {
mWifiRunningTimer.detach();
@@ -6307,7 +6510,9 @@
return !active;
}
- void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ void writeToParcelLocked(Parcel out, long uptimeUs, long elapsedRealtimeUs) {
+ mOnBatteryBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
+
final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap();
int NW = wakeStats.size();
out.writeInt(NW);
@@ -6525,6 +6730,8 @@
}
void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
+ mOnBatteryBackgroundTimeBase.readFromParcel(in);
+
int numWakelocks = in.readInt();
mWakelockStats.clear();
for (int j = 0; j < numWakelocks; j++) {
@@ -6559,7 +6766,8 @@
for (int k = 0; k < numSensors; k++) {
int sensorNumber = in.readInt();
Uid.Sensor sensor = new Sensor(mBsi, this, sensorNumber);
- sensor.readFromParcelLocked(mBsi.mOnBatteryTimeBase, in);
+ sensor.readFromParcelLocked(mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase,
+ in);
mSensorStats.put(sensorNumber, sensor);
}
@@ -6597,8 +6805,9 @@
}
mWifiScanStarted = false;
if (in.readInt() != 0) {
- mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
- mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, in);
+ mWifiScanTimer = new DualTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
+ mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase,
+ in);
} else {
mWifiScanTimer = null;
}
@@ -6648,8 +6857,9 @@
mForegroundActivityTimer = null;
}
if (in.readInt() != 0) {
- mBluetoothScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
- mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase, in);
+ mBluetoothScanTimer = new DualTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
+ mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase,
+ mOnBatteryBackgroundTimeBase, in);
} else {
mBluetoothScanTimer = null;
}
@@ -6946,14 +7156,12 @@
protected BatteryStatsImpl mBsi;
/**
- * BatteryStatsImpl that we are associated with.
+ * Uid that we are associated with.
*/
protected Uid mUid;
final int mHandle;
- StopwatchTimer mTimer;
-
- Counter mBgCounter;
+ DualTimer mTimer;
public Sensor(BatteryStatsImpl bsi, Uid uid, int handle) {
mBsi = bsi;
@@ -6961,7 +7169,8 @@
mHandle = handle;
}
- private StopwatchTimer readTimerFromParcel(TimeBase timeBase, Parcel in) {
+ private DualTimer readTimersFromParcel(
+ TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
if (in.readInt() == 0) {
return null;
}
@@ -6971,23 +7180,10 @@
pool = new ArrayList<StopwatchTimer>();
mBsi.mSensorTimers.put(mHandle, pool);
}
- return new StopwatchTimer(mBsi.mClocks, mUid, 0, pool, timeBase, in);
- }
-
- private Counter readCounterFromParcel(TimeBase timeBase, Parcel in) {
- if (in.readInt() == 0) {
- return null;
- }
- return new Counter(timeBase, in);
+ return new DualTimer(mBsi.mClocks, mUid, 0, pool, timeBase, bgTimeBase, in);
}
boolean reset() {
- if (mBgCounter != null) {
- mBgCounter.reset(true /*detachIfReset*/);
- // If we detach, we must null the mBgCounter reference so that it
- // can be recreated and attached.
- mBgCounter = null;
- }
if (mTimer.reset(true)) {
mTimer = null;
return true;
@@ -6995,24 +7191,28 @@
return false;
}
- void readFromParcelLocked(TimeBase timeBase, Parcel in) {
- mTimer = readTimerFromParcel(timeBase, in);
- mBgCounter = readCounterFromParcel(timeBase, in);
+ void readFromParcelLocked(TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
+ mTimer = readTimersFromParcel(timeBase, bgTimeBase, in);
}
void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
- Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs);
- Counter.writeCounterToParcel(out, mBgCounter);
+ DualTimer.writeDualTimerToParcel(out, mTimer, elapsedRealtimeUs);
}
@Override
public Timer getSensorTime() {
- return mTimer;
+ if (mTimer == null) {
+ return null;
+ }
+ return mTimer.getMainTimer();
}
@Override
- public Counter getSensorBgCount() {
- return mBgCounter;
+ public Timer getSensorBackgroundTime() {
+ if (mTimer == null) {
+ return null;
+ }
+ return mTimer.getSubTimer();
}
@Override
@@ -7749,18 +7949,29 @@
if (mProcessState == uidRunningState) return;
- final long elapsedRealtime = mBsi.mClocks.elapsedRealtime();
+ final long elapsedRealtimeMs = mBsi.mClocks.elapsedRealtime();
+ final long uptimeMs = mBsi.mClocks.uptimeMillis();
if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
- mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtime);
+ mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
}
mProcessState = uidRunningState;
if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
if (mProcessStateTimer[uidRunningState] == null) {
makeProcessState(uidRunningState, null);
}
- mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtime);
+ mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
}
+
+ updateBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
+ }
+
+ public boolean updateBgTimeBase(long uptimeUs, long realtimeUs) {
+ // Note that PROCESS_STATE_CACHED and ActivityManager.PROCESS_STATE_NONEXISTENT is
+ // also considered to be 'background' for our purposes, because it's not foreground.
+ boolean isBgAndUnplugged = mBsi.mOnBatteryTimeBase.isRunning()
+ && mProcessState >= PROCESS_STATE_BACKGROUND;
+ return mOnBatteryBackgroundTimeBase.setRunning(isBgAndUnplugged, uptimeUs, realtimeUs);
}
public SparseArray<? extends Pid> getPidStats() {
@@ -7834,7 +8045,7 @@
}
}
- public StopwatchTimer getSensorTimerLocked(int sensor, boolean create) {
+ public DualTimer getSensorTimerLocked(int sensor, boolean create) {
Sensor se = mSensorStats.get(sensor);
if (se == null) {
if (!create) {
@@ -7843,7 +8054,7 @@
se = new Sensor(mBsi, this, sensor);
mSensorStats.put(sensor, se);
}
- StopwatchTimer t = se.mTimer;
+ DualTimer t = se.mTimer;
if (t != null) {
return t;
}
@@ -7852,28 +8063,12 @@
timers = new ArrayList<StopwatchTimer>();
mBsi.mSensorTimers.put(sensor, timers);
}
- t = new StopwatchTimer(mBsi.mClocks, this, BatteryStats.SENSOR, timers,
- mBsi.mOnBatteryTimeBase);
+ t = new DualTimer(mBsi.mClocks, this, BatteryStats.SENSOR, timers,
+ mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
se.mTimer = t;
return t;
}
- public Counter getSensorBgCounterLocked(int sensor, boolean create) {
- Sensor se = mSensorStats.get(sensor);
- if (se == null) {
- if (!create) {
- return null;
- }
- se = new Sensor(mBsi, this, sensor);
- mSensorStats.put(sensor, se);
- }
- Counter c = se.mBgCounter;
- if (c != null) return c;
- c = new Counter(mBsi.mOnBatteryTimeBase);
- se.mBgCounter = c;
- return c;
- }
-
public void noteStartSyncLocked(String name, long elapsedRealtimeMs) {
StopwatchTimer t = mSyncStats.startObject(name);
if (t != null) {
@@ -7946,39 +8141,24 @@
}
public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
- StopwatchTimer t = getSensorTimerLocked(sensor, /* create= */ true);
+ DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
t.startRunningLocked(elapsedRealtimeMs);
-
- Counter c = getSensorBgCounterLocked(sensor, /* create= */ true);
- if (mProcessState >= PROCESS_STATE_BACKGROUND && t.mNesting == 1) {
- c.stepAtomic();
- }
}
public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
// Don't create a timer if one doesn't already exist
- StopwatchTimer t = getSensorTimerLocked(sensor, false);
+ DualTimer t = getSensorTimerLocked(sensor, false);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
}
}
public void noteStartGps(long elapsedRealtimeMs) {
- StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, /* create= */ true);
- t.startRunningLocked(elapsedRealtimeMs);
-
- Counter c = getSensorBgCounterLocked(Sensor.GPS, /* create= */ true);
- if (mProcessState >= PROCESS_STATE_BACKGROUND && t.mNesting == 1) {
- c.stepAtomic();
- }
+ noteStartSensor(Sensor.GPS, elapsedRealtimeMs);
}
public void noteStopGps(long elapsedRealtimeMs) {
- // Don't create a timer if one doesn't already exist
- StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false);
- if (t != null) {
- t.stopRunningLocked(elapsedRealtimeMs);
- }
+ noteStopSensor(Sensor.GPS, elapsedRealtimeMs);
}
public BatteryStatsImpl getBatteryStats() {
@@ -8969,7 +9149,7 @@
final Uid uid = mUidStats.valueAt(i);
// Sum the total scan power for all apps.
- totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
+ totalScanTimeMs += uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
// Sum the total time holding wifi lock for all apps.
@@ -8990,7 +9170,7 @@
for (int i = 0; i < uidStatsSize; i++) {
final Uid uid = mUidStats.valueAt(i);
- long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
+ long scanTimeSinceMarkMs = uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
@@ -9244,7 +9424,7 @@
mHasBluetoothReporting = true;
- final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+ final long elapsedRealtimeMs = mClocks.elapsedRealtime();
final long rxTimeMs = info.getControllerRxTimeMillis();
final long txTimeMs = info.getControllerTxTimeMillis();
@@ -9264,7 +9444,7 @@
continue;
}
- totalScanTimeMs += u.mBluetoothScanTimer.getTimeSinceMarkLocked(
+ totalScanTimeMs += u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
}
@@ -9285,7 +9465,7 @@
continue;
}
- long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getTimeSinceMarkLocked(
+ long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
@@ -10791,6 +10971,8 @@
Uid u = new Uid(this, uid);
mUidStats.put(uid, u);
+ u.mOnBatteryBackgroundTimeBase.readSummaryFromParcel(in);
+
u.mWifiRunning = false;
if (in.readInt() != 0) {
u.mWifiRunningTimer.readSummaryFromParcelLocked(in);
@@ -10948,8 +11130,7 @@
for (int is = 0; is < NP; is++) {
int seNumber = in.readInt();
if (in.readInt() != 0) {
- u.getSensorTimerLocked(seNumber, true)
- .readSummaryFromParcelLocked(in);
+ u.getSensorTimerLocked(seNumber, true).readSummaryFromParcelLocked(in);
}
}
@@ -11155,6 +11336,8 @@
out.writeInt(mUidStats.keyAt(iu));
Uid u = mUidStats.valueAt(iu);
+ u.mOnBatteryBackgroundTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+
if (u.mWifiRunningTimer != null) {
out.writeInt(1);
u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
@@ -11738,7 +11921,7 @@
out.writeInt(mUidStats.keyAt(i));
Uid uid = mUidStats.valueAt(i);
- uid.writeToParcelLocked(out, uSecRealtime);
+ uid.writeToParcelLocked(out, uSecUptime, uSecRealtime);
}
} else {
out.writeInt(0);
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index ebcec5c..6dff8b4 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -381,6 +381,7 @@
if (parentView != null) {
// This menu is a cascading submenu anchored to a parent view.
+ popupWindow.setAnchorView(parentView);
popupWindow.setTouchModal(false);
popupWindow.setEnterTransition(null);
@@ -388,42 +389,30 @@
final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
mLastPosition = nextMenuPosition;
- // A popup anchored to mAnchorView with (0,0) offset would be shown at this position.
- final int[] offsetOrigin = new int[2];
- mAnchorView.getLocationOnScreen(offsetOrigin);
- offsetOrigin[1] += mAnchorView.getHeight();
-
- final int[] parentViewScreenLocation = new int[2];
- parentView.getLocationOnScreen(parentViewScreenLocation);
-
- // Translate the parent view location into the offset coordinate space.
- // If used as horizontal/vertical offsets, these values would position the submenu
- // at the exact same position as the parent item.
- final int parentOffsetLeft = parentViewScreenLocation[0] - offsetOrigin[0];
- final int parentOffsetTop = parentViewScreenLocation[1] - offsetOrigin[1];
-
- // Adjust the horizontal offset to display the submenu to the right or to the left
+ // Compute the horizontal offset to display the submenu to the right or to the left
// of the parent item.
// By now, mDropDownGravity is the resolved absolute gravity, so
// this should work in both LTR and RTL.
final int x;
if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
if (showOnRight) {
- x = parentOffsetLeft + menuWidth;
+ x = menuWidth;
} else {
- x = parentOffsetLeft - parentView.getWidth();
+ x = -parentView.getWidth();
}
} else {
if (showOnRight) {
- x = parentOffsetLeft + parentView.getWidth();
+ x = parentView.getWidth();
} else {
- x = parentOffsetLeft - menuWidth;
+ x = -menuWidth;
}
}
popupWindow.setHorizontalOffset(x);
- // Use the same vertical offset as the parent item.
- popupWindow.setVerticalOffset(parentOffsetTop);
+ // Align with the top edge of the parent view (or the bottom edge when the submenu is
+ // flipped vertically).
+ popupWindow.setOverlapAnchor(true);
+ popupWindow.setVerticalOffset(0);
} else {
if (mHasXOffset) {
popupWindow.setHorizontalOffset(mXOffset);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 373bda9..90aee9e 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -178,11 +178,7 @@
// Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
// use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
char subdir[PROP_VALUE_MAX];
- int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PERSIST_PROPERTY,
- subdir);
- if (len == 0) {
- len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
- }
+ int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
if (len > 0) {
String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir;
if (stat(overlayPath.string(), &st) == 0) {
diff --git a/core/res/res/values-ldrtl-television/config.xml b/core/res/res/values-ldrtl-television/config.xml
index 503b902..2a5a0c9 100644
--- a/core/res/res/values-ldrtl-television/config.xml
+++ b/core/res/res/values-ldrtl-television/config.xml
@@ -22,7 +22,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- The default gravity for the picture-in-picture window.
- Currently, this maps to Gravity.TOP | Gravity.LEFT -->
- <integer name="config_defaultPictureInPictureGravity">0x33</integer>
+ Currently, this maps to Gravity.BOTTOM | Gravity.LEFT -->
+ <integer name="config_defaultPictureInPictureGravity">0x53</integer>
</resources>
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 1987ac4..78eeee9 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -34,6 +34,6 @@
<string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">56x27</string>
<!-- The default gravity for the picture-in-picture window.
- Currently, this maps to Gravity.TOP | Gravity.RIGHT -->
- <integer name="config_defaultPictureInPictureGravity">0x35</integer>
+ Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
+ <integer name="config_defaultPictureInPictureGravity">0x55</integer>
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b9409f2..ed5a42b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1191,6 +1191,20 @@
<p>The default value is <code>false</code>. -->
<attr name="supportsPictureInPicture" format="boolean" />
+ <!-- This value indicates the maximum aspect ratio the activity supports. If the app runs on a
+ device with a wider aspect ratio, the system automatically letterboxes the app, leaving
+ portions of the screen unused so the app can run at its specified maximum aspect ratio.
+ <p>
+ Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+ form. For example, if the maximum aspect ratio is 7:3, set value to 2.33.
+ <p>
+ Value needs to be greater or equal to 1.0, otherwise it is ignored.
+ <p>
+ NOTE: This attribute is ignored if the activity has
+ {@link android.R.attr#resizeableActivity} set to true, since that means your activity
+ supports any size. -->
+ <attr name="maxAspectRatio" format="float" />
+
<!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
While in lockTask mode the system will not launch non-permitted tasks until
lockTask mode is disabled.
@@ -1408,6 +1422,7 @@
<attr name="defaultToDeviceProtectedStorage" format="boolean" />
<attr name="directBootAware" />
<attr name="resizeableActivity" />
+ <attr name="maxAspectRatio" />
<attr name="networkSecurityConfig" />
<!-- Declare the category of this app. Categories are used to cluster multiple apps
together into meaningful groups, such as when summarizing battery, network, or
@@ -2066,6 +2081,7 @@
<attr name="resumeWhilePausing" />
<attr name="resizeableActivity" />
<attr name="supportsPictureInPicture" />
+ <attr name="maxAspectRatio" />
<attr name="lockTaskMode" />
<attr name="showForAllUsers" />
<attr name="directBootAware" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 9205839..1129647 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -530,9 +530,4 @@
<dimen name="item_touch_helper_max_drag_scroll_per_frame">20dp</dimen>
<dimen name="item_touch_helper_swipe_escape_velocity">120dp</dimen>
<dimen name="item_touch_helper_swipe_escape_max_velocity">800dp</dimen>
-
- <!-- The elevation of AutoFill fill window-->
- <dimen name="autofill_fill_elevation">4dp</dimen>
- <dimen name="autofill_fill_item_height">64dp</dimen>
- <dimen name="autofill_fill_min_margin">16dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d6b5527..876d44d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2812,6 +2812,7 @@
<public name="fontProviderCerts" />
<public name="iconTint" />
<public name="iconTintMode" />
+ <public name="maxAspectRatio"/>
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d1c5900..566ba02 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -370,7 +370,7 @@
<string name="ssl_ca_cert_noti_by_unknown">By an unknown third party</string>
<!-- Content text for a notification. The Title of the notification is "ssl_ca_cert_warning".
This indicates that an unspecified administrator is doing the monitoring. [CHAR LIMIT=100]-->
- <string name="ssl_ca_cert_noti_by_administrator">By your work profile administrator</string>
+ <string name="ssl_ca_cert_noti_by_administrator">By your work profile admin</string>
<!-- Content text for a notification. The Title of the notification is "ssl_ca_cert_warning".
This indicates who is doing the monitoring. [CHAR LIMIT=100]-->
<string name="ssl_ca_cert_noti_managed">By <xliff:g id="managing_domain">%s</xliff:g></string>
@@ -381,15 +381,15 @@
<!-- Content text for a notification. The Title of the notification is "work_profile_deleted",
i.e. "Work profile deleted". This says that the profile is deleted by the system as a result of
the current profile owner gone missing. [CHAR LIMIT=100]-->
- <string name="work_profile_deleted_description">Work profile deleted due to missing admin app.</string>
+ <string name="work_profile_deleted_description">Work profile deleted due to missing admin app</string>
<!-- Content text for an expanded notification. The Title of the notification is "work_profile_deleted",
i.e. "Work profile deleted". This further explains that the profile is deleted by the system
as a result of the current profile admin gone missing. [CHAR LIMIT=NONE]-->
<string name="work_profile_deleted_details">The work profile admin app is either missing or corrupted.
- As a result, your work profile and related data have been deleted. Contact your administrator for assistance.</string>
+ As a result, your work profile and related data have been deleted. Contact your admin for assistance.</string>
<!-- Content text for a notification. The Title of the notification is "work_profile_deleted",
This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]-->
- <string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device.</string>
+ <string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device</string>
<!-- Content title for a notification. This notification indicates that the device is managed
and network logging was activated by a device owner. [CHAR LIMIT=NONE]-->
@@ -403,8 +403,8 @@
<string name="factory_reset_warning">Your device will be erased</string>
<!-- Text message in the factory reset warning dialog. This says that the the device admin app
is missing or corrupted. As a result the device will be erased. [CHAR LIMIT=NONE]-->
- <string name="factory_reset_message">The admin app is missing components or corrupted, and can\'t be used.
- Your device will now be erased. Contact your administrator for assistance.</string>
+ <string name="factory_reset_message">The admin app can\'t be used. Your device will now be
+ erased.\n\nIf you have questions, contact your organization's admin.</string>
<!-- Display name for any time a piece of data refers to the owner of the phone. For example, this could be used in place of the phone's phone number. -->
<string name="me">Me</string>
@@ -1460,7 +1460,7 @@
<!-- Description of policy access to limiting the user's password choices -->
<string name="policydesc_limitPassword">Control the length and the characters allowed in screen lock passwords and PINs.</string>
<!-- Title of policy access to watch user login attempts -->
- <string name="policylab_watchLogin">Monitor screen-unlock attempts</string>
+ <string name="policylab_watchLogin">Monitor screen unlock attempts</string>
<!-- Description of policy access to watch user login attempts -->
<string name="policydesc_watchLogin" product="tablet">Monitor the number of incorrect passwords
typed when unlocking the screen, and lock the tablet or erase all the tablet\'s
@@ -2385,7 +2385,7 @@
It is also used by the home screen's search "widget". It should be short -->
<string name="search_go">Search</string>
<!-- Default hint text for the system-wide search UI's text field. [CHAR LIMIT=30] -->
- <string name="search_hint">Search…</string>
+ <string name="search_hint">Search\u2026</string>
<!-- SearchView accessibility description for search button [CHAR LIMIT=NONE] -->
<string name="searchview_description_search">Search</string>
<!-- SearchView accessibility description for search text field [CHAR LIMIT=NONE] -->
@@ -3135,7 +3135,8 @@
<!-- Title of notification shown to indicate that bug report is still being collected after sharing was accepted. -->
<string name="sharing_remote_bugreport_notification_title">Sharing bug report\u2026</string>
<!-- Message of a notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
- <string name="share_remote_bugreport_notification_message_finished">Your IT admin requested a bug report to help troubleshoot this device. Apps and data may be shared.</string>
+ <string name="share_remote_bugreport_notification_message_finished">Your admin requested a bug
+ report to help troubleshoot this device. Apps and data may be shared.</string>
<!-- Acceptance label of notification shown to ask for user consent for sharing the remote bugreport. -->
<string name="share_remote_bugreport_action">SHARE</string>
<!-- Decline label of notification shown to ask for user consent for sharing the remote bugreport. -->
@@ -3928,22 +3929,24 @@
<!-- Dialog title for dialog shown when the accessibility shortcut is activated, and we want
to confirm that the user understands what's going to happen-->
- <string name="accessibility_shortcut_warning_dialog_title">Accessibility Shortcut is ON</string>
+ <string name="accessibility_shortcut_warning_dialog_title">Use Accessibility Shortcut?</string>
<!-- Message shown in dialog when user is in the process of enabling the accessibility
service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_toogle_warning">
- Turn <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> on or off by holding down
- both volume buttons for 3 seconds.\n\nYou can change the service in
- Settings > Accessibility.
+ When the shortcut is on, pressing both volume buttons for 3 seconds will start an
+ accessibility feature.\n\n
+ Current accessibility feature:\n
+ <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>\n\n
+ You can change the feature in Settings > Accessibility.
</string>
<!-- Text in button that turns off the accessibility shortcut -->
- <string name="disable_accessibility_shortcut">Turn Off Shortcut</string>
+ <string name="disable_accessibility_shortcut">Turn off Shortcut</string>
<!-- Text in button that closes the warning dialog about the accessibility shortcut, leaving the
shortcut enabled.-->
- <string name="leave_accessibility_shortcut_on">Leave on</string>
+ <string name="leave_accessibility_shortcut_on">Use Shortcut</string>
<!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility
service.-->
@@ -3966,7 +3969,7 @@
<!-- Error message title [CHAR LIMIT=35] -->
<string name="error_message_title">Error</string>
<!-- Message informing user that the change was disallowed by an administrator. [CHAR LIMIT=none] -->
- <string name="error_message_change_not_allowed">This change isn\'t allowed by your administrator</string>
+ <string name="error_message_change_not_allowed">This change isn\'t allowed by your admin</string>
<!-- Message informing user that the requested activity could not be found [CHAR LIMIT=none] -->
<string name="app_not_found">No application found to handle this action</string>
<string name="revoke">Revoke</string>
@@ -4169,7 +4172,7 @@
<string name="print_service_installed_message">Tap to enable</string>
<!-- PIN entry dialog title for entering the administrator PIN [CHAR LIMIT=none] -->
- <string name="restr_pin_enter_admin_pin">Enter administrator PIN</string>
+ <string name="restr_pin_enter_admin_pin">Enter admin PIN</string>
<!-- PIN entry dialog label/hint for PIN [CHAR LIMIT=none] -->
<string name="restr_pin_enter_pin">Enter PIN</string>
<!-- PIN entry dialog label/hint for incorrect PIN entry [CHAR LIMIT=none] -->
@@ -4255,9 +4258,11 @@
<string name="date_picker_day_typeface">sans-serif-medium</string>
<!-- Notify use that they are in Lock-to-app -->
- <string name="lock_to_app_toast">To unpin this screen, touch & hold Back and Overview.</string>
+ <string name="lock_to_app_toast">To unpin this screen, touch & hold Back and Overview
+ buttons</string>
+
<!-- Notify user that they are locked in lock-to-app mode -->
- <string name="lock_to_app_toast_locked">App is pinned: Unpinning isn\'t allowed on this device.</string>
+ <string name="lock_to_app_toast_locked">This app can\'t be unpinned</string>
<!-- Starting lock-to-app indication. -->
<string name="lock_to_app_start">Screen pinned</string>
<!-- Exting lock-to-app indication. -->
@@ -4271,11 +4276,11 @@
<string name="lock_to_app_unlock_password">Ask for password before unpinning</string>
<!-- Notification shown when device owner silently installs a package [CHAR LIMIT=NONE] -->
- <string name="package_installed_device_owner">Installed by your administrator</string>
+ <string name="package_installed_device_owner">Installed by your admin</string>
<!-- Notification shown when device owner silently updates a package [CHAR LIMIT=NONE] -->
- <string name="package_updated_device_owner">Updated by your administrator</string>
+ <string name="package_updated_device_owner">Updated by your admin</string>
<!-- Notification shown when device owner silently deletes a package [CHAR LIMIT=NONE] -->
- <string name="package_deleted_device_owner">Deleted by your administrator</string>
+ <string name="package_deleted_device_owner">Deleted by your admin</string>
<!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
<string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
@@ -4462,9 +4467,10 @@
<string name="locale_search_menu">Search</string>
<!-- Title for dialog displayed when work profile is turned off. [CHAR LIMIT=30] -->
- <string name="work_mode_off_title">Work mode is OFF</string>
+ <string name="work_mode_off_title">Turn on work mode?</string>
<!-- Message displayed in dialog when work profile is turned off. [CHAR LIMIT=NONE] -->
- <string name="work_mode_off_message">Allow work profile to function, including apps, background sync, and related features.</string>
+ <string name="work_mode_off_message">This will turn on your work profile, including apps,
+ background sync, and related features</string>
<!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
<string name="work_mode_turn_on">Turn on</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 11cde7a5..f4d490a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2848,9 +2848,8 @@
<java-symbol type="dimen" name="item_touch_helper_swipe_escape_max_velocity"/>
<!-- com.android.server.autofill -->
- <java-symbol type="dimen" name="autofill_fill_elevation" />
- <java-symbol type="dimen" name="autofill_fill_item_height" />
- <java-symbol type="dimen" name="autofill_fill_min_margin" />
+ <!-- TODO: floating_window_z temporary exposed until Autofill UI uses a ContextThemeWrapper -->
+ <java-symbol type="dimen" name="floating_window_z" />
<java-symbol type="layout" name="autofill_save"/>
<java-symbol type="layout" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 7deff06..9911d9d 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -790,6 +790,8 @@
<item name="colorAccent">@color/accent_device_default_light</item>
</style>
+ <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar"/>
+
<!-- Theme used for the intent picker activity. -->
<style name="Theme.DeviceDefault.Resolver" parent="Theme.Material.Light">
<item name="windowIsTranslucent">true</item>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 0cfdaf5..3e33dd8 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -141,6 +141,7 @@
Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+ Settings.Global.DATABASE_CREATION_BUILDID,
Settings.Global.DATABASE_DOWNGRADE_REASON,
Settings.Global.DATA_ROAMING,
Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
new file mode 100644
index 0000000..6b52b98
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 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 android.app.ActivityManager;
+import android.os.BatteryStats;
+import android.os.WorkSource;
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Test BatteryStatsImpl onBatteryBackgroundTimeBase TimeBase.
+ */
+public class BatteryStatsBackgroundStatsTest extends TestCase {
+
+ private static final int UID = 10500;
+
+ /** Test that BatteryStatsImpl.Uid.mOnBatteryBackgroundTimeBase works correctly. */
+ @SmallTest
+ public void testBgTimeBase() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long cur = 0; // realtime in us
+
+ BatteryStatsImpl.TimeBase bgtb = bi.getOnBatteryBackgroundTimeBase(UID);
+
+ // Off-battery, non-existent
+ clocks.realtime = clocks.uptime = 10;
+ cur = clocks.realtime * 1000;
+ bi.updateTimeBasesLocked(false, false, cur, cur); // off battery
+ assertFalse(bgtb.isRunning());
+ assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // Off-battery, foreground
+ clocks.realtime = clocks.uptime = 100;
+ cur = clocks.realtime * 1000;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ assertFalse(bgtb.isRunning());
+ assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // Off-battery, background
+ clocks.realtime = clocks.uptime = 201;
+ cur = clocks.realtime * 1000;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ assertFalse(bgtb.isRunning());
+ assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // On-battery, background
+ clocks.realtime = clocks.uptime = 303;
+ cur = clocks.realtime * 1000;
+ bi.updateTimeBasesLocked(true, false, cur, cur); // on battery
+ // still in ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ assertTrue(bgtb.isRunning());
+ assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // On-battery, background - but change screen state
+ clocks.realtime = clocks.uptime = 409;
+ cur = clocks.realtime * 1000;
+ bi.updateTimeBasesLocked(true, true, cur, cur); // on battery (again)
+ assertTrue(bgtb.isRunning());
+ assertEquals(106_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // On-battery, background - but a different background state
+ clocks.realtime = clocks.uptime = 417;
+ cur = clocks.realtime * 1000;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_RECEIVER); // background too
+ assertTrue(bgtb.isRunning());
+ assertEquals(114_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // Off-battery, foreground
+ clocks.realtime = clocks.uptime = 530;
+ cur = clocks.realtime * 1000;
+ bi.updateTimeBasesLocked(false, false, cur, cur); // off battery
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ assertFalse(bgtb.isRunning());
+ assertEquals(227_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // Off-battery, non-existent
+ clocks.realtime = clocks.uptime = 690;
+ cur = clocks.realtime * 1000;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_NONEXISTENT);
+ assertFalse(bgtb.isRunning());
+ assertEquals(227_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+ }
+
+ @SmallTest
+ public void testWifiScan() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // On battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+ // App in foreground
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // Start timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+ bi.noteWifiScanStartedLocked(UID);
+
+ // Move to background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 254);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // Off battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+
+ // Stop timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+ bi.noteWifiScanStoppedLocked(UID);
+
+ // Test
+ curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+ long time = bi.getUidStats().get(UID).getWifiScanTime(curr, STATS_SINCE_CHARGED);
+ int count = bi.getUidStats().get(UID).getWifiScanCount(STATS_SINCE_CHARGED);
+ int bgCount = bi.getUidStats().get(UID).getWifiScanBackgroundCount(STATS_SINCE_CHARGED);
+ long actualTime = bi.getUidStats().get(UID).getWifiScanActualTime(curr);
+ long bgTime = bi.getUidStats().get(UID).getWifiScanBackgroundTime(curr);
+ assertEquals((305 - 202) * 1000, time);
+ assertEquals(1, count);
+ assertEquals(1, bgCount);
+ assertEquals((305 - 202) * 1000, actualTime);
+ assertEquals((305 - 254) * 1000, bgTime);
+ }
+
+ @SmallTest
+ public void testAppBluetoothScan() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ WorkSource ws = new WorkSource(UID); // needed for bluetooth
+ long curr = 0; // realtime in us
+
+ // On battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+
+ // App in foreground
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // Start timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+ bi.noteBluetoothScanStartedFromSourceLocked(ws);
+
+ // Move to background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 254);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // Off battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+
+ // Stop timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+ bi.noteBluetoothScanStoppedFromSourceLocked(ws);
+
+ // Test
+ curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+ BatteryStats.Timer timer = bi.getUidStats().get(UID).getBluetoothScanTimer();
+ BatteryStats.Timer bgTimer = bi.getUidStats().get(UID).getBluetoothScanBackgroundTimer();
+
+ long time = timer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED);
+ int count = timer.getCountLocked(STATS_SINCE_CHARGED);
+ int bgCount = bgTimer.getCountLocked(STATS_SINCE_CHARGED);
+ long actualTime = timer.getTotalDurationMsLocked(clocks.realtime) * 1000;
+ long bgTime = bgTimer.getTotalDurationMsLocked(clocks.realtime) * 1000;
+ assertEquals((305 - 202) * 1000, time);
+ assertEquals(1, count);
+ assertEquals(1, bgCount);
+ assertEquals((305 - 202) * 1000, actualTime);
+ assertEquals((305 - 254) * 1000, bgTime);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
index f1aeecc..a1b05cd 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
@@ -44,18 +44,21 @@
assertFalse(timer.isRunningLocked());
assertEquals(0, timer.getCurrentDurationMsLocked(300));
assertEquals(0, timer.getMaxDurationMsLocked(301));
+ assertEquals(0, timer.getTotalDurationMsLocked(301));
- // Start timer: current and max advance
+ // Start timer: current, total, and max advance
timer.startRunningLocked(700);
assertTrue(timer.isRunningLocked());
assertEquals(800, timer.getCurrentDurationMsLocked(1500));
assertEquals(801, timer.getMaxDurationMsLocked(1501));
+ assertEquals(802, timer.getTotalDurationMsLocked(1502));
- // Stop timer: current resets to 0, max remains
+ // Stop timer: current resets to 0; total and max remain
timer.stopRunningLocked(3100);
assertFalse(timer.isRunningLocked());
assertEquals(0, timer.getCurrentDurationMsLocked(6300));
assertEquals(2400, timer.getMaxDurationMsLocked(6301));
+ assertEquals(2400, timer.getTotalDurationMsLocked(6302));
// Start time again, but check with a short time, and make sure max doesn't
// increment.
@@ -63,31 +66,36 @@
assertTrue(timer.isRunningLocked());
assertEquals(100, timer.getCurrentDurationMsLocked(12800));
assertEquals(2400, timer.getMaxDurationMsLocked(12801));
+ assertEquals(2502, timer.getTotalDurationMsLocked(12802));
// And stop it again, but with a short time, and make sure it doesn't increment.
timer.stopRunningLocked(12900);
assertFalse(timer.isRunningLocked());
assertEquals(0, timer.getCurrentDurationMsLocked(13000));
assertEquals(2400, timer.getMaxDurationMsLocked(13001));
+ assertEquals(2600, timer.getTotalDurationMsLocked(13002));
// Now start and check that the time doesn't increase if the two times are the same.
timer.startRunningLocked(27000);
assertTrue(timer.isRunningLocked());
assertEquals(0, timer.getCurrentDurationMsLocked(27000));
assertEquals(2400, timer.getMaxDurationMsLocked(27000));
+ assertEquals(2600, timer.getTotalDurationMsLocked(27000));
// Stop the TimeBase. The values should be frozen.
timeBase.setRunning(false, /* uptimeUs */ 10, /* realtimeUs */ 55000*1000);
assertTrue(timer.isRunningLocked());
assertEquals(28000, timer.getCurrentDurationMsLocked(110100));
assertEquals(28000, timer.getMaxDurationMsLocked(110101));
+ assertEquals(30600, timer.getTotalDurationMsLocked(110102));
// Start the TimeBase. The values should be the old value plus the delta
- // between when the timer restarted and the current time
+ // between when the timer restarted and the current time.
timeBase.setRunning(true, /* uptimeUs */ 10, /* realtimeUs */ 220100*1000);
assertTrue(timer.isRunningLocked());
assertEquals(28200, timer.getCurrentDurationMsLocked(220300));
assertEquals(28201, timer.getMaxDurationMsLocked(220301));
+ assertEquals(30802, timer.getTotalDurationMsLocked(220302));
}
@SmallTest
@@ -114,6 +122,7 @@
// Check that it did start running
assertEquals(400, timer.getMaxDurationMsLocked(700));
assertEquals(401, timer.getCurrentDurationMsLocked(701));
+ assertEquals(402, timer.getTotalDurationMsLocked(702));
// Write summary
final Parcel summaryParcel = Parcel.obtain();
@@ -128,8 +137,9 @@
// The new one shouldn't be running, and therefore 0 for current time
assertFalse(summary.isRunningLocked());
assertEquals(0, summary.getCurrentDurationMsLocked(6300));
- // The new one should have the max duration that we had when we wrote it
+ // The new one should have the max and total durations that we had when we wrote it
assertEquals(1200, summary.getMaxDurationMsLocked(6301));
+ assertEquals(1200, summary.getTotalDurationMsLocked(6302));
// Write full
final Parcel fullParcel = Parcel.obtain();
@@ -142,7 +152,9 @@
// The new one shouldn't be running, and therefore 0 for current time
assertFalse(full.isRunningLocked());
assertEquals(0, full.getCurrentDurationMsLocked(6300));
- // The new one should have the max duration that we had when we wrote it
+ // The new one should have the max and total durations that we had when we wrote it
assertEquals(1200, full.getMaxDurationMsLocked(6301));
+ assertEquals(1200, full.getTotalDurationMsLocked(6302));
+
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
index b4afdda..251ceb0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
@@ -146,6 +146,8 @@
BatteryStatsImpl.SamplingTimer timer = new BatteryStatsImpl.SamplingTimer(clocks, timeBase);
// Start running on battery.
+ // (Note that the wrong units are used in this class. setRunning is actually supposed to
+ // take us, not the ms that clocks uses.)
timeBase.setRunning(true, clocks.uptimeMillis(), clocks.elapsedRealtime());
// The first update on battery consumes the values as a way of starting cleanly.
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index 4ec78ff..a41a023 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -17,9 +17,7 @@
import android.app.ActivityManager;
import android.os.BatteryStats;
-import android.os.Debug;
import android.support.test.filters.SmallTest;
-import android.util.Log;
import junit.framework.TestCase;
@@ -31,6 +29,9 @@
private static final int UID = 10500;
private static final int SENSOR_ID = -10000;
+ // TODO: fix the bug in StopwatchTimer to prevent this bug from manifesting here.
+ boolean revealCntBug = false;
+
@SmallTest
public void testSensorStartStop() throws Exception {
final MockClocks clocks = new MockClocks();
@@ -38,7 +39,7 @@
bi.mForceOnBattery = true;
clocks.realtime = 100;
clocks.uptime = 100;
- bi.getOnBatteryTimeBase().setRunning(true, 100, 100);
+ bi.getOnBatteryTimeBase().setRunning(true, 100_000, 100_000);
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
bi.noteStartSensorLocked(UID, SENSOR_ID);
@@ -56,58 +57,345 @@
BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
.get(SENSOR_ID).getSensorTime();
- BatteryStats.Counter sensorBgCounter = bi.getUidStats().get(UID).getSensorStats()
- .get(SENSOR_ID).getSensorBgCount();
+ BatteryStats.Timer sensorBgTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorBackgroundTime();
assertEquals(2, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
- assertEquals(300000,
- sensorTimer.getTotalTimeLocked(clocks.realtime, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(300_000, sensorTimer.getTotalTimeLocked(
+ clocks.realtime * 1000, BatteryStats.STATS_SINCE_CHARGED));
- assertEquals(1, sensorBgCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(1, sensorBgTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(200_000, sensorBgTimer.getTotalTimeLocked(
+ clocks.realtime * 1000, BatteryStats.STATS_SINCE_CHARGED));
}
@SmallTest
- public void testNestedSensorReset() throws Exception {
+ public void testCountingWhileOffBattery() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // Plugged-in (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(false, false, curr, curr);
+
+
+ // Start sensor (battery=off, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+ BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals(0,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ if(revealCntBug) {
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ // Stop sensor (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals(0,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ @SmallTest
+ public void testCountingWhileOnBattery() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // Unplugged (battery=on, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr);
+
+ // Start sensor (battery=on, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+ BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals((215-200)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+
+ // Stop sensor (battery=on, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals((550-200)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ @SmallTest
+ public void testBatteryStatusOnToOff() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // On battery (battery=on, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // Start sensor (battery=on, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Off battery (battery=off, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(false, false, curr, curr);
+
+ // Stop sensor while off battery (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ // Start sensor while off battery (battery=off, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 519);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Test while still running (but off battery)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+ BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ if(revealCntBug) {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(2, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+ assertEquals((305-202)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+
+ // Now stop running (still off battery) (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 693);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals((305-202)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ @SmallTest
+ public void testBatteryStatusOffToOn() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // Plugged-in (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(false, false, curr, curr);
+
+ // Start sensor (battery=off, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+ BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ // Time was entirely off battery, so time=0.
+ assertEquals(0,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Acquired off battery, so count=0.
+ if(revealCntBug) {
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ // Unplug (battery=on, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(true, false, curr, curr);
+
+ //Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 410);
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats().get(SENSOR_ID).getSensorTime();
+ // Part of the time it was on battery.
+ assertEquals((410-305)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Only ever acquired off battery, so count=0.
+ if(revealCntBug) {
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ // Stop sensor (battery=on, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats().get(SENSOR_ID).getSensorTime();
+ // Part of the time it was on battery.
+ assertEquals((550-305)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Only ever acquired off battery, so count=0.
+ if(revealCntBug) {
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+ }
+
+ @SmallTest
+ public void testPooledBackgroundUsage() throws Exception {
+ final int UID_2 = 20000; // second uid for testing pool usage
final MockClocks clocks = new MockClocks();
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
bi.mForceOnBattery = true;
- clocks.realtime = 100;
- clocks.uptime = 100;
- bi.getOnBatteryTimeBase().setRunning(true, 100, 100);
- bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_RECEIVER);
+ long curr = 0; // realtime in us
+ // Entire test is on-battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 1000);
+ bi.updateTimeBasesLocked(true, false, curr, curr);
- clocks.realtime += 100;
- clocks.uptime += 100;
+ // See below for a diagram of events.
+ // UID in foreground
+ curr = 1000 * (clocks.realtime = clocks.uptime = 2002);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // UID starts the sensor (foreground)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 3004);
bi.noteStartSensorLocked(UID, SENSOR_ID);
- clocks.realtime += 100;
- clocks.uptime += 100;
+ // UID_2 in background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 4008);
+ bi.noteUidProcessStateLocked(UID_2, ActivityManager.PROCESS_STATE_RECEIVER); // background
- // The sensor is started and the background counter has been created.
- final BatteryStats.Uid uid = bi.getUidStats().get(UID);
- assertNotNull(uid);
+ // UID_2 starts the sensor (background)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 5016);
+ bi.noteStartSensorLocked(UID_2, SENSOR_ID);
- BatteryStats.Uid.Sensor sensor = uid.getSensorStats().get(SENSOR_ID);
- assertNotNull(sensor);
- assertNotNull(sensor.getSensorTime());
- assertNotNull(sensor.getSensorBgCount());
+ // UID enters background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 6032);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
- // Reset the stats. Since the sensor is still running, we should still see the sensor
- // timer. Background counter should be gone though.
- bi.getUidStatsLocked(UID).reset();
+ // UID enters background again (from a different background state)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 7004);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
- sensor = uid.getSensorStats().get(SENSOR_ID);
- assertNotNull(sensor);
- assertNotNull(sensor.getSensorTime());
- assertNull(sensor.getSensorBgCount());
+ // UID_2 stops the sensor (background), then starts it again, then stops again
+ curr = 1000 * (clocks.realtime = clocks.uptime = 8064);
+ bi.noteStopSensorLocked(UID_2, SENSOR_ID);
+ curr = 1000 * (clocks.realtime = clocks.uptime = 9128);
+ bi.noteStartSensorLocked(UID_2, SENSOR_ID);
+ curr = 1000 * (clocks.realtime = clocks.uptime = 10256);
+ bi.noteStopSensorLocked(UID_2, SENSOR_ID);
+ // UID re-enters foreground
+ curr = 1000 * (clocks.realtime = clocks.uptime = 11512);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // UID starts the sensor a second time (foreground)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 12000);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // UID re-enters background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 13002);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // UID stops the sensor completely (background)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 14004);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+ curr = 1000 * (clocks.realtime = clocks.uptime = 14024);
bi.noteStopSensorLocked(UID, SENSOR_ID);
- // Now the sensor timer has stopped so this reset should also take out the sensor.
- bi.getUidStatsLocked(UID).reset();
+ // UID starts the sensor anew (background)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 15010);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
- sensor = uid.getSensorStats().get(SENSOR_ID);
- assertNull(sensor);
+ // UID stops the sensor (background)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 16020);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+// Summary
+// UID
+// foreground: 2002---6032, 11512---13002
+// background: 6032---------------11512, 13002--------------------------
+// sensor running: 3004-----------------------------14024, 15010-16020
+//
+// UID2
+// foreground:
+// background: 4008-------------------------------------------------------
+// sensor running: 5016--8064, 9128-10256
+
+ BatteryStats.Timer timer1 = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ BatteryStats.Timer bgTimer1 = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorBackgroundTime();
+
+ BatteryStats.Timer timer2 = bi.getUidStats().get(UID_2).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ BatteryStats.Timer bgTimer2 = bi.getUidStats().get(UID_2).getSensorStats()
+ .get(SENSOR_ID).getSensorBackgroundTime();
+
+ // Expected values
+ long expActualTime1 = (14024 - 3004) + (16020 - 15010);
+ long expBgTime1 = (11512 - 6032) + (14024 - 13002) + (16020 - 15010);
+
+ long expActualTime2 = (8064 - 5016) + (10256 - 9128);
+ long expBgTime2 = (8064 - 5016) + (10256 - 9128);
+
+ long expBlamedTime1 = (5016 - 3004) + (8064 - 5016)/2 + (9128 - 8064) + (10256 - 9128)/2
+ + (14024 - 10256) + (16020 - 15010);
+ long expBlamedTime2 = (8064 - 5016)/2 + (10256 - 9128)/2;
+
+ // Test: UID - blamed time
+ assertEquals(expBlamedTime1 * 1000,
+ timer1.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Test: UID - actual time
+ assertEquals(expActualTime1 * 1000,
+ timer1.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+ // Test: UID - background time
+ // bg timer ignores pools, so both totalTime and totalDuration should give the same result
+ assertEquals(expBgTime1 * 1000,
+ bgTimer1.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(expBgTime1 * 1000,
+ bgTimer1.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+ // Test: UID - count
+ assertEquals(2, timer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ // Test: UID - background count
+ if(revealCntBug) {
+ assertEquals(1, bgTimer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(2, bgTimer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ // Test: UID_2 - blamed time
+ assertEquals(expBlamedTime2 * 1000,
+ timer2.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Test: UID_2 - actual time
+ assertEquals(expActualTime2 * 1000,
+ timer2.getTotalDurationMsLocked(clocks.realtime) * 1000);
+ // Test: UID_2 - background time
+ // bg timer ignores pools, so both totalTime and totalDuration should give the same result
+ assertEquals(expBgTime2 * 1000,
+ bgTimer2.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(expBgTime2 * 1000,
+ bgTimer2.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+ // Test: UID_2 - count
+ assertEquals(2, timer2.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ // Test: UID_2 - background count
+ assertEquals(2, bgTimer2.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
}
}
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 c7cd0ee..1113268 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -12,6 +12,7 @@
BatteryStatsTimerTest.class,
BatteryStatsUidTest.class,
BatteryStatsSensorTest.class,
+ BatteryStatsBackgroundStatsTest.class,
})
public class BatteryStatsTests {
}
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 1054106..65f898c 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -26,6 +26,7 @@
MockBatteryStatsImpl(Clocks clocks) {
super(clocks);
this.clocks = mClocks;
+ mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
}
MockBatteryStatsImpl() {
@@ -39,5 +40,9 @@
public boolean isOnBattery() {
return mForceOnBattery ? true : super.isOnBattery();
}
+
+ public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
+ return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
+ }
}
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index acacd76..5603508 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -74,7 +74,6 @@
const char* AssetManager::IDMAP_BIN = "/system/bin/idmap";
const char* AssetManager::OVERLAY_DIR = "/vendor/overlay";
const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme";
-const char* AssetManager::OVERLAY_THEME_DIR_PERSIST_PROPERTY = "persist.vendor.overlay.theme";
const char* AssetManager::TARGET_PACKAGE_NAME = "android";
const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk";
const char* AssetManager::IDMAP_DIR = "/data/resource-cache";
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index becd307..0441b9d 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -66,11 +66,6 @@
* OVERLAY_DIR.
*/
static const char* OVERLAY_THEME_DIR_PROPERTY;
- /**
- * If OVERLAY_THEME_DIR_PERSIST_PROPERTY, use it to override
- * OVERLAY_THEME_DIR_PROPERTY.
- */
- static const char* OVERLAY_THEME_DIR_PERSIST_PROPERTY;
static const char* TARGET_PACKAGE_NAME;
static const char* TARGET_APK_PATH;
static const char* IDMAP_DIR;
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index a84cee8..44494a6 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -141,6 +141,11 @@
mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
mDefaultAudioVideo.updatePresentationDisplay();
+ if (((AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE))
+ .isVolumeFixed()) {
+ mDefaultAudioVideo.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
+ }
+
addRouteStatic(mDefaultAudioVideo);
// This will select the active wifi display route if there is one.
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 7122eaf..5bf205e 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -87,10 +87,11 @@
*/
public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
- private static final int CONNECT_STATE_DISCONNECTED = 0;
- private static final int CONNECT_STATE_CONNECTING = 1;
- private static final int CONNECT_STATE_CONNECTED = 2;
- private static final int CONNECT_STATE_SUSPENDED = 3;
+ private static final int CONNECT_STATE_DISCONNECTING = 0;
+ private static final int CONNECT_STATE_DISCONNECTED = 1;
+ private static final int CONNECT_STATE_CONNECTING = 2;
+ private static final int CONNECT_STATE_CONNECTED = 3;
+ private static final int CONNECT_STATE_SUSPENDED = 4;
private final Context mContext;
private final ComponentName mServiceComponent;
@@ -213,6 +214,7 @@
// It's ok to call this any state, because allowing this lets apps not have
// to check isConnected() unnecessarily. They won't appreciate the extra
// assertions for this. We do everything we can here to go back to a sane state.
+ mState = CONNECT_STATE_DISCONNECTING;
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -535,7 +537,7 @@
// If we are connected, tell the service that we are watching. If we aren't connected,
// the service will be told when we connect.
- if (mState == CONNECT_STATE_CONNECTED) {
+ if (isConnected()) {
try {
if (options == null) {
mServiceBinder.addSubscriptionDeprecated(parentId, mServiceCallbacks);
@@ -563,7 +565,7 @@
// Tell the service if necessary.
try {
if (callback == null) {
- if (mState == CONNECT_STATE_CONNECTED) {
+ if (isConnected()) {
mServiceBinder.removeSubscriptionDeprecated(parentId, mServiceCallbacks);
mServiceBinder.removeSubscription(parentId, null, mServiceCallbacks);
}
@@ -572,7 +574,7 @@
final List<Bundle> optionsList = sub.getOptionsList();
for (int i = callbacks.size() - 1; i >= 0; --i) {
if (callbacks.get(i) == callback) {
- if (mState == CONNECT_STATE_CONNECTED) {
+ if (isConnected()) {
mServiceBinder.removeSubscription(
parentId, callback.mToken, mServiceCallbacks);
}
@@ -597,6 +599,8 @@
*/
private static String getStateLabel(int state) {
switch (state) {
+ case CONNECT_STATE_DISCONNECTING:
+ return "CONNECT_STATE_DISCONNECTING";
case CONNECT_STATE_DISCONNECTED:
return "CONNECT_STATE_DISCONNECTED";
case CONNECT_STATE_CONNECTING:
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8802010..b60e2fe 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -480,21 +480,18 @@
}
@Override
- public boolean ejectRoot(String rootId) {
+ public void ejectRoot(String rootId) {
final long token = Binder.clearCallingIdentity();
- boolean ejected = false;
RootInfo root = mRoots.get(rootId);
if (root != null) {
try {
mStorageManager.unmount(root.volumeId);
- ejected = true;
} catch (RuntimeException e) {
- Log.w(TAG, "Root '" + root.title + "' could not be ejected");
+ throw new IllegalStateException(e);
} finally {
Binder.restoreCallingIdentity(token);
}
}
- return ejected;
}
@Override
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ca34bb9..cc2dc1b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -356,7 +356,7 @@
</string-array>
<!-- Title for profile selection dialog [CHAR LIMIT=30] -->
- <string name="choose_profile">Choose Profile</string>
+ <string name="choose_profile">Choose profile</string>
<!-- Header for items under the personal user [CHAR LIMIT=30] -->
<string name="category_personal">Personal</string>
@@ -850,9 +850,9 @@
<string name="disabled_by_admin_summary_text">Controlled by admin</string>
<!-- Summary for switch preference to denote it is switched on [CHAR LIMIT=50] -->
- <string name="enabled_by_admin">Enabled by administrator</string>
+ <string name="enabled_by_admin">Enabled by admin</string>
<!-- Summary for switch preference to denote it is switched off [CHAR LIMIT=50] -->
- <string name="disabled_by_admin">Disabled by administrator</string>
+ <string name="disabled_by_admin">Disabled by admin</string>
<!-- Option in navigation drawer that leads to Settings main screen [CHAR LIMIT=30] -->
<string name="home">Settings Home</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index 9bb3c36..350b648 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -169,6 +169,19 @@
return context.getString(R.string.config_defaultAccessibilityService);
}
+ /**
+ * Check if the accessibility shortcut is enabled for a user
+ *
+ * @param context A valid context
+ * @param userId The user of interest
+ * @return {@code true} if the shortcut is enabled for the user. {@code false} otherwise.
+ * Note that the shortcut may be enabled, but no action associated with it.
+ */
+ public static boolean isShortcutEnabled(Context context, int userId) {
+ return Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, userId) == 1;
+ }
+
private static Set<ComponentName> getInstalledServices(Context context) {
final Set<ComponentName> installedServices = new HashSet<>();
installedServices.clear();
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 14bb02d..a8629f8 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -51,7 +51,7 @@
<bool name="def_wifi_on">false</bool>
<!-- 0 == never, 1 == only when plugged in, 2 == always -->
<integer name="def_wifi_sleep_policy">2</integer>
- <bool name="def_wifi_wakeup_enabled">false</bool>
+ <bool name="def_wifi_wakeup_enabled">true</bool>
<bool name="def_networks_available_notification_on">true</bool>
<bool name="def_backup_enabled">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1f1c189..37b155f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2037,6 +2037,8 @@
private final BackupManager mBackupManager;
+ private String mSettingsCreationBuildId;
+
public SettingsRegistry() {
mHandler = new MyHandler(getContext().getMainLooper());
mGenerationRegistry = new GenerationRegistry(mLock);
@@ -2502,6 +2504,8 @@
return;
}
+ mSettingsCreationBuildId = Build.ID;
+
final long identity = Binder.clearCallingIdentity();
try {
List<UserInfo> users = mUserManager.getUsers(true);
@@ -2570,6 +2574,12 @@
ensureSettingsStateLocked(globalKey);
SettingsState globalSettings = mSettingsStates.get(globalKey);
migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL);
+ // If this was just created
+ if (mSettingsCreationBuildId != null) {
+ globalSettings.insertSettingLocked(Settings.Global.DATABASE_CREATION_BUILDID,
+ mSettingsCreationBuildId, null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
globalSettings.persistSyncLocked();
}
@@ -3333,6 +3343,24 @@
currentVersion = 142;
}
+ if (currentVersion == 142) {
+ // Version 142: Set a default value for Wi-Fi wakeup feature.
+ if (userId == UserHandle.USER_SYSTEM) {
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ Setting currentSetting = globalSettings.getSettingLocked(
+ Settings.Global.WIFI_WAKEUP_ENABLED);
+ if (currentSetting.isNull()) {
+ globalSettings.insertSettingLocked(
+ Settings.Global.WIFI_WAKEUP_ENABLED,
+ getContext().getResources().getBoolean(
+ R.bool.def_wifi_wakeup_enabled) ? "1" : "0",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 143;
+ }
+
if (currentVersion != newVersion) {
Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
+ newVersion + " left it at "
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 1c49a55d..f6f2935 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -31,8 +31,12 @@
<!-- Text of notification indicating that bugreport will appear on the phone. [CHAR LIMIT=100] -->
<string name="bugreport_finished_text" product="watch">The bug report will appear on the phone shortly</string>
<!-- Text of notification indicating that tapping will share the captured bugreport. [CHAR LIMIT=100] -->
+ <string name="bugreport_finished_text" product="tv">Select to share your bug report</string>
+ <!-- Text of notification indicating that tapping will share the captured bugreport. [CHAR LIMIT=100] -->
<string name="bugreport_finished_text" product="default">Tap to share your bug report</string>
<!-- Text of notification indicating that swipe left will share the captured bugreport, but giving user the option to wait for the screenshot. [CHAR LIMIT=100] -->
+ <string name="bugreport_finished_pending_screenshot_text" product="tv">Select to share your bug report without a screenshot or wait for the screenshot to finish</string>
+ <!-- Text of notification indicating that swipe left will share the captured bugreport, but giving user the option to wait for the screenshot. [CHAR LIMIT=100] -->
<string name="bugreport_finished_pending_screenshot_text" product="watch">Tap to share your bug report without a screenshot or wait for the screenshot to finish</string>
<!-- Text of notification indicating that tapping will share the captured bugreport, but giving user the option to wait for the screenshot. [CHAR LIMIT=100] -->
<string name="bugreport_finished_pending_screenshot_text" product="default">Tap to share your bug report without a screenshot or wait for the screenshot to finish</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4b932a3..e4d71b6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -531,7 +531,7 @@
android:excludeFromRecents="true"
android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.SHOW_BRIGHTNESS_DIALOG" />
+ <action android:name="com.android.intent.action.SHOW_BRIGHTNESS_DIALOG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
@@ -564,8 +564,8 @@
<receiver
android:name=".statusbar.KeyboardShortcutsReceiver">
<intent-filter>
- <action android:name="android.intent.action.DISMISS_KEYBOARD_SHORTCUTS" />
- <action android:name="android.intent.action.SHOW_KEYBOARD_SHORTCUTS" />
+ <action android:name="com.android.intent.action.DISMISS_KEYBOARD_SHORTCUTS" />
+ <action android:name="com.android.intent.action.SHOW_KEYBOARD_SHORTCUTS" />
</intent-filter>
</receiver>
</application>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java
new file mode 100644
index 0000000..9e5db73
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java
@@ -0,0 +1,41 @@
+/*
+ * 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.systemui.plugins;
+
+import com.android.systemui.plugins.VolumeDialog.Callback;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * This interface is really just a stub for initialization/teardown, actual handling of
+ * when to show will be done through {@link VolumeDialogController}
+ */
+@ProvidesInterface(action = VolumeDialog.ACTION, version = VolumeDialog.VERSION)
+@DependsOn(target = Callback.class)
+public interface VolumeDialog extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_VOLUME";
+ int VERSION = 1;
+
+ void init(int windowType, Callback callback);
+ void destroy();
+
+ @ProvidesInterface(version = VERSION)
+ public interface Callback {
+ int VERSION = 1;
+
+ void onZenSettingsClicked();
+ void onZenPrioritySettingsClicked();
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
new file mode 100644
index 0000000..903ff72
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -0,0 +1,176 @@
+/*
+ * 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.systemui.plugins;
+
+import android.annotation.IntegerRes;
+import android.content.ComponentName;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.systemui.plugins.VolumeDialogController.Callbacks;
+import com.android.systemui.plugins.VolumeDialogController.State;
+import com.android.systemui.plugins.VolumeDialogController.StreamState;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Manages the VolumeDialog.
+ *
+ * Accessible through {@link PluginDependency}
+ */
+@ProvidesInterface(version = VolumeDialogController.VERSION)
+@DependsOn(target = StreamState.class)
+@DependsOn(target = State.class)
+@DependsOn(target = Callbacks.class)
+public interface VolumeDialogController {
+ int VERSION = 1;
+
+ void setActiveStream(int stream);
+ void setStreamVolume(int stream, int userLevel);
+ void setRingerMode(int ringerModeNormal, boolean external);
+
+ boolean hasVibrator();
+ void vibrate();
+
+ AudioManager getAudioManager();
+
+ void notifyVisible(boolean visible);
+
+ void addCallback(Callbacks callbacks, Handler handler);
+ void removeCallback(Callbacks callbacks);
+
+ void userActivity();
+ void getState();
+
+ @ProvidesInterface(version = StreamState.VERSION)
+ public static final class StreamState {
+ public static final int VERSION = 1;
+
+ public boolean dynamic;
+ public int level;
+ public int levelMin;
+ public int levelMax;
+ public boolean muted;
+ public boolean muteSupported;
+ public @IntegerRes int name;
+ public String remoteLabel;
+ public boolean routedToBluetooth;
+
+ public StreamState copy() {
+ final StreamState rt = new StreamState();
+ rt.dynamic = dynamic;
+ rt.level = level;
+ rt.levelMin = levelMin;
+ rt.levelMax = levelMax;
+ rt.muted = muted;
+ rt.muteSupported = muteSupported;
+ rt.name = name;
+ rt.remoteLabel = remoteLabel;
+ rt.routedToBluetooth = routedToBluetooth;
+ return rt;
+ }
+ }
+
+ @ProvidesInterface(version = State.VERSION)
+ public static final class State {
+ public static final int VERSION = 1;
+
+ public static int NO_ACTIVE_STREAM = -1;
+
+ public final SparseArray<StreamState> states = new SparseArray<>();
+
+ public int ringerModeInternal;
+ public int ringerModeExternal;
+ public int zenMode;
+ public ComponentName effectsSuppressor;
+ public String effectsSuppressorName;
+ public int activeStream = NO_ACTIVE_STREAM;
+
+ public State copy() {
+ final State rt = new State();
+ for (int i = 0; i < states.size(); i++) {
+ rt.states.put(states.keyAt(i), states.valueAt(i).copy());
+ }
+ rt.ringerModeExternal = ringerModeExternal;
+ rt.ringerModeInternal = ringerModeInternal;
+ rt.zenMode = zenMode;
+ if (effectsSuppressor != null) {
+ rt.effectsSuppressor = effectsSuppressor.clone();
+ }
+ rt.effectsSuppressorName = effectsSuppressorName;
+ rt.activeStream = activeStream;
+ return rt;
+ }
+
+ @Override
+ public String toString() {
+ return toString(0);
+ }
+
+ public String toString(int indent) {
+ final StringBuilder sb = new StringBuilder("{");
+ if (indent > 0) sep(sb, indent);
+ for (int i = 0; i < states.size(); i++) {
+ if (i > 0) {
+ sep(sb, indent);
+ }
+ final int stream = states.keyAt(i);
+ final StreamState ss = states.valueAt(i);
+ sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
+ .append('[').append(ss.levelMin).append("..").append(ss.levelMax)
+ .append(']');
+ if (ss.muted) sb.append(" [MUTED]");
+ if (ss.dynamic) sb.append(" [DYNAMIC]");
+ }
+ sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
+ sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
+ sep(sb, indent); sb.append("zenMode:").append(zenMode);
+ sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
+ sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
+ sep(sb, indent); sb.append("activeStream:").append(activeStream);
+ if (indent > 0) sep(sb, indent);
+ return sb.append('}').toString();
+ }
+
+ private static void sep(StringBuilder sb, int indent) {
+ if (indent > 0) {
+ sb.append('\n');
+ for (int i = 0; i < indent; i++) {
+ sb.append(' ');
+ }
+ } else {
+ sb.append(',');
+ }
+ }
+ }
+
+ @ProvidesInterface(version = Callbacks.VERSION)
+ public interface Callbacks {
+ int VERSION = 1;
+
+ void onShowRequested(int reason);
+ void onDismissRequested(int reason);
+ void onStateChanged(State state);
+ void onLayoutDirectionChanged(int layoutDirection);
+ void onConfigurationChanged();
+ void onShowVibrateHint();
+ void onShowSilentHint();
+ void onScreenOff();
+ void onShowSafetyWarning(int flags);
+ void onAccessibilityModeChanged(Boolean showA11yStream);
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
new file mode 100644
index 0000000..8dde357
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -0,0 +1,97 @@
+/*
+ * 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.systemui.plugins.statusbar;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+
+@ProvidesInterface(action = NotificationMenuRowPlugin.ACTION,
+ version = NotificationMenuRowPlugin.VERSION)
+@DependsOn(target = OnMenuEventListener.class)
+@DependsOn(target = MenuItem.class)
+@DependsOn(target = NotificationSwipeActionHelper.class)
+@DependsOn(target = SnoozeOption.class)
+public interface NotificationMenuRowPlugin extends Plugin {
+
+ public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
+ public static final int VERSION = 1;
+
+ @ProvidesInterface(version = OnMenuEventListener.VERSION)
+ public interface OnMenuEventListener {
+ public static final int VERSION = 1;
+ public void onMenuClicked(View row, int x, int y, MenuItem menu);
+
+ public void onMenuReset(View row);
+
+ public void onMenuShown(View row);
+ }
+
+ @ProvidesInterface(version = MenuItem.VERSION)
+ public interface MenuItem {
+ public static final int VERSION = 1;
+ public View getMenuView();
+
+ public View getGutsView();
+
+ public String getContentDescription();
+ }
+
+ /**
+ * @return a list of items to populate the menu 'behind' a notification.
+ */
+ public ArrayList<MenuItem> getMenuItems(Context context);
+
+ /**
+ * @return the {@link MenuItem} to display when a notification is long pressed.
+ */
+ public MenuItem getLongpressMenuItem(Context context);
+
+ public void setMenuItems(ArrayList<MenuItem> items);
+
+ public void setMenuClickListener(OnMenuEventListener listener);
+
+ public void setSwipeActionHelper(NotificationSwipeActionHelper listener);
+
+ public void setAppName(String appName);
+
+ public void createMenu(ViewGroup parent);
+
+ public View getMenuView();
+
+ public boolean isMenuVisible();
+
+ public void resetMenu();
+
+ public void onTranslationUpdate(float translation);
+
+ public void onHeightUpdate();
+
+ public boolean onTouchEvent(View view, MotionEvent ev, float velocity);
+
+ public default boolean useDefaultMenuItems() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
deleted file mode 100644
index 529c421..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
+++ /dev/null
@@ -1,95 +0,0 @@
-
-package com.android.systemui.plugins.statusbar;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.service.notification.SnoozeCriterion;
-import android.service.notification.StatusBarNotification;
-import android.view.View;
-
-import java.util.ArrayList;
-
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-@ProvidesInterface(action = NotificationMenuRowProvider.ACTION,
- version = NotificationMenuRowProvider.VERSION)
-public interface NotificationMenuRowProvider extends Plugin {
-
- public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
-
- public static final int VERSION = 1;
-
- /**
- * Returns a list of items to populate the menu 'behind' a notification.
- */
- public ArrayList<MenuItem> getMenuItems(Context context);
-
- public interface OnMenuClickListener {
- public void onMenuClicked(View row, int x, int y, MenuItem menu);
-
- public void onMenuReset(View row);
- }
-
- public interface GutsInteractionListener {
- public void onInteraction(View view);
-
- public void closeGuts(View view);
- }
-
- public interface GutsContent {
- public void setInteractionListener(GutsInteractionListener listener);
-
- public View getContentView();
-
- public boolean handleCloseControls(boolean save);
-
- public boolean willBeRemoved();
- }
-
- public interface SnoozeGutsContent extends GutsContent {
- public void setSnoozeListener(SnoozeListener listener);
-
- public void setStatusBarNotification(StatusBarNotification sbn);
- }
-
- public interface SnoozeListener {
- public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption);
- }
-
- public static class MenuItem {
- public Drawable icon;
- public String menuDescription;
- public View menuView;
- public GutsContent gutsContent;
-
- public MenuItem(Drawable i, String s, GutsContent content) {
- icon = i;
- menuDescription = s;
- gutsContent = content;
- }
-
- public View getGutsView() {
- return gutsContent.getContentView();
- }
-
- public boolean onTouch(View v, int x, int y) {
- return false;
- }
- }
-
- public static class SnoozeOption {
- public SnoozeCriterion criterion;
- public int snoozeForMinutes;
- public CharSequence description;
- public CharSequence confirmation;
-
- public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc,
- CharSequence confirm) {
- criterion = crit;
- snoozeForMinutes = minsToSnoozeFor;
- description = desc;
- confirmation = confirm;
- }
- }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
new file mode 100644
index 0000000..4ce1e36
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -0,0 +1,72 @@
+/*
+ * 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.systemui.plugins.statusbar;
+
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+import android.view.MotionEvent;
+import android.view.View;
+
+@ProvidesInterface(version = NotificationSwipeActionHelper.VERSION)
+@DependsOn(target = SnoozeOption.class)
+public interface NotificationSwipeActionHelper {
+ public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_SWIPE_ACTION";
+
+ public static final int VERSION = 1;
+
+ /**
+ * Call this to dismiss a notification.
+ */
+ public void dismiss(View animView, float velocity);
+
+ /**
+ * Call this to snap a notification to provided {@code targetLeft}.
+ */
+ public void snap(View animView, float velocity, float targetLeft);
+
+ /**
+ * Call this to snooze a notification based on the provided {@link SnoozeOption}.
+ */
+ public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+
+ public float getMinDismissVelocity();
+
+ public boolean isDismissGesture(MotionEvent ev);
+
+ public boolean swipedFarEnough(float translation, float viewSize);
+
+ public boolean swipedFastEnough(float translation, float velocity);
+
+ @ProvidesInterface(version = SnoozeOption.VERSION)
+ public static class SnoozeOption {
+ public static final int VERSION = 1;
+ public int snoozeForMinutes;
+ public SnoozeCriterion criterion;
+ public CharSequence description;
+ public CharSequence confirmation;
+
+ public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc,
+ CharSequence confirm) {
+ criterion = crit;
+ snoozeForMinutes = minsToSnoozeFor;
+ description = desc;
+ confirmation = confirm;
+ }
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index ff689aa..2f52227 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -327,7 +327,7 @@
<!-- An explanation text that the credential needs to be entered because a device admin has
locked the device. [CHAR LIMIT=80] -->
- <string name="kg_prompt_reason_device_admin">Device administrator locked device</string>
+ <string name="kg_prompt_reason_device_admin">Device locked by admin</string>
<!-- An explanation text that the credential needs to be entered because the user has clicked
the force lock button. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/drawable/ic_add_circle_qs.xml b/packages/SystemUI/res/drawable/ic_add_circle_qs.xml
index 6415ecb4..8e93344 100644
--- a/packages/SystemUI/res/drawable/ic_add_circle_qs.xml
+++ b/packages/SystemUI/res/drawable/ic_add_circle_qs.xml
@@ -18,7 +18,7 @@
android:height="48.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:tint="?android:attr/colorForeground">
<group
android:scaleX="1.2"
android:scaleY="1.2"
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index f5c3d40..deb494f 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -25,5 +25,5 @@
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
android:textColor="?android:attr/textColorPrimary"
android:gravity="center_vertical|start"
- android:paddingStart="@dimen/battery_level_padding_start"
+ android:paddingEnd="@dimen/battery_level_padding_start"
/>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index fc1271c..e3063c5 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -85,6 +85,7 @@
android:layout_height="@dimen/keyguard_affordance_height"
android:layout_gravity="bottom|center_horizontal"
android:src="@drawable/ic_lock_24dp"
+ android:contentDescription="@string/accessibility_unlock_button"
android:scaleType="center" />
</com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/layout/notification_menu_row.xml b/packages/SystemUI/res/layout/notification_menu_row.xml
deleted file mode 100644
index 12bcf81..0000000
--- a/packages/SystemUI/res/layout/notification_menu_row.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2016, 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.
--->
-<com.android.systemui.statusbar.NotificationMenuRow
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="invisible"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 0fd2dc9..64caefd 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<RelativeLayout
+<com.android.systemui.qs.tileimpl.ButtonRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -88,4 +88,4 @@
android:alpha="?android:attr/disabledAlpha"
android:background="?android:attr/colorForeground"/>
-</RelativeLayout>
+</com.android.systemui.qs.tileimpl.ButtonRelativeLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index d62cc18..7f37087 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -23,13 +23,7 @@
android:clickable="true"
>
- <ViewStub
- android:layout="@layout/notification_menu_row"
- android:id="@+id/menu_row_stub"
- android:inflatedId="@+id/notification_menu_row"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
+ <!-- Menu displayed behind notification added here programmatically -->
<com.android.systemui.statusbar.NotificationBackgroundView
android:id="@+id/backgroundNormal"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d6d01d8a..5b20716 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -190,7 +190,8 @@
<!-- Notification text displayed when we fail to save a screenshot. [CHAR LIMIT=100] -->
<string name="screenshot_failed_to_save_text">Can\'t save screenshot due to limited storage space.</string>
<!-- Notification text displayed when we fail to take a screenshot. [CHAR LIMIT=100] -->
- <string name="screenshot_failed_to_capture_text">Taking screenshots is not allowed by the app or your organization.</string>
+ <string name="screenshot_failed_to_capture_text">Taking screenshots isn\'t allowed by the app or
+ your organization</string>
<!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] -->
<string name="usb_preference_title">USB file transfer options</string>
@@ -221,8 +222,8 @@
<string name="accessibility_voice_assist_button">Voice Assist</string>
<!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_unlock_button">Unlock</string>
- <!-- Content description of the unlock button when fingerpint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_unlock_button_fingerprint">Unlock button, waiting for fingerprint</string>
+ <!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_waiting_for_fingerprint">Waiting for fingerprint</string>
<!-- Accessibility action of the unlock button when fingerpint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_unlock_without_fingerprint">Unlock without using your fingerprint</string>
<!-- Click action label for accessibility for the unlock button. [CHAR LIMIT=NONE] -->
@@ -1020,6 +1021,51 @@
<!-- Footer vpn present text [CHAR LIMIT=50] -->
<string name="branded_vpn_footer">Network may be monitored</string>
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner that can monitor the network traffic [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_management_monitoring">Your organization manages this device and may monitor network traffic</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner that can monitor the network traffic [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_named_management_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> manages this device and may monitor network traffic</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to a VPN [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_management_named_vpn">Device is managed by your organization and connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to a VPN [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_named_management_named_vpn">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> and connected to <xliff:g id="vpn_app" example="Foo VPN App">%2$s</xliff:g></string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_management">Device is managed by your organization</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_named_management">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g></string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_management_vpns">Device is managed by your organization and connected to VPNs</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_named_management_vpns">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> and connected to VPNs</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a managed profile which can be monitored by the profile owner [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_managed_profile_monitoring">Your organization may monitor network traffic in your work profile</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a managed profile which can be monitored by the profile owner [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_named_managed_profile_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> may monitor network traffic in your work profile</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that a certificate authorithy is installed on this device and the traffic might be monitored [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_monitoring">Network may be monitored</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_vpns">Device connected to VPNs</string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN in the work profile [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_managed_profile_named_vpn">Work profile connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN in the personal profile (as opposed to the work profile) [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_personal_profile_named_vpn">Personal profile connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN [CHAR LIMIT=100] -->
+ <string name="quick_settings_disclosure_named_vpn">Device connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+
<!-- Monitoring dialog title for device owned devices [CHAR LIMIT=35] -->
<string name="monitoring_title_device_owned">Device monitoring</string>
@@ -1034,7 +1080,7 @@
<string name="monitoring_subtitle_vpn">VPN</string>
<!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=35]-->
- <string name="monitoring_subtitle_network_logging">Network Logging</string>
+ <string name="monitoring_subtitle_network_logging">Network logging</string>
<!-- Monitoring dialog subtitle for the section describing certificate authorities [CHAR LIMIT=35]-->
<string name="monitoring_subtitle_ca_certificate">CA certificates</string>
@@ -1064,7 +1110,7 @@
<string name="monitoring_description_ca_certificate">A certificate authority is installed on this device. Your secure network traffic may be monitored or modified.</string>
<!-- Monitoring dialog: Description of Network Logging. [CHAR LIMIT=NONE]-->
- <string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitores traffic on your device.</string>
+ <string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitors traffic on your device.</string>
<!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]-->
<string name="monitoring_description_named_vpn">You're connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
@@ -1102,6 +1148,12 @@
<!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=60] -->
<string name="monitoring_description_vpn_settings">Open VPN Settings</string>
+ <!-- Monitoring dialog: Space that separates the CA certs body text and the "Open trusted credentials" link that follows it. [CHAR LIMIT=5] -->
+ <string name="monitoring_description_ca_cert_settings_separator">" "</string>
+
+ <!-- Monitoring dialog: Link to open the settings page containing CA certificates [CHAR LIMIT=NONE] -->
+ <string name="monitoring_description_ca_cert_settings">Open trusted credentials</string>
+
<!-- Monitoring dialog: Network logging text [CHAR LIMIT=400] -->
<string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information, contact your admin.</string>
@@ -1115,7 +1167,9 @@
<string name="legacy_vpn_name">VPN</string>
<!-- Monitoring dialog text for single app (no profile or device owner) [CHAR LIMIT=400] -->
- <string name="monitoring_description_app">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your network activity including emails, apps, and websites.</string>
+ <string name="monitoring_description_app">You\'re connected to
+ <xliff:g id="application">%1$s</xliff:g>, which can monitor your network activity,
+ including emails, apps, and websites.</string>
<!-- Monitoring dialog text for single app (inside personal profile) [CHAR LIMIT=400] -->
<string name="monitoring_description_app_personal">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps, and websites.</string>
@@ -1124,10 +1178,18 @@
<string name="branded_monitoring_description_app_personal">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps, and websites.</string>
<!-- Monitoring dialog text for single app (inside work profile) [CHAR LIMIT=400] -->
- <string name="monitoring_description_app_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nFor more information, contact your admin.</string>
+ <string name="monitoring_description_app_work">Your work profile is managed by
+ <xliff:g id="organization">%1$s</xliff:g>. The profile is connected to
+ <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity,
+ including emails, apps, and websites.\n\nFor more information, contact your admin.</string>
<!-- Monitoring dialog text for multiple apps (in personal and work profiles) [CHAR LIMIT=400] -->
- <string name="monitoring_description_app_personal_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nYou\'re also connected to <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network activity.</string>
+ <string name="monitoring_description_app_personal_work">Your work profile is managed by
+ <xliff:g id="organization">%1$s</xliff:g>. The profile is connected to
+ <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity,
+ including emails, apps, and websites.\n\nYou\'re also connected to
+ <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network
+ activity.</string>
<!-- Indication on the keyguard that appears when the user disables trust agents until the next time they unlock manually. [CHAR LIMIT=NONE] -->
<string name="keyguard_indication_trust_disabled">Device will stay locked until you manually unlock</string>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 14f2c4a..8c1062b 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -181,6 +181,7 @@
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
updatePercentText();
addView(mBatteryPercentView,
+ 0,
new ViewGroup.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 374086d..d058e78 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -32,6 +32,7 @@
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.PluginManagerImpl;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -79,6 +80,7 @@
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.leak.LeakReporter;
+import com.android.systemui.volume.VolumeDialogControllerImpl;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -252,6 +254,9 @@
mProviders.put(LocalBluetoothManager.class, () ->
LocalBluetoothManager.getInstance(mContext, null));
+ mProviders.put(VolumeDialogController.class, () ->
+ new VolumeDialogControllerImpl(mContext));
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index a95713f..5a04108d 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -32,9 +32,10 @@
import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.NotificationMenuRow;
import java.util.HashMap;
@@ -267,7 +268,7 @@
mCurrView = mCallback.getChildAtPosition(ev);
if (mCurrView != null) {
- onDownUpdate(mCurrView);
+ onDownUpdate(mCurrView, ev);
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
mVelocityTracker.addMovement(ev);
mInitialTouchPos = getPos(ev);
@@ -285,8 +286,12 @@
mCurrView.getLocationOnScreen(mTmpPos);
final int x = (int) ev.getRawX() - mTmpPos[0];
final int y = (int) ev.getRawY() - mTmpPos[1];
- mLongPressListener.onLongPress(mCurrView, x, y,
- NotificationMenuRow.getLongpressMenuItem(mContext));
+ MenuItem menuItem = null;
+ if (mCurrView instanceof ExpandableNotificationRow) {
+ menuItem = ((ExpandableNotificationRow) mCurrView)
+ .getProvider().getLongpressMenuItem(mContext);
+ }
+ mLongPressListener.onLongPress(mCurrView, x, y, menuItem);
}
}
};
@@ -479,14 +484,14 @@
/**
* Called when there's a down event.
*/
- public void onDownUpdate(View currView) {
+ public void onDownUpdate(View currView, MotionEvent ev) {
// Do nothing
}
/**
* Called on a move event.
*/
- protected void onMoveUpdate(View view, float totalTranslation, float delta) {
+ protected void onMoveUpdate(View view, MotionEvent ev, float totalTranslation, float delta) {
// Do nothing
}
@@ -580,7 +585,7 @@
setTranslation(mCurrView, mTranslation + delta);
updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
- onMoveUpdate(mCurrView, mTranslation + delta, delta);
+ onMoveUpdate(mCurrView, ev, mTranslation + delta, delta);
}
break;
case MotionEvent.ACTION_UP:
@@ -635,7 +640,7 @@
return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView);
}
- protected boolean isDismissGesture(MotionEvent ev) {
+ public boolean isDismissGesture(MotionEvent ev) {
boolean falsingDetected = mCallback.isAntiFalsingNeeded();
if (mFalsingManager.isClassiferEnabled()) {
falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index af7e9b4..5237244 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -16,7 +16,6 @@
package com.android.systemui;
-import android.content.ComponentName;
import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
@@ -39,7 +38,7 @@
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.volume.VolumeDialogController;
+import com.android.systemui.volume.VolumeDialogControllerImpl;
/**
* Class factory to provide customizable SystemUI components.
@@ -89,11 +88,6 @@
return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim);
}
- public VolumeDialogController createVolumeDialogController(Context context,
- ComponentName name) {
- return new VolumeDialogController(context, name);
- }
-
public NotificationIconAreaController createNotificationIconAreaController(Context context,
StatusBar statusBar) {
return new NotificationIconAreaController(context, statusBar);
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index 67aa4dc..5a19e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -15,11 +15,12 @@
*/
package com.android.systemui.car;
-import android.content.ComponentName;
import android.content.Context;
+import android.util.ArrayMap;
+import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.SystemUIFactory;
-import com.android.systemui.volume.VolumeDialogController;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.volume.car.CarVolumeDialogController;
/**
@@ -27,8 +28,9 @@
*/
public class CarSystemUIFactory extends SystemUIFactory {
@Override
- public VolumeDialogController createVolumeDialogController(Context context,
- ComponentName name) {
- return new CarVolumeDialogController(context, name);
+ public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
+ Context context) {
+ super.injectDependencies(providers, context);
+ providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 57c75bf..2b6ea15 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -22,6 +22,7 @@
import android.app.FragmentManager.FragmentLifecycleCallbacks;
import android.app.FragmentManagerNonConfig;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
@@ -33,9 +34,7 @@
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.Dependency;
-import com.android.systemui.SystemUIApplication;
import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -48,7 +47,8 @@
private final Context mContext;
private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
private final View mRootView;
- private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges();
+ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
+ ActivityInfo.CONFIG_FONT_SCALE);
private final FragmentService mManager;
private final PluginFragmentManager mPlugins = new PluginFragmentManager();
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
index 1fb6c87..ec5f9e7 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
@@ -59,7 +59,6 @@
private static PluginManager sInstance;
- private final HandlerThread mBackgroundThread;
private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
= new ArrayMap<>();
private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
@@ -71,6 +70,7 @@
private ClassLoaderFilter mParentClassLoader;
private boolean mListening;
private boolean mHasOneShot;
+ private Looper mLooper;
public PluginManagerImpl(Context context) {
this(context, new PluginInstanceManagerFactory(),
@@ -82,8 +82,7 @@
UncaughtExceptionHandler defaultHandler) {
mContext = context;
mFactory = factory;
- mBackgroundThread = new HandlerThread("Plugins");
- mBackgroundThread.start();
+ mLooper = Dependency.get(Dependency.BG_LOOPER);
isDebuggable = debuggable;
mPluginPrefs = new PluginPrefs(mContext);
@@ -91,7 +90,7 @@
defaultHandler);
Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
if (isDebuggable) {
- new Handler(mBackgroundThread.getLooper()).post(() -> {
+ new Handler(mLooper).post(() -> {
// Plugin dependencies that don't have another good home can go here, but
// dependencies that have better places to init can happen elsewhere.
Dependency.get(PluginDependencyProvider.class)
@@ -120,7 +119,7 @@
throw new RuntimeException("Must be called from UI thread");
}
PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null,
- false, mBackgroundThread.getLooper(), cls, this);
+ false, mLooper, cls, this);
mPluginPrefs.addAction(action);
PluginInfo<T> info = p.getPlugin();
if (info != null) {
@@ -154,7 +153,7 @@
}
mPluginPrefs.addAction(action);
PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
- allowMultiple, mBackgroundThread.getLooper(), cls, this);
+ allowMultiple, mLooper, cls, this);
p.loadAll();
mPluginMap.put(listener, p);
startListening();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java
new file mode 100644
index 0000000..2c17b87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+import android.widget.RelativeLayout;
+
+public class ButtonRelativeLayout extends RelativeLayout {
+
+ public ButtonRelativeLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return Button.class.getName();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index d2ae6e9f..5c998090 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -103,6 +103,8 @@
}
mDivider.setVisibility(state.dualTarget ? View.VISIBLE : View.INVISIBLE);
mExpandIndicator.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE);
+ mLabelContainer.setContentDescription(state.dualTarget ? state.dualLabelContentDescription
+ : null);
if (state.dualTarget != mLabelContainer.isClickable()) {
mLabelContainer.setClickable(state.dualTarget);
mLabelContainer.setLongClickable(state.dualTarget);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index d552a2e..ed6e6ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -148,13 +148,7 @@
state.state = Tile.STATE_INACTIVE;
}
- CharSequence bluetoothName = state.label;
- if (connected) {
- bluetoothName = state.dualLabelContentDescription = mContext.getString(
- R.string.accessibility_bluetooth_name, state.label);
- }
- state.dualLabelContentDescription = bluetoothName;
- state.contentDescription = state.contentDescription + "," + mContext.getString(
+ state.dualLabelContentDescription = mContext.getResources().getString(
R.string.accessibility_quick_settings_open_settings, getTileLabel());
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index d3586c4..f35de68 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -198,6 +198,8 @@
if (valueChanged) {
fireToggleStateChanged(state.value);
}
+ state.dualLabelContentDescription = mContext.getResources().getString(
+ R.string.accessibility_quick_settings_open_settings, getTileLabel());
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 0270641..fde2e04 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -157,7 +157,6 @@
state.activityIn = cb.enabled && cb.activityIn;
state.activityOut = cb.enabled && cb.activityOut;
final StringBuffer minimalContentDescription = new StringBuffer();
- final StringBuffer expandedContentDescription = new StringBuffer();
final Resources r = mContext.getResources();
if (!state.value) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disabled);
@@ -175,26 +174,14 @@
minimalContentDescription.append(
mContext.getString(R.string.quick_settings_wifi_label)).append(",");
if (state.value) {
- expandedContentDescription.append(
- r.getString(R.string.quick_settings_wifi_on_label)).append(",");
if (wifiConnected) {
minimalContentDescription.append(cb.wifiSignalContentDescription).append(",");
minimalContentDescription.append(removeDoubleQuotes(cb.enabledDesc));
- expandedContentDescription.append(cb.wifiSignalContentDescription).append(",");
- expandedContentDescription.append(removeDoubleQuotes(cb.enabledDesc));
}
- } else {
- expandedContentDescription.append(
- r.getString(R.string.quick_settings_wifi_off_label));
}
- expandedContentDescription.append(",").append(
- r.getString(R.string.accessibility_quick_settings_open_settings, getTileLabel()));
- state.contentDescription = expandedContentDescription;
- CharSequence wifiName = state.label;
- if (cb.connected) {
- wifiName = r.getString(R.string.accessibility_wifi_name, state.label);
- }
- state.dualLabelContentDescription = wifiName;
+ state.contentDescription = minimalContentDescription;
+ state.dualLabelContentDescription = r.getString(
+ R.string.accessibility_quick_settings_open_settings, getTileLabel());
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 25eea95..9b75f01 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -32,7 +32,6 @@
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.KeyguardManager;
-import android.app.UiModeManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -42,7 +41,6 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -298,15 +296,9 @@
mDummyIcon.eraseColor(0xFF999999);
}
- UiModeManager uiModeManager = (UiModeManager) context.
- getSystemService(Context.UI_MODE_SERVICE);
- if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
- Collections.addAll(sRecentsBlacklist,
- res.getStringArray(R.array.recents_tv_blacklist_array));
- } else {
- Collections.addAll(sRecentsBlacklist,
- res.getStringArray(R.array.recents_blacklist_array));
- }
+ Collections.addAll(sRecentsBlacklist,
+ res.getStringArray(R.array.recents_blacklist_array));
+
mLauncherIcons = new LauncherIcons(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 00968ee..9176f57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -43,6 +43,7 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Chronometer;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RemoteViews;
@@ -50,10 +51,15 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.NotificationColorUtil;
import com.android.internal.widget.CachingIconView;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationInflater;
@@ -71,10 +77,13 @@
import java.util.ArrayList;
import java.util.List;
-public class ExpandableNotificationRow extends ActivatableNotificationView {
+public class ExpandableNotificationRow extends ActivatableNotificationView
+ implements PluginListener<NotificationMenuRowPlugin> {
private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
private static final int COLORED_DIVIDER_ALPHA = 0x7B;
+ private static final int MENU_VIEW_INDEX = 0;
+
private final NotificationInflater mNotificationInflater;
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
@@ -129,7 +138,6 @@
private int mNotificationColor;
private ExpansionLogger mLogger;
private String mLoggingKey;
- private NotificationMenuRow mMenuRow;
private NotificationGuts mGuts;
private NotificationData.Entry mEntry;
private StatusBarNotification mStatusBarNotification;
@@ -141,7 +149,7 @@
private boolean mChildrenExpanded;
private boolean mIsSummaryWithChildren;
private NotificationChildrenContainer mChildrenContainer;
- private ViewStub mMenuRowStub;
+ private NotificationMenuRowPlugin mMenuRow;
private ViewStub mGutsStub;
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
@@ -422,7 +430,7 @@
public void setAppName(String appName) {
mAppName = appName;
- if (mMenuRow != null) {
+ if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.setAppName(mAppName);
}
}
@@ -496,7 +504,7 @@
@Override
protected boolean handleSlideBack() {
- if (mMenuRow != null && mMenuRow.isVisible()) {
+ if (mMenuRow != null && mMenuRow.isMenuVisible()) {
animateTranslateNotification(0 /* targetLeft */);
return true;
}
@@ -725,12 +733,65 @@
}
public void setGutsView(MenuItem item) {
- if (mGuts != null) {
- item.gutsContent.setInteractionListener(mGuts);
- mGuts.setGutsContent(item.gutsContent);
+ if (mGuts != null && item.getGutsView() instanceof GutsContent) {
+ ((GutsContent) item.getGutsView()).setGutsParent(mGuts);
+ mGuts.setGutsContent((GutsContent) item.getGutsView());
}
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ Dependency.get(PluginManager.class).addPluginListener(this,
+ NotificationMenuRowPlugin.class, false /* Allow multiple */);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ Dependency.get(PluginManager.class).removePluginListener(this);
+ }
+
+ @Override
+ public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) {
+ boolean existed = mMenuRow.getMenuView() != null;
+ if (existed) {
+ removeView(mMenuRow.getMenuView());
+ }
+ mMenuRow = plugin;
+ if (mMenuRow.useDefaultMenuItems()) {
+ mMenuRow.setMenuItems(NotificationMenuRow.getDefaultMenuItems(mContext));
+ }
+ if (existed) {
+ createMenu();
+ }
+ }
+
+ @Override
+ public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
+ boolean existed = mMenuRow.getMenuView() != null;
+ mMenuRow = new NotificationMenuRow(mContext); // Back to default
+ if (existed) {
+ createMenu();
+ }
+ }
+
+ public NotificationMenuRowPlugin createMenu() {
+ if (mMenuRow.getMenuView() == null) {
+ mMenuRow.createMenu(this);
+ mMenuRow.setAppName(mAppName);
+ FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp);
+ }
+ return mMenuRow;
+ }
+
+
+ public NotificationMenuRowPlugin getProvider() {
+ return mMenuRow;
+ }
+
public void onDensityOrFontScaleChanged() {
initDimens();
if (mIsSummaryWithChildren) {
@@ -747,17 +808,13 @@
mGuts.setVisibility(oldGuts.getVisibility());
addView(mGuts, index);
}
- if (mMenuRow != null) {
- View oldMenu = mMenuRow;
+ View oldMenu = mMenuRow.getMenuView();
+ if (oldMenu != null) {
int menuIndex = indexOfChild(oldMenu);
removeView(oldMenu);
- mMenuRow = (NotificationMenuRow) LayoutInflater.from(mContext).inflate(
- R.layout.notification_menu_row, this, false);
- mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
+ mMenuRow.createMenu(ExpandableNotificationRow.this);
mMenuRow.setAppName(mAppName);
- mMenuRow.setVisibility(oldMenu.getVisibility());
- addView(mMenuRow, menuIndex);
-
+ addView(mMenuRow.getMenuView(), menuIndex);
}
for (NotificationContentView l : mLayouts) {
l.reInflateViews();
@@ -1061,6 +1118,7 @@
super(context, attrs);
mFalsingManager = FalsingManager.getInstance(context);
mNotificationInflater = new NotificationInflater(this);
+ mMenuRow = new NotificationMenuRow(mContext);
initDimens();
}
@@ -1113,15 +1171,6 @@
l.setExpandClickListener(mExpandClickListener);
l.setContainingNotification(this);
}
- mMenuRowStub = (ViewStub) findViewById(R.id.menu_row_stub);
- mMenuRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
- @Override
- public void onInflate(ViewStub stub, View inflated) {
- mMenuRow = (NotificationMenuRow) inflated;
- mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
- mMenuRow.setAppName(mAppName);
- }
- });
mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
@@ -1145,13 +1194,12 @@
}
});
- // Add the views that we translate to reveal the gear
+ // Add the views that we translate to reveal the menu
mTranslateableViews = new ArrayList<View>();
for (int i = 0; i < getChildCount(); i++) {
mTranslateableViews.add(getChildAt(i));
}
// Remove views that don't translate
- mTranslateableViews.remove(mMenuRowStub);
mTranslateableViews.remove(mChildrenContainerStub);
mTranslateableViews.remove(mGutsStub);
}
@@ -1166,9 +1214,7 @@
}
}
invalidateOutline();
- if (mMenuRow != null) {
- mMenuRow.resetState(true /* notify */);
- }
+ mMenuRow.resetMenu();
}
public void animateTranslateNotification(final float leftTarget) {
@@ -1194,8 +1240,8 @@
}
}
invalidateOutline();
- if (mMenuRow != null) {
- mMenuRow.updateMenuAlpha(translationX, getMeasuredWidth());
+ if (mMenuRow.getMenuView() != null) {
+ mMenuRow.onTranslationUpdate(translationX);
}
}
@@ -1232,8 +1278,8 @@
@Override
public void onAnimationEnd(Animator anim) {
- if (!cancelled && mMenuRow != null && leftTarget == 0) {
- mMenuRow.resetState(true /* notify */);
+ if (!cancelled && leftTarget == 0) {
+ mMenuRow.resetMenu();
mTranslateAnim = null;
}
}
@@ -1242,20 +1288,6 @@
return translateAnim;
}
- public float getSpaceForGear() {
- if (mMenuRow != null) {
- return mMenuRow.getSpaceForMenu();
- }
- return 0;
- }
-
- public NotificationMenuRow getSettingsRow() {
- if (mMenuRow == null) {
- mMenuRowStub.inflate();
- }
- return mMenuRow;
- }
-
public void inflateGuts() {
if (mGuts == null) {
mGutsStub.inflate();
@@ -1531,8 +1563,8 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateMaxHeights();
- if (mMenuRow != null) {
- mMenuRow.updateVerticalLocation();
+ if (mMenuRow.getMenuView() != null) {
+ mMenuRow.onHeightUpdate();
}
updateContentShiftHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index fd1317e..2713f58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -51,8 +51,8 @@
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.Set;
@@ -60,8 +60,7 @@
/**
* The guts of a notification revealed when performing a long press.
*/
-public class NotificationGuts extends FrameLayout
- implements NotificationMenuRowProvider.GutsInteractionListener {
+public class NotificationGuts extends FrameLayout {
private static final String TAG = "NotificationGuts";
private static final long CLOSE_GUTS_DELAY = 8000;
@@ -78,10 +77,35 @@
private GutsContent mGutsContent;
+ public interface GutsContent {
+
+ public void setGutsParent(NotificationGuts listener);
+
+ /**
+ * @return the view to be shown in the notification guts.
+ */
+ public View getContentView();
+
+ /**
+ * Called when the guts view have been told to close, typically after an outside
+ * interaction. Returning {@code true} here will prevent the guts view to close.
+ */
+ public boolean handleCloseControls(boolean save);
+
+ /**
+ * @return whether the notification associated with these guts is set to be removed.
+ */
+ public boolean willBeRemoved();
+ }
+
public interface OnGutsClosedListener {
public void onGutsClosed(NotificationGuts guts);
}
+ interface OnSettingsClickListener {
+ void onClick(View v, int appUid);
+ }
+
public NotificationGuts(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
@@ -163,10 +187,6 @@
}
}
- interface OnSettingsClickListener {
- void onClick(View v, int appUid);
- }
-
public void closeControls(int x, int y, boolean save) {
if (getWindowToken() == null) {
if (mListener != null) {
@@ -251,14 +271,4 @@
public boolean isExposed() {
return mExposed;
}
-
- @Override
- public void onInteraction(View view) {
- resetFalsingCheck();
- }
-
- @Override
- public void closeGuts(View view) {
- closeControls(-1 /* x */, -1 /* y */, true /* notify */);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 54921a7..0398f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -53,8 +53,6 @@
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -65,7 +63,7 @@
/**
* The guts of a notification revealed when performing a long press.
*/
-public class NotificationInfo extends LinearLayout implements GutsContent {
+public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
private static final String TAG = "InfoGuts";
private INotificationManager mINotificationManager;
@@ -79,7 +77,7 @@
private View mChannelDisabledView;
private Switch mChannelEnabledSwitch;
- private GutsInteractionListener mGutsInteractionListener;
+ private NotificationGuts mGutsContainer;
public NotificationInfo(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -282,8 +280,8 @@
// Callback when checked.
mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (mGutsInteractionListener != null) {
- mGutsInteractionListener.onInteraction(NotificationInfo.this);
+ if (mGutsContainer != null) {
+ mGutsContainer.resetFalsingCheck();
}
updateSecondaryText();
});
@@ -302,8 +300,8 @@
}
@Override
- public void setInteractionListener(GutsInteractionListener listener) {
- mGutsInteractionListener = listener;
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 534a719..5055dda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -16,276 +16,449 @@
package com.android.systemui.statusbar;
+import java.util.ArrayList;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
+import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
-import java.util.ArrayList;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener;
-
-public class NotificationMenuRow extends FrameLayout
- implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener {
+public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener {
private static final int ICON_ALPHA_ANIM_DURATION = 200;
+ private static final long SHOW_MENU_DELAY = 60;
+ private static final long SWIPE_MENU_TIMING = 200;
+
+ private static final int NOTIFICATION_INFO_INDEX = 1;
private ExpandableNotificationRow mParent;
- private OnMenuClickListener mListener;
- private NotificationMenuRowProvider mMenuProvider;
- private ArrayList<MenuItem> mMenuItems = new ArrayList<>();
+
+ private Context mContext;
+ private FrameLayout mMenuContainer;
+ private ArrayList<MenuItem> mMenuItems;
+ private OnMenuEventListener mMenuListener;
private ValueAnimator mFadeAnimator;
- private boolean mMenuFadedIn = false;
- private boolean mAnimating = false;
- private boolean mOnLeft = true;
- private boolean mDismissing = false;
- private boolean mSnapping = false;
- private boolean mIconsPlaced = false;
+ private boolean mAnimating;
+ private boolean mMenuFadedIn;
+
+ private boolean mOnLeft;
+ private boolean mIconsPlaced;
+
+ private boolean mDismissing;
+ private boolean mSnapping;
+ private float mTranslation;
private int[] mIconLocation = new int[2];
private int[] mParentLocation = new int[2];
private float mHorizSpaceForIcon;
private int mVertSpaceForIcons;
-
private int mIconPadding;
- private int mIconTint;
private float mAlpha = 0f;
+ private CheckForDrag mCheckForDrag;
+ private Handler mHandler;
+
+ private boolean mMenuSnappedTo;
+ private boolean mMenuSnappedOnLeft;
+ private boolean mShouldShowMenu;
+
+ private NotificationSwipeActionHelper mSwipeHelper;
+
public NotificationMenuRow(Context context) {
- this(context, null);
- }
-
- public NotificationMenuRow(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs);
- mMenuItems.addAll(getDefaultNotificationMenuItems());
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Dependency.get(PluginManager.class).addPluginListener(
- this, NotificationMenuRowProvider.class, false /* Allow multiple */);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(PluginManager.class).removePluginListener(this);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- final Resources res = getResources();
+ mContext = context;
+ final Resources res = context.getResources();
+ mShouldShowMenu = res.getBoolean(R.bool.config_showNotificationGear);
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
- mIconTint = res.getColor(R.color.notification_gear_color);
- updateMenu(false /* notify */);
+ mHandler = new Handler();
+ mMenuItems = getDefaultMenuItems(context);
}
- public static MenuItem getLongpressMenuItem(Context context) {
- Resources res = context.getResources();
- Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
- String settingsDescription = res.getString(R.string.notification_menu_gear_description);
- NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
- R.layout.notification_info, null, false);
- MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
- return settings;
+ @Override
+ public ArrayList<MenuItem> getMenuItems(Context context) {
+ return mMenuItems;
}
- public ArrayList<MenuItem> getDefaultNotificationMenuItems() {
- ArrayList<MenuItem> items = new ArrayList<MenuItem>();
- Resources res = getResources();
-
- Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze);
- NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext)
- .inflate(R.layout.notification_snooze, null, false);
- String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
- MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content);
- items.add(snooze);
-
- Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
- String settingsDescription = res.getString(R.string.notification_menu_gear_description);
- NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate(
- R.layout.notification_info, null, false);
- MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
- items.add(settings);
- return items;
+ @Override
+ public MenuItem getLongpressMenuItem(Context context) {
+ return mMenuItems.get(NOTIFICATION_INFO_INDEX);
}
- private void updateMenu(boolean notify) {
- removeAllViews();
- mMenuItems.clear();
- if (mMenuProvider != null) {
- mMenuItems.addAll(mMenuProvider.getMenuItems(getContext()));
- }
- mMenuItems.addAll(getDefaultNotificationMenuItems());
+ @Override
+ public void setSwipeActionHelper(NotificationSwipeActionHelper helper) {
+ mSwipeHelper = helper;
+ }
+
+ @Override
+ public void setMenuClickListener(OnMenuEventListener listener) {
+ mMenuListener = listener;
+ }
+
+ @Override
+ public void createMenu(ViewGroup parent) {
+ mParent = (ExpandableNotificationRow) parent;
+ mMenuContainer = new FrameLayout(mContext);
for (int i = 0; i < mMenuItems.size(); i++) {
- final View v = createMenuView(mMenuItems.get(i));
- mMenuItems.get(i).menuView = v;
+ addMenuView(mMenuItems.get(i), mMenuContainer);
}
- resetState(notify);
+ resetState(false);
}
- private View createMenuView(MenuItem item) {
- AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext());
- addView(iv);
- iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
- iv.setImageDrawable(item.icon);
- iv.setOnClickListener(this);
- iv.setColorFilter(mIconTint);
- iv.setAlpha(mAlpha);
- FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams();
- lp.width = (int) mHorizSpaceForIcon;
- lp.height = (int) mHorizSpaceForIcon;
- return iv;
+ @Override
+ public boolean isMenuVisible() {
+ return mAlpha > 0;
}
- public void resetState(boolean notify) {
+ @Override
+ public View getMenuView() {
+ return mMenuContainer;
+ }
+
+ @Override
+ public void resetMenu() {
+ resetState(true);
+ }
+
+ private void resetState(boolean notify) {
setMenuAlpha(0f);
mIconsPlaced = false;
mMenuFadedIn = false;
mAnimating = false;
mSnapping = false;
mDismissing = false;
- setMenuLocation(mOnLeft ? 1 : -1 /* on left */);
- if (mListener != null && notify) {
- mListener.onMenuReset(mParent);
+ mMenuSnappedTo = false;
+ setMenuLocation();
+ if (mMenuListener != null && notify) {
+ mMenuListener.onMenuReset(mParent);
}
}
- public void setMenuClickListener(OnMenuClickListener listener) {
- mListener = listener;
+ @Override
+ public boolean onTouchEvent(View view, MotionEvent ev, float velocity) {
+ final int action = ev.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mSnapping = false;
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ mHandler.removeCallbacks(mCheckForDrag);
+ mCheckForDrag = null;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ mSnapping = false;
+ // If the menu is visible and the movement is towards it it's not a location change.
+ boolean locationChange = isTowardsMenu(mTranslation)
+ ? false : isMenuLocationChange();
+ if (locationChange) {
+ // Don't consider it "snapped" if location has changed.
+ mMenuSnappedTo = false;
+
+ // Changed directions, make sure we check to fade in icon again.
+ if (!mHandler.hasCallbacks(mCheckForDrag)) {
+ // No check scheduled, set null to schedule a new one.
+ mCheckForDrag = null;
+ } else {
+ // Check scheduled, reset alpha and update location; check will fade it in
+ setMenuAlpha(0f);
+ setMenuLocation();
+ }
+ }
+ if (mShouldShowMenu
+ && !NotificationStackScrollLayout.isPinnedHeadsUp(view)
+ && !mParent.areGutsExposed()
+ && (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag))) {
+ // Only show the menu if we're not a heads up view and guts aren't exposed.
+ mCheckForDrag = new CheckForDrag();
+ mHandler.postDelayed(mCheckForDrag, SHOW_MENU_DELAY);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ return handleUpEvent(ev, view, velocity);
+ }
+ return false;
}
- public void setNotificationRowParent(ExpandableNotificationRow parent) {
- mParent = parent;
- setMenuLocation(mOnLeft ? 1 : -1);
+ private boolean handleUpEvent(MotionEvent ev, View animView, float velocity) {
+ // If the menu should not be shown, then there is no need to check if the a swipe
+ // should result in a snapping to the menu. As a result, just check if the swipe
+ // was enough to dismiss the notification.
+ if (!mShouldShowMenu) {
+ if (mSwipeHelper.isDismissGesture(ev)) {
+ dismiss(animView, velocity);
+ } else {
+ snapBack(animView, velocity);
+ }
+ return true;
+ }
+
+ final boolean gestureTowardsMenu = isTowardsMenu(velocity);
+ final boolean gestureFastEnough =
+ mSwipeHelper.getMinDismissVelocity() <= Math.abs(velocity);
+ final boolean gestureFarEnough =
+ mSwipeHelper.swipedFarEnough(mTranslation, mParent.getWidth());
+ final double timeForGesture = ev.getEventTime() - ev.getDownTime();
+ final boolean showMenuForSlowOnGoing = !mParent.canViewBeDismissed()
+ && timeForGesture >= SWIPE_MENU_TIMING;
+
+ final float targetLeft = mOnLeft ? getSpaceForMenu() : -getSpaceForMenu();
+ if (mMenuSnappedTo && isMenuVisible()) {
+ if (mMenuSnappedOnLeft == mOnLeft) {
+ boolean coveringMenu = Math.abs(mTranslation) <= getSpaceForMenu() * 0.6f;
+ if (gestureTowardsMenu || coveringMenu) {
+ // Gesture is towards or covering the menu or a dismiss
+ snapBack(animView, 0);
+ } else if (mSwipeHelper.isDismissGesture(ev)) {
+ dismiss(animView, velocity);
+ } else {
+ // Didn't move enough to dismiss or cover, snap to the menu
+ showMenu(animView, targetLeft, velocity);
+ }
+ } else if ((!gestureFastEnough && swipedEnoughToShowMenu())
+ || (gestureTowardsMenu && !gestureFarEnough)) {
+ // The menu has been snapped to previously, however, the menu is now on the
+ // other side. If gesture is towards menu and not too far snap to the menu.
+ showMenu(animView, targetLeft, velocity);
+ } else if (mSwipeHelper.isDismissGesture(ev)) {
+ dismiss(animView, velocity);
+ } else {
+ snapBack(animView, velocity);
+ }
+ } else if (((!gestureFastEnough || showMenuForSlowOnGoing)
+ && swipedEnoughToShowMenu())
+ || gestureTowardsMenu) {
+ // Menu has not been snapped to previously and this is menu revealing gesture
+ showMenu(animView, targetLeft, velocity);
+ } else if (mSwipeHelper.isDismissGesture(ev)) {
+ dismiss(animView, velocity);
+ } else {
+ snapBack(animView, velocity);
+ }
+ return true;
}
+ private void showMenu(View animView, float targetLeft, float velocity) {
+ mMenuSnappedTo = true;
+ mMenuSnappedOnLeft = mOnLeft;
+ mMenuListener.onMenuShown(animView);
+ mSwipeHelper.snap(animView, targetLeft, velocity);
+ }
+
+ private void snapBack(View animView, float velocity) {
+ mMenuSnappedTo = false;
+ mSnapping = true;
+ mSwipeHelper.snap(animView, 0 /* leftTarget */, velocity);
+ }
+
+ private void dismiss(View animView, float velocity) {
+ mMenuSnappedTo = false;
+ mDismissing = true;
+ mSwipeHelper.dismiss(animView, velocity);
+ }
+
+ private boolean swipedEnoughToShowMenu() {
+ // If the notification can't be dismissed then how far it can move is
+ // restricted -- reduce the distance it needs to move in this case.
+ final float multiplier = mParent.canViewBeDismissed() ? 0.4f : 0.2f;
+ final float snapBackThreshold = getSpaceForMenu() * multiplier;
+ return !mSwipeHelper.swipedFarEnough(0, 0) && isMenuVisible() && (mOnLeft
+ ? mTranslation > snapBackThreshold
+ : mTranslation < -snapBackThreshold);
+ }
+
+ /**
+ * Returns whether the gesture is towards the menu location or not.
+ */
+ private boolean isTowardsMenu(float movement) {
+ return isMenuVisible()
+ && ((mOnLeft && movement <= 0)
+ || (!mOnLeft && movement >= 0));
+ }
+
+ @Override
public void setAppName(String appName) {
- Resources res = getResources();
+ if (appName == null) {
+ return;
+ }
+ Resources res = mContext.getResources();
final int count = mMenuItems.size();
for (int i = 0; i < count; i++) {
MenuItem item = mMenuItems.get(i);
String description = String.format(
res.getString(R.string.notification_menu_accessibility),
- appName, item.menuDescription);
- item.menuView.setContentDescription(description);
+ appName, item.getContentDescription());
+ View menuView = item.getMenuView();
+ if (menuView != null) {
+ menuView.setContentDescription(description);
+ }
}
}
- public ExpandableNotificationRow getNotificationParent() {
- return mParent;
- }
-
- public void setMenuAlpha(float alpha) {
- mAlpha = alpha;
- if (alpha == 0) {
- mMenuFadedIn = false; // Can fade in again once it's gone.
- setVisibility(View.INVISIBLE);
+ @Override
+ public void onHeightUpdate() {
+ if (mParent == null || mMenuItems.size() == 0) {
+ return;
+ }
+ int parentHeight = mParent.getCollapsedHeight();
+ float translationY;
+ if (parentHeight < mVertSpaceForIcons) {
+ translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
} else {
- setVisibility(View.VISIBLE);
+ translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
}
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- getChildAt(i).setAlpha(mAlpha);
- }
+ mMenuContainer.setTranslationY(translationY);
}
- /**
- * Returns whether the menu is displayed on the left side of the view or not.
- */
- public boolean isMenuOnLeft() {
- return mOnLeft;
- }
-
- /**
- * Returns the horizontal space in pixels required to display the menu.
- */
- public float getSpaceForMenu() {
- return mHorizSpaceForIcon * getChildCount();
- }
-
- /**
- * Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is
- * visible.
- */
- public boolean isVisible() {
- return mAlpha > 0;
- }
-
- public void cancelFadeAnimator() {
- if (mFadeAnimator != null) {
- mFadeAnimator.cancel();
- }
- }
-
- public void updateMenuAlpha(final float transX, final float size) {
+ @Override
+ public void onTranslationUpdate(float translation) {
+ mTranslation = translation;
if (mAnimating || !mMenuFadedIn) {
// Don't adjust when animating, or if the menu hasn't been shown yet.
return;
}
-
- final float fadeThreshold = size * 0.3f;
- final float absTrans = Math.abs(transX);
+ final float fadeThreshold = mParent.getWidth() * 0.3f;
+ final float absTrans = Math.abs(translation);
float desiredAlpha = 0;
-
if (absTrans == 0) {
desiredAlpha = 0;
} else if (absTrans <= fadeThreshold) {
desiredAlpha = 1;
} else {
- desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
+ desiredAlpha = 1 - ((absTrans - fadeThreshold) / (mParent.getWidth() - fadeThreshold));
}
setMenuAlpha(desiredAlpha);
}
- public void fadeInMenu(final boolean fromLeft, final float transX,
- final float notiThreshold) {
+ @Override
+ public void onClick(View v) {
+ if (mMenuListener == null) {
+ // Nothing to do
+ return;
+ }
+ v.getLocationOnScreen(mIconLocation);
+ mParent.getLocationOnScreen(mParentLocation);
+ final int centerX = (int) (mHorizSpaceForIcon / 2);
+ final int centerY = v.getHeight() / 2;
+ final int x = mIconLocation[0] - mParentLocation[0] + centerX;
+ final int y = mIconLocation[1] - mParentLocation[1] + centerY;
+ final int index = mMenuContainer.indexOfChild(v);
+ mMenuListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
+ }
+
+ private boolean isMenuLocationChange() {
+ boolean onLeft = mTranslation > mIconPadding;
+ boolean onRight = mTranslation < -mIconPadding;
+ if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
+ return true;
+ }
+ return false;
+ }
+
+ private void setMenuLocation() {
+ boolean showOnLeft = mTranslation > 0;
+ if ((mIconsPlaced && showOnLeft == mOnLeft) || mSnapping || mParent == null) {
+ // Do nothing
+ return;
+ }
+ final boolean isRtl = mParent.isLayoutRtl();
+ final int count = mMenuContainer.getChildCount();
+ final int width = mParent.getWidth();
+ for (int i = 0; i < count; i++) {
+ final View v = mMenuContainer.getChildAt(i);
+ final float left = isRtl
+ ? -(width - mHorizSpaceForIcon * (i + 1))
+ : i * mHorizSpaceForIcon;
+ final float right = isRtl
+ ? -i * mHorizSpaceForIcon
+ : width - (mHorizSpaceForIcon * (i + 1));
+ v.setTranslationX(showOnLeft ? left : right);
+ }
+ mOnLeft = showOnLeft;
+ mIconsPlaced = true;
+ }
+
+ private void setMenuAlpha(float alpha) {
+ mAlpha = alpha;
+ if (mMenuContainer == null) {
+ return;
+ }
+ if (alpha == 0) {
+ mMenuFadedIn = false; // Can fade in again once it's gone.
+ mMenuContainer.setVisibility(View.INVISIBLE);
+ } else {
+ mMenuContainer.setVisibility(View.VISIBLE);
+ }
+ final int count = mMenuContainer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ mMenuContainer.getChildAt(i).setAlpha(mAlpha);
+ }
+ }
+
+ /**
+ * Returns the horizontal space in pixels required to display the menu.
+ */
+ private float getSpaceForMenu() {
+ return mHorizSpaceForIcon * mMenuContainer.getChildCount();
+ }
+
+ private final class CheckForDrag implements Runnable {
+ @Override
+ public void run() {
+ final float absTransX = Math.abs(mTranslation);
+ final float bounceBackToMenuWidth = getSpaceForMenu();
+ final float notiThreshold = mParent.getWidth() * 0.4f;
+ if ((!isMenuVisible() || isMenuLocationChange())
+ && absTransX >= bounceBackToMenuWidth * 0.4
+ && absTransX < notiThreshold) {
+ fadeInMenu(notiThreshold);
+ }
+ }
+ }
+
+ private void fadeInMenu(final float notiThreshold) {
if (mDismissing || mAnimating) {
return;
}
- if (isMenuLocationChange(transX)) {
+ if (isMenuLocationChange()) {
setMenuAlpha(0f);
}
- setMenuLocation((int) transX);
+ final float transX = mTranslation;
+ final boolean fromLeft = mTranslation > 0;
+ setMenuLocation();
mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1);
mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float absTrans = Math.abs(transX);
- boolean pastGear = (fromLeft && transX <= notiThreshold)
+ boolean pastMenu = (fromLeft && transX <= notiThreshold)
|| (!fromLeft && absTrans <= notiThreshold);
- if (pastGear && !mMenuFadedIn) {
+ if (pastMenu && !mMenuFadedIn) {
setMenuAlpha((float) animation.getAnimatedValue());
}
}
@@ -313,91 +486,77 @@
mFadeAnimator.start();
}
- public void updateVerticalLocation() {
- if (mParent == null || mMenuItems.size() == 0) {
- return;
- }
- int parentHeight = mParent.getCollapsedHeight();
- float translationY;
- if (parentHeight < mVertSpaceForIcons) {
- translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
- } else {
- translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
- }
- setTranslationY(translationY);
- }
-
@Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- mIconsPlaced = false;
- setMenuLocation(mOnLeft ? 1 : -1);
+ public void setMenuItems(ArrayList<MenuItem> items) {
+ // Do nothing we use our own for now.
+ // TODO -- handle / allow custom menu items!
}
- public void setMenuLocation(int translation) {
- boolean onLeft = translation > 0;
- if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) {
- // Do nothing
- return;
+ public static ArrayList<MenuItem> getDefaultMenuItems(Context context) {
+ ArrayList<MenuItem> items = new ArrayList<MenuItem>();
+ Resources res = context.getResources();
+
+ NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context)
+ .inflate(R.layout.notification_snooze, null, false);
+ String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
+ MenuItem snooze = new NotificationMenuItem(context, snoozeDescription, content,
+ R.drawable.ic_snooze);
+ items.add(snooze);
+
+ String settingsDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
+ R.layout.notification_info, null, false);
+ MenuItem settings = new NotificationMenuItem(context, settingsDescription, settingsContent,
+ R.drawable.ic_settings);
+ items.add(settings);
+ return items;
+ }
+
+ private void addMenuView(MenuItem item, ViewGroup parent) {
+ View menuView = item.getMenuView();
+ if (menuView != null) {
+ parent.addView(menuView);
+ menuView.setOnClickListener(this);
+ FrameLayout.LayoutParams lp = (LayoutParams) menuView.getLayoutParams();
+ lp.width = (int) mHorizSpaceForIcon;
+ lp.height = (int) mHorizSpaceForIcon;
+ menuView.setLayoutParams(lp);
}
- final boolean isRtl = mParent.isLayoutRtl();
- final int count = getChildCount();
- final int width = getWidth();
- for (int i = 0; i < count; i++) {
- final View v = getChildAt(i);
- final float left = isRtl
- ? -(width - mHorizSpaceForIcon * (i + 1))
- : i * mHorizSpaceForIcon;
- final float right = isRtl
- ? -i * mHorizSpaceForIcon
- : width - (mHorizSpaceForIcon * (i + 1));
- v.setTranslationX(onLeft ? left : right);
+ }
+
+ public static class NotificationMenuItem implements MenuItem {
+ View mMenuView;
+ GutsContent mGutsContent;
+ String mContentDescription;
+
+ public NotificationMenuItem(Context context, String s, GutsContent content, int iconResId) {
+ Resources res = context.getResources();
+ int padding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
+ int tint = res.getColor(R.color.notification_gear_color);
+ AlphaOptimizedImageView iv = new AlphaOptimizedImageView(context);
+ iv.setPadding(padding, padding, padding, padding);
+ Drawable icon = context.getResources().getDrawable(iconResId);
+ iv.setImageDrawable(icon);
+ iv.setColorFilter(tint);
+ iv.setAlpha(1f);
+ mMenuView = iv;
+ mContentDescription = s;
+ mGutsContent = content;
}
- mOnLeft = onLeft;
- mIconsPlaced = true;
- }
- public boolean isMenuLocationChange(float translation) {
- boolean onLeft = translation > mIconPadding;
- boolean onRight = translation < -mIconPadding;
- if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
- return true;
+ @Override
+ public View getMenuView() {
+ return mMenuView;
}
- return false;
- }
- public void setDismissing() {
- mDismissing = true;
- }
-
- public void setSnapping(boolean snapping) {
- mSnapping = snapping;
- }
-
- @Override
- public void onClick(View v) {
- if (mListener == null) {
- // Nothing to do
- return;
+ @Override
+ public View getGutsView() {
+ return mGutsContent.getContentView();
}
- v.getLocationOnScreen(mIconLocation);
- mParent.getLocationOnScreen(mParentLocation);
- final int centerX = (int) (mHorizSpaceForIcon / 2);
- final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2;
- final int x = mIconLocation[0] - mParentLocation[0] + centerX;
- final int y = mIconLocation[1] - mParentLocation[1] + centerY;
- final int index = indexOfChild(v);
- mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
- }
- @Override
- public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) {
- mMenuProvider = plugin;
- updateMenu(false /* notify */);
+ @Override
+ public String getContentDescription() {
+ return mContentDescription;
+ }
}
-
- @Override
- public void onPluginDisconnected(NotificationMenuRowProvider plugin) {
- mMenuProvider = null;
- updateMenu(false /* notify */);
- }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 6b1e62d9..0de3e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,14 +16,10 @@
*/
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.List;
-import java.util.concurrent.TimeUnit;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import android.content.Context;
import android.content.res.Resources;
@@ -33,23 +29,18 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.RadioGroup;
-import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.TextView;
-import android.widget.Toast;
import com.android.systemui.R;
public class NotificationSnooze extends LinearLayout
- implements NotificationMenuRowProvider.SnoozeGutsContent, View.OnClickListener {
+ implements NotificationGuts.GutsContent, View.OnClickListener {
private static final int MAX_ASSISTANT_SUGGESTIONS = 2;
- private GutsInteractionListener mGutsInteractionListener;
- private SnoozeListener mSnoozeListener;
+ private NotificationGuts mGutsContainer;
+ private NotificationSwipeActionHelper mSnoozeListener;
private StatusBarNotification mSbn;
private TextView mSelectedOptionText;
@@ -155,8 +146,8 @@
@Override
public void onClick(View v) {
- if (mGutsInteractionListener != null) {
- mGutsInteractionListener.onInteraction(this);
+ if (mGutsContainer != null) {
+ mGutsContainer.resetFalsingCheck();
}
final int id = v.getId();
final SnoozeOption tag = (SnoozeOption) v.getTag();
@@ -172,7 +163,7 @@
private void undoSnooze() {
mSelectedOption = null;
- mGutsInteractionListener.closeGuts(this);
+ mGutsContainer.closeControls(-1 /* x */, -1 /* y */, true /* notify */);
}
@Override
@@ -185,18 +176,16 @@
return this;
}
- @Override
public void setStatusBarNotification(StatusBarNotification sbn) {
mSbn = sbn;
}
@Override
- public void setInteractionListener(GutsInteractionListener listener) {
- mGutsInteractionListener = listener;
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
}
- @Override
- public void setSnoozeListener(SnoozeListener listener) {
+ public void setSnoozeListener(NotificationSwipeActionHelper listener) {
mSnoozeListener = listener;
}
@@ -206,7 +195,7 @@
// then we commit the snooze action.
if (mSnoozeListener != null && mSelectedOption != null) {
mSnoozing = true;
- mSnoozeListener.snoozeNotification(mSbn, mSelectedOption);
+ mSnoozeListener.snooze(mSbn, mSelectedOption);
return true;
} else {
// Reset the view once it's closed
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index ef42b2f..bccc5d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -179,10 +179,6 @@
setRestingAlpha(
anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT);
setImageDrawable(icon);
- String contentDescription = getResources().getString(anyFingerprintIcon
- ? R.string.accessibility_unlock_button_fingerprint
- : R.string.accessibility_unlock_button);
- setContentDescription(contentDescription);
mHasFingerPrintIcon = anyFingerprintIcon;
if (animation != null && isAnim) {
animation.forceAnimationOnUI();
@@ -225,13 +221,13 @@
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (mHasFingerPrintIcon) {
- // Avoid that the button description is also spoken
- info.setClassName(LockIcon.class.getName());
AccessibilityNodeInfo.AccessibilityAction unlock
= new AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfo.ACTION_CLICK,
getContext().getString(R.string.accessibility_unlock_without_fingerprint));
info.addAction(unlock);
+ info.setHintText(getContext().getString(
+ R.string.accessibility_waiting_for_fingerprint));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 101aee4..b82b113 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -140,8 +140,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTileHost;
@@ -241,8 +240,8 @@
import com.android.systemui.RecentsComponent;
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeGutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -255,8 +254,8 @@
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
- OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener,
- CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
+ OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
+ ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
ExpandableNotificationRow.OnExpandClickListener {
public static final boolean MULTIUSER_DEBUG = false;
@@ -5069,15 +5068,6 @@
}
- public SnoozeListener getSnoozeListener() {
- return this;
- }
-
- @Override
- public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption) {
- setNotificationSnoozed(sbn, snoozeOption);
- }
-
// Begin Extra BaseStatusBar methods.
protected CommandQueue mCommandQueue;
@@ -5745,7 +5735,7 @@
}, false /* afterKeyguardGone */);
}
- protected void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
+ public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
if (snoozeOption.criterion != null) {
mNotificationListener.snoozeNotification(sbn.getKey(), snoozeOption.criterion.getId());
} else {
@@ -5768,20 +5758,22 @@
mGutsMenuItem = null;
});
- if (item.gutsContent instanceof SnoozeGutsContent) {
- ((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener());
- ((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn);
- ((NotificationSnooze) item.gutsContent).setSnoozeOptions(row.getEntry().snoozeCriteria);
+ View gutsView = item.getGutsView();
+ if (gutsView instanceof NotificationSnooze) {
+ NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
+ snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+ snoozeGuts.setStatusBarNotification(sbn);
+ snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
}
- if (item.gutsContent instanceof NotificationInfo) {
+ if (gutsView instanceof NotificationInfo) {
final UserHandle userHandle = sbn.getUser();
PackageManager pmUser = getPackageManagerForUser(mContext,
userHandle.getIdentifier());
final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
final String pkg = sbn.getPackageName();
- NotificationInfo info = (NotificationInfo) item.gutsContent;
+ NotificationInfo info = (NotificationInfo) gutsView;
final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
NotificationChannel channel, int appUid) -> {
mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
@@ -5890,7 +5882,7 @@
+ "window");
return;
}
- dismissPopups(-1 /* x */, -1 /* y */, false /* resetGear */,
+ dismissPopups(-1 /* x */, -1 /* y */, false /* resetMenu */,
false /* animate */);
guts.setVisibility(View.VISIBLE);
final double horz = Math.max(guts.getWidth() - x, x);
@@ -5904,7 +5896,7 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- // Move the notification view back over the gear
+ // Move the notification view back over the menu
row.resetTranslation();
}
});
@@ -5930,19 +5922,19 @@
}
public void dismissPopups() {
- dismissPopups(-1 /* x */, -1 /* y */, true /* resetGear */, false /* animate */);
+ dismissPopups(-1 /* x */, -1 /* y */, true /* resetMenu */, false /* animate */);
}
private void dismissPopups(int x, int y) {
- dismissPopups(x, y, true /* resetGear */, false /* animate */);
+ dismissPopups(x, y, true /* resetMenu */, false /* animate */);
}
- public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
+ public void dismissPopups(int x, int y, boolean resetMenu, boolean animate) {
if (mNotificationGutsExposed != null) {
mNotificationGutsExposed.closeControls(x, y, true /* save */);
}
- if (resetGear) {
- mStackScroller.resetExposedGearView(animate, true /* force */);
+ if (resetMenu) {
+ mStackScroller.resetExposedMenuView(animate, true /* force */);
}
}
@@ -6299,8 +6291,8 @@
return;
}
- // Check if the notification is displaying the gear, if so slide notification back
- if (row.getSettingsRow() != null && row.getSettingsRow().isVisible()) {
+ // Check if the notification is displaying the menu, if so slide notification back
+ if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
row.animateTranslateNotification(0);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index d3b336b..7d2d0df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -37,6 +37,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
+import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
@@ -63,15 +64,15 @@
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationGuts;
-import com.android.systemui.statusbar.NotificationMenuRow;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
@@ -96,7 +97,7 @@
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
- NotificationMenuRowProvider.OnMenuClickListener, ScrollContainer,
+ NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
VisibilityLocationProvider {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
@@ -228,10 +229,9 @@
private int mMaxScrollAfterExpand;
private SwipeHelper.LongPressListener mLongPressListener;
- private NotificationMenuRow mCurrIconRow;
+ private NotificationMenuRowPlugin mCurrMenuRow;
private View mTranslatingParentView;
- private View mGearExposedView;
- private boolean mShouldShowGear;
+ private View mMenuExposedView;
/**
* Should in this touch motion only be scrolling allowed? It's true when the scroller was
@@ -397,7 +397,6 @@
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
initView(context);
mFalsingManager = FalsingManager.getInstance(context);
- mShouldShowGear = res.getBoolean(R.bool.config_showNotificationGear);
mShouldDrawNotificationBackground =
res.getBoolean(R.bool.config_drawNotificationBackground);
@@ -410,6 +409,10 @@
}
}
+ public NotificationSwipeActionHelper getSwipeActionHelper() {
+ return mSwipeHelper;
+ }
+
@Override
public void onMenuClicked(View view, int x, int y, MenuItem item) {
if (mLongPressListener == null) {
@@ -426,13 +429,22 @@
@Override
public void onMenuReset(View row) {
if (mTranslatingParentView != null && row == mTranslatingParentView) {
- mSwipeHelper.setSnappedToGear(false);
- mGearExposedView = null;
+ mMenuExposedView = null;
mTranslatingParentView = null;
}
}
@Override
+ public void onMenuShown(View row) {
+ mMenuExposedView = mTranslatingParentView;
+ if (row instanceof ExpandableNotificationRow) {
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
+ ((ExpandableNotificationRow) row).getStatusBarNotification()
+ .getPackageName());
+ }
+ mSwipeHelper.onMenuShown(row);
+ }
+
protected void onDraw(Canvas canvas) {
if (mShouldDrawNotificationBackground && mCurrentBounds.top < mCurrentBounds.bottom) {
canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
@@ -936,9 +948,9 @@
// We start the swipe and snap back in the same frame, we don't want any animation
mDragAnimPendingChildren.remove(animView);
}
- if (mCurrIconRow != null && targetLeft == 0) {
- mCurrIconRow.resetState(true /* notify */);
- mCurrIconRow = null;
+ if (mCurrMenuRow != null && targetLeft == 0) {
+ mCurrMenuRow.resetMenu();
+ mCurrMenuRow = null;
}
}
@@ -999,10 +1011,10 @@
ExpandableNotificationRow parent = row.getNotificationParent();
if (parent != null && parent.areChildrenExpanded()
&& (parent.areGutsExposed()
- || mGearExposedView == parent
+ || mMenuExposedView == parent
|| (parent.getNotificationChildren().size() == 1
&& parent.isClearable()))) {
- // In this case the group is expanded and showing the gear for the
+ // In this case the group is expanded and showing the menu for the
// group, further interaction should apply to the group, not any
// child notifications so we use the parent of the child. We also do the same
// if we only have a single child.
@@ -1261,8 +1273,8 @@
public void snapViewIfNeeded(ExpandableNotificationRow child) {
boolean animate = mIsExpanded || isPinnedHeadsUp(child);
- // If the child is showing the gear to go to settings, snap to that
- float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0;
+ // If the child is showing the notification menu snap to that
+ float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
}
@@ -4196,15 +4208,11 @@
void flingTopOverscroll(float velocity, boolean open);
}
- private class NotificationSwipeHelper extends SwipeHelper {
- private static final long SHOW_GEAR_DELAY = 60;
- private static final long COVER_GEAR_DELAY = 4000;
- private static final long SWIPE_GEAR_TIMING = 200;
- private CheckForDrag mCheckForDrag;
+ private class NotificationSwipeHelper extends SwipeHelper
+ implements NotificationSwipeActionHelper {
+ private static final long COVER_MENU_DELAY = 4000;
private Runnable mFalsingCheck;
private Handler mHandler;
- private boolean mGearSnappedTo;
- private boolean mGearSnappedOnLeft;
public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
super(swipeDirection, callback, context);
@@ -4212,69 +4220,46 @@
mFalsingCheck = new Runnable() {
@Override
public void run() {
- resetExposedGearView(true /* animate */, true /* force */);
+ resetExposedMenuView(true /* animate */, true /* force */);
}
};
}
@Override
- public void onDownUpdate(View currView) {
- // Set the active view
+ public void onDownUpdate(View currView, MotionEvent ev) {
mTranslatingParentView = currView;
-
- // Reset check for drag gesture
- cancelCheckForDrag();
- if (mCurrIconRow != null) {
- mCurrIconRow.setSnapping(false);
+ mCurrMenuRow = null;
+ if (mCurrMenuRow != null) {
+ mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */);
}
- mCheckForDrag = null;
- mCurrIconRow = null;
mHandler.removeCallbacks(mFalsingCheck);
- // Slide back any notifications that might be showing a gear
- resetExposedGearView(true /* animate */, false /* force */);
+ // Slide back any notifications that might be showing a menu
+ resetExposedMenuView(true /* animate */, false /* force */);
if (currView instanceof ExpandableNotificationRow) {
- // Set the listener for the current row's gear
- mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
- mCurrIconRow.setMenuClickListener(NotificationStackScrollLayout.this);
+ ExpandableNotificationRow row = (ExpandableNotificationRow) currView;
+ mCurrMenuRow = row.createMenu();
+ mCurrMenuRow.setSwipeActionHelper(NotificationSwipeHelper.this);
+ mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this);
}
}
@Override
- public void onMoveUpdate(View view, float translation, float delta) {
+ public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) {
mHandler.removeCallbacks(mFalsingCheck);
-
- if (mCurrIconRow != null) {
- mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
-
- // If the gear is visible and the movement is towards it it's not a location change.
- boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isMenuOnLeft();
- boolean locationChange = isTowardsGear(translation, onLeft)
- ? false : mCurrIconRow.isMenuLocationChange(translation);
- if (locationChange) {
- // Don't consider it "snapped" if location has changed.
- setSnappedToGear(false);
-
- // Changed directions, make sure we check to fade in icon again.
- if (!mHandler.hasCallbacks(mCheckForDrag)) {
- // No check scheduled, set null to schedule a new one.
- mCheckForDrag = null;
- } else {
- // Check scheduled, reset alpha and update location; check will fade it in
- mCurrIconRow.setMenuAlpha(0f);
- mCurrIconRow.setMenuLocation((int) translation);
- }
- }
+ if (mCurrMenuRow != null) {
+ mCurrMenuRow.onTouchEvent(view, ev, 0 /* velocity */);
}
+ }
- final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
- && ((ExpandableNotificationRow) view).areGutsExposed();
-
- if (mShouldShowGear && !isPinnedHeadsUp(view) && !gutsExposed) {
- // Only show the gear if we're not a heads up view and guts aren't exposed.
- checkForDrag();
+ @Override
+ public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
+ float translation) {
+ if (mCurrMenuRow != null) {
+ return mCurrMenuRow.onTouchEvent(animView, ev, velocity);
}
+ return false;
}
@Override
@@ -4286,7 +4271,7 @@
// of the panel early.
handleChildDismissed(view);
}
- handleGearCoveredOrDismissed();
+ handleMenuCoveredOrDismissed();
}
@Override
@@ -4294,121 +4279,21 @@
super.snapChild(animView, targetLeft, velocity);
onDragCancelled(animView);
if (targetLeft == 0) {
- handleGearCoveredOrDismissed();
- }
- }
-
- private void handleGearCoveredOrDismissed() {
- cancelCheckForDrag();
- setSnappedToGear(false);
- if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
- mGearExposedView = null;
+ handleMenuCoveredOrDismissed();
}
}
@Override
- public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
- float translation) {
- if (mCurrIconRow == null) {
- cancelCheckForDrag();
- return false; // Let SwipeHelper handle it.
- }
-
- // If the gear icon should not be shown, then there is no need to check if the a swipe
- // should result in a snapping to the gear icon. As a result, just check if the swipe
- // was enough to dismiss the notification.
- if (!mShouldShowGear) {
- dismissOrSnapBack(animView, velocity, ev);
- return true;
- }
-
- boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isMenuOnLeft());
- boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
- final double timeForGesture = ev.getEventTime() - ev.getDownTime();
- final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
- && timeForGesture >= SWIPE_GEAR_TIMING;
-
- if (mGearSnappedTo && mCurrIconRow.isVisible()) {
- if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) {
- boolean coveringGear =
- Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
- if (gestureTowardsGear || coveringGear) {
- // Gesture is towards or covering the gear
- snapChild(animView, 0 /* leftTarget */, velocity);
- } else if (isDismissGesture(ev)) {
- // Gesture is a dismiss that's not towards the gear
- dismissChild(animView, velocity,
- !swipedFastEnough() /* useAccelerateInterpolator */);
- } else {
- // Didn't move enough to dismiss or cover, snap to the gear
- snapToGear(animView, velocity);
- }
- } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
- || (gestureTowardsGear && !swipedFarEnough())) {
- // The gear has been snapped to previously, however, the gear is now on the
- // other side. If gesture is towards gear and not too far snap to the gear.
- snapToGear(animView, velocity);
- } else {
- dismissOrSnapBack(animView, velocity, ev);
- }
- } else if (((!gestureFastEnough || showGearForSlowOnGoing)
- && swipedEnoughToShowGear(animView))
- || gestureTowardsGear) {
- // Gear has not been snapped to previously and this is gear revealing gesture
- snapToGear(animView, velocity);
- } else {
- dismissOrSnapBack(animView, velocity, ev);
- }
- return true;
+ public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption) {
+ mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
}
- private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
- if (isDismissGesture(ev)) {
- dismissChild(animView, velocity,
- !swipedFastEnough() /* useAccelerateInterpolator */);
- } else {
- snapChild(animView, 0 /* leftTarget */, velocity);
+ private void handleMenuCoveredOrDismissed() {
+ if (mMenuExposedView != null && mMenuExposedView == mTranslatingParentView) {
+ mMenuExposedView = null;
}
}
- private void snapToGear(View animView, float velocity) {
- final float snapBackThreshold = getSpaceForGear(animView);
- final float target = mCurrIconRow.isMenuOnLeft() ? snapBackThreshold
- : -snapBackThreshold;
- mGearExposedView = mTranslatingParentView;
- if (animView instanceof ExpandableNotificationRow) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
- ((ExpandableNotificationRow) animView).getStatusBarNotification()
- .getPackageName());
- }
- if (mCurrIconRow != null) {
- mCurrIconRow.setSnapping(true);
- setSnappedToGear(true);
- }
- onDragCancelled(animView);
-
- // If we're on the lockscreen we want to false this.
- if (isAntiFalsingNeeded()) {
- mHandler.removeCallbacks(mFalsingCheck);
- mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
- }
- super.snapChild(animView, target, velocity);
- }
-
- private boolean swipedEnoughToShowGear(View animView) {
- if (mTranslatingParentView == null) {
- return false;
- }
- // If the notification can't be dismissed then how far it can move is
- // restricted -- reduce the distance it needs to move in this case.
- final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
- final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
- final float translation = getTranslation(animView);
- return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isMenuOnLeft()
- ? translation > snapBackThreshold
- : translation < -snapBackThreshold);
- }
-
@Override
public Animator getViewTranslationAnimator(View v, float target,
AnimatorUpdateListener listener) {
@@ -4429,6 +4314,42 @@
return ((ExpandableView) v).getTranslation();
}
+ @Override
+ public void dismiss(View animView, float velocity) {
+ dismissChild(animView, velocity,
+ !swipedFastEnough(0, 0) /* useAccelerateInterpolator */);
+ }
+
+ @Override
+ public void snap(View animView, float targetLeft, float velocity) {
+ snapChild(animView, targetLeft, velocity);
+ }
+
+ @Override
+ public boolean swipedFarEnough(float translation, float viewSize) {
+ return swipedFarEnough();
+ }
+
+ @Override
+ public boolean swipedFastEnough(float translation, float velocity) {
+ return swipedFastEnough();
+ }
+
+ @Override
+ public float getMinDismissVelocity() {
+ return getEscapeVelocity();
+ }
+
+ public void onMenuShown(View animView) {
+ onDragCancelled(animView);
+
+ // If we're on the lockscreen we want to false this.
+ if (isAntiFalsingNeeded()) {
+ mHandler.removeCallbacks(mFalsingCheck);
+ mHandler.postDelayed(mFalsingCheck, COVER_MENU_DELAY);
+ }
+ }
+
public void closeControlsIfOutsideTouch(MotionEvent ev) {
NotificationGuts guts = mStatusBar.getExposedGuts();
View view = null;
@@ -4437,9 +4358,9 @@
// Checking guts
view = guts;
height = guts.getActualHeight();
- } else if (mCurrIconRow != null && mCurrIconRow.isVisible()
+ } else if (mCurrMenuRow != null && mCurrMenuRow.isMenuVisible()
&& mTranslatingParentView != null) {
- // Checking gear
+ // Checking menu
view = mTranslatingParentView;
height = ((ExpandableView) mTranslatingParentView).getActualHeight();
}
@@ -4452,95 +4373,29 @@
final int y = mTempInt2[1];
Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
if (!rect.contains(rx, ry)) {
- // Touch was outside visible guts / gear notification, close what's visible
- mStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
+ // Touch was outside visible guts / meny notification, close what's visible
+ mStatusBar.dismissPopups(-1, -1, true /* resetMenu */, true /* animate */);
}
}
}
- /**
- * Returns whether the gesture is towards the gear location or not.
- */
- private boolean isTowardsGear(float velocity, boolean onLeft) {
- if (mCurrIconRow == null) {
- return false;
- }
- return mCurrIconRow.isVisible()
- && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
- }
-
- /**
- * Indicates the the gear has been snapped to.
- */
- private void setSnappedToGear(boolean snapped) {
- mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isMenuOnLeft() : false;
- mGearSnappedTo = snapped && mCurrIconRow != null;
- }
-
- /**
- * Returns the horizontal space in pixels required to display the gear behind a
- * notification.
- */
- private float getSpaceForGear(View view) {
- if (view instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) view).getSpaceForGear();
- }
- return 0;
- }
-
- private void checkForDrag() {
- if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
- mCheckForDrag = new CheckForDrag();
- mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY);
- }
- }
-
- private void cancelCheckForDrag() {
- if (mCurrIconRow != null) {
- mCurrIconRow.cancelFadeAnimator();
- }
- mHandler.removeCallbacks(mCheckForDrag);
- }
-
- private final class CheckForDrag implements Runnable {
- @Override
- public void run() {
- if (mTranslatingParentView == null) {
- return;
- }
- final float translation = getTranslation(mTranslatingParentView);
- final float absTransX = Math.abs(translation);
- final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
- final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
- if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
- || mCurrIconRow.isMenuLocationChange(translation)))
- && absTransX >= bounceBackToGearWidth * 0.4
- && absTransX < notiThreshold) {
- // Fade in the gear
- mCurrIconRow.fadeInMenu(translation > 0 /* fromLeft */, translation,
- notiThreshold);
- }
- }
- }
-
- public void resetExposedGearView(boolean animate, boolean force) {
- if (mGearExposedView == null
- || (!force && mGearExposedView == mTranslatingParentView)) {
- // If no gear is showing or it's showing for this view we do nothing.
+ public void resetExposedMenuView(boolean animate, boolean force) {
+ if (mMenuExposedView == null
+ || (!force && mMenuExposedView == mTranslatingParentView)) {
+ // If no menu is showing or it's showing for this view we do nothing.
return;
}
- final View prevGearExposedView = mGearExposedView;
+ final View prevMenuExposedView = mMenuExposedView;
if (animate) {
- Animator anim = getViewTranslationAnimator(prevGearExposedView,
+ Animator anim = getViewTranslationAnimator(prevMenuExposedView,
0 /* leftTarget */, null /* updateListener */);
if (anim != null) {
anim.start();
}
- } else if (mGearExposedView instanceof ExpandableNotificationRow) {
- ((ExpandableNotificationRow) mGearExposedView).resetTranslation();
+ } else if (mMenuExposedView instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) mMenuExposedView).resetTranslation();
}
- mGearExposedView = null;
- mGearSnappedTo = false;
+ mMenuExposedView = null;
}
}
@@ -4557,8 +4412,8 @@
}
}
- public void resetExposedGearView(boolean animate, boolean force) {
- mSwipeHelper.resetExposedGearView(animate, force);
+ public void resetExposedMenuView(boolean animate, boolean force) {
+ mSwipeHelper.resetExposedMenuView(animate, force);
}
public void closeControlsIfOutsideTouch(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index ca53bc4..8ed4fca 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -24,7 +24,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.volume.VolumeDialogController.State;
+import com.android.systemui.plugins.VolumeDialogController.State;
import java.util.Arrays;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 9d0ecec..2f9bcff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -24,13 +24,19 @@
import android.os.Bundle;
import android.os.Handler;
import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.PluginDependency;
+import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.VolumeDialog;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
@@ -41,7 +47,7 @@
* Implementation of VolumeComponent backed by the new volume dialog.
*/
public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
- VolumeDialogController.UserActivityListener{
+ VolumeDialogControllerImpl.UserActivityListener{
public static final String VOLUME_DOWN_SILENT = "sysui_volume_down_silent";
public static final String VOLUME_UP_SILENT = "sysui_volume_up_silent";
@@ -53,9 +59,8 @@
private final SystemUI mSysui;
private final Context mContext;
- private final VolumeDialogController mController;
- private final ZenModeController mZenModeController;
- private final VolumeDialog mDialog;
+ private final VolumeDialogControllerImpl mController;
+ private VolumeDialog mDialog;
private VolumePolicy mVolumePolicy = new VolumePolicy(
DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent
DEFAULT_VOLUME_UP_TO_EXIT_SILENT, // volumeUpToExitSilent
@@ -66,16 +71,35 @@
public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler) {
mSysui = sysui;
mContext = context;
- mController = SystemUIFactory.getInstance().createVolumeDialogController(context, null);
+ mController = (VolumeDialogControllerImpl) Dependency.get(VolumeDialogController.class);
mController.setUserActivityListener(this);
- mZenModeController = Dependency.get(ZenModeController.class);
- mDialog = new VolumeDialog(context, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY,
- mController, mZenModeController, mVolumeDialogCallback);
+ // Allow plugins to reference the VolumeDialogController.
+ Dependency.get(PluginDependencyProvider.class)
+ .allowPluginDependency(VolumeDialogController.class);
+ Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
+ .withPlugin(VolumeDialog.class)
+ .withDefault(this::createDefault)
+ .withCallback(dialog -> {
+ if (mDialog != null) {
+ mDialog.destroy();
+ }
+ mDialog = dialog;
+ mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
+ }).build();
applyConfiguration();
Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
VOLUME_SILENT_DO_NOT_DISTURB);
}
+ private VolumeDialog createDefault() {
+ VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
+ impl.setStreamImportant(AudioManager.STREAM_ALARM, true);
+ impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+ impl.setAutomute(true);
+ impl.setSilentMode(false);
+ return impl;
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
if (VOLUME_DOWN_SILENT.equals(key)) {
@@ -118,10 +142,6 @@
}
private void applyConfiguration() {
- mDialog.setStreamImportant(AudioManager.STREAM_ALARM, true);
- mDialog.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
- mDialog.setAutomute(true);
- mDialog.setSilentMode(false);
mController.setVolumePolicy(mVolumePolicy);
mController.showDndTile(true);
}
@@ -149,8 +169,6 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mController.dump(fd, pw, args);
- mDialog.dump(pw);
}
private void startSettings(Intent intent) {
@@ -158,7 +176,7 @@
true /* onlyProvisioned */, true /* dismissShade */);
}
- private final VolumeDialog.Callback mVolumeDialogCallback = new VolumeDialog.Callback() {
+ private final VolumeDialogImpl.Callback mVolumeDialogCallback = new VolumeDialogImpl.Callback() {
@Override
public void onZenSettingsClicked() {
startSettings(ZenModePanel.ZEN_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
rename to packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 276b7c3..5d51a33 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.volume;
-import android.annotation.IntegerRes;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -44,10 +43,11 @@
import android.service.notification.Condition;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
import java.io.FileDescriptor;
@@ -63,8 +63,8 @@
*
* Methods ending in "W" must be called on the worker thread.
*/
-public class VolumeDialogController {
- private static final String TAG = Util.logTag(VolumeDialogController.class);
+public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
+ private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class);
private static final int DYNAMIC_STREAM_START_INDEX = 100;
private static final int VIBRATE_HINT_DURATION = 50;
@@ -89,7 +89,6 @@
private final Context mContext;
private AudioManager mAudio;
private final NotificationManager mNoMan;
- private final ComponentName mComponent;
private final SettingObserver mObserver;
private final Receiver mReceiver = new Receiver();
private final MediaSessions mMediaSessions;
@@ -108,11 +107,10 @@
protected final VC mVolumeController = new VC();
- public VolumeDialogController(Context context, ComponentName component) {
+ public VolumeDialogControllerImpl(Context context) {
mContext = context.getApplicationContext();
Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED);
- mComponent = component;
- mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName());
+ mWorkerThread = new HandlerThread(VolumeDialogControllerImpl.class.getSimpleName());
mWorkerThread.start();
mWorker = new W(mWorkerThread.getLooper());
mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(),
@@ -197,7 +195,7 @@
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println(VolumeDialogController.class.getSimpleName() + " state:");
+ pw.println(VolumeDialogControllerImpl.class.getSimpleName() + " state:");
pw.print(" mDestroyed: "); pw.println(mDestroyed);
pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy);
pw.print(" mState: "); pw.println(mState.toString(4));
@@ -530,7 +528,7 @@
}
private final class VC extends IVolumeController.Stub {
- private final String TAG = VolumeDialogController.TAG + ".VC";
+ private final String TAG = VolumeDialogControllerImpl.TAG + ".VC";
@Override
public void displaySafeVolumeWarning(int flags) throws RemoteException {
@@ -958,113 +956,6 @@
}
}
- public static final class StreamState {
- public boolean dynamic;
- public int level;
- public int levelMin;
- public int levelMax;
- public boolean muted;
- public boolean muteSupported;
- public @IntegerRes int name;
- public String remoteLabel;
- public boolean routedToBluetooth;
-
- public StreamState copy() {
- final StreamState rt = new StreamState();
- rt.dynamic = dynamic;
- rt.level = level;
- rt.levelMin = levelMin;
- rt.levelMax = levelMax;
- rt.muted = muted;
- rt.muteSupported = muteSupported;
- rt.name = name;
- rt.remoteLabel = remoteLabel;
- rt.routedToBluetooth = routedToBluetooth;
- return rt;
- }
- }
-
- public static final class State {
- public static int NO_ACTIVE_STREAM = -1;
-
- public final SparseArray<StreamState> states = new SparseArray<StreamState>();
-
- public int ringerModeInternal;
- public int ringerModeExternal;
- public int zenMode;
- public ComponentName effectsSuppressor;
- public String effectsSuppressorName;
- public int activeStream = NO_ACTIVE_STREAM;
-
- public State copy() {
- final State rt = new State();
- for (int i = 0; i < states.size(); i++) {
- rt.states.put(states.keyAt(i), states.valueAt(i).copy());
- }
- rt.ringerModeExternal = ringerModeExternal;
- rt.ringerModeInternal = ringerModeInternal;
- rt.zenMode = zenMode;
- if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
- rt.effectsSuppressorName = effectsSuppressorName;
- rt.activeStream = activeStream;
- return rt;
- }
-
- @Override
- public String toString() {
- return toString(0);
- }
-
- public String toString(int indent) {
- final StringBuilder sb = new StringBuilder("{");
- if (indent > 0) sep(sb, indent);
- for (int i = 0; i < states.size(); i++) {
- if (i > 0) {
- sep(sb, indent);
- }
- final int stream = states.keyAt(i);
- final StreamState ss = states.valueAt(i);
- sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
- .append('[').append(ss.levelMin).append("..").append(ss.levelMax)
- .append(']');
- if (ss.muted) sb.append(" [MUTED]");
- if (ss.dynamic) sb.append(" [DYNAMIC]");
- }
- sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
- sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
- sep(sb, indent); sb.append("zenMode:").append(zenMode);
- sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
- sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
- sep(sb, indent); sb.append("activeStream:").append(activeStream);
- if (indent > 0) sep(sb, indent);
- return sb.append('}').toString();
- }
-
- private static void sep(StringBuilder sb, int indent) {
- if (indent > 0) {
- sb.append('\n');
- for (int i = 0; i < indent; i++) {
- sb.append(' ');
- }
- } else {
- sb.append(',');
- }
- }
- }
-
- public interface Callbacks {
- void onShowRequested(int reason);
- void onDismissRequested(int reason);
- void onStateChanged(State state);
- void onLayoutDirectionChanged(int layoutDirection);
- void onConfigurationChanged();
- void onShowVibrateHint();
- void onShowSilentHint();
- void onScreenOff();
- void onShowSafetyWarning(int flags);
- void onAccessibilityModeChanged(Boolean showA11yStream);
- }
-
public interface UserActivityListener {
void onUserActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
rename to packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1933349a..697cac9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -16,6 +16,9 @@
package com.android.systemui.volume;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
import android.annotation.NonNull;
@@ -72,28 +75,27 @@
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.plugins.VolumeDialogController.State;
+import com.android.systemui.plugins.VolumeDialogController.StreamState;
+import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerZenModePanel;
-import com.android.systemui.volume.VolumeDialogController.State;
-import com.android.systemui.volume.VolumeDialogController.StreamState;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
-
/**
* Visual presentation of the volume dialog.
*
- * A client of VolumeDialogController and its state model.
+ * A client of VolumeDialogControllerImpl and its state model.
*
* Methods ending in "H" must be called on the (ui) handler.
*/
-public class VolumeDialog implements TunerService.Tunable {
- private static final String TAG = Util.logTag(VolumeDialog.class);
+public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
+ private static final String TAG = Util.logTag(VolumeDialogImpl.class);
public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
@@ -102,7 +104,7 @@
private final Context mContext;
private final H mHandler = new H();
- private final VolumeDialogController mController;
+ private VolumeDialogController mController;
private Window mWindow;
private CustomDialog mDialog;
@@ -123,7 +125,7 @@
private final ColorStateList mActiveSliderTint;
private final ColorStateList mInactiveSliderTint;
private VolumeDialogMotion mMotion;
- private final int mWindowType;
+ private int mWindowType;
private final ZenModeController mZenModeController;
private boolean mShowing;
@@ -146,32 +148,39 @@
private boolean mShowFullZen;
private TunerZenModePanel mZenPanel;
- public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
- ZenModeController zenModeController, Callback callback) {
+ public VolumeDialogImpl(Context context) {
mContext = context;
- mController = controller;
- mCallback = callback;
- mWindowType = windowType;
- mZenModeController = zenModeController;
- mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mZenModeController = Dependency.get(ZenModeController.class);
+ mController = Dependency.get(VolumeDialogController.class);
+ mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAccessibilityMgr =
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
+ }
+
+ public void init(int windowType, Callback callback) {
+ mCallback = callback;
+ mWindowType = windowType;
initDialog();
mAccessibility.init();
- controller.addCallback(mControllerCallbackH, mHandler);
- controller.getState();
+ mController.addCallback(mControllerCallbackH, mHandler);
+ mController.getState();
Dependency.get(TunerService.class).addTunable(this, SHOW_FULL_ZEN);
final Configuration currentConfig = mContext.getResources().getConfiguration();
mDensity = currentConfig.densityDpi;
}
+ @Override
+ public void destroy() {
+ mController.removeCallback(mControllerCallbackH);
+ }
+
private void initDialog() {
mDialog = new CustomDialog(mContext);
@@ -193,7 +202,7 @@
final WindowManager.LayoutParams lp = mWindow.getAttributes();
lp.type = mWindowType;
lp.format = PixelFormat.TRANSLUCENT;
- lp.setTitle(VolumeDialog.class.getSimpleName());
+ lp.setTitle(VolumeDialogImpl.class.getSimpleName());
lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
lp.gravity = Gravity.TOP;
@@ -361,7 +370,7 @@
}
public void dump(PrintWriter writer) {
- writer.println(VolumeDialog.class.getSimpleName() + " state:");
+ writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
writer.print(" mShowing: "); writer.println(mShowing);
writer.print(" mExpanded: "); writer.println(mExpanded);
writer.print(" mExpandButtonAnimationRunning: ");
@@ -459,10 +468,6 @@
}
}
- public void destroy() {
- mController.removeCallback(mControllerCallbackH);
- }
-
public void show(int reason) {
mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
}
@@ -1289,9 +1294,4 @@
private int animTargetProgress;
private int lastAudibleLevel = 1;
}
-
- public interface Callback {
- void onZenSettingsClicked();
- void onZenPrioritySettingsClicked();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
index a2c32b7..d28e42e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
@@ -25,7 +25,7 @@
import android.os.IBinder;
import android.util.Log;
-import com.android.systemui.volume.VolumeDialogController;
+import com.android.systemui.volume.VolumeDialogControllerImpl;
/**
* A volume dialog controller for the automotive use case.
@@ -33,7 +33,7 @@
* {@link android.car.media.CarAudioManager} is the source of truth to get the stream volumes.
* And volume changes should be sent to the car's audio module instead of the android's audio mixer.
*/
-public class CarVolumeDialogController extends VolumeDialogController {
+public class CarVolumeDialogController extends VolumeDialogControllerImpl {
private static final String TAG = "CarVolumeDialogController";
private final Car mCar;
@@ -57,8 +57,8 @@
}
};
- public CarVolumeDialogController(Context context, ComponentName component) {
- super(context, component);
+ public CarVolumeDialogController(Context context) {
+ super(context);
mCar = Car.createCar(context, mConnection);
mCar.connect();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
index a3d5d5f..b8e9fcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -29,13 +29,18 @@
import android.support.test.annotation.UiThreadTest;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.annotations.ProvidesInterface;
import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,7 +50,8 @@
import java.lang.Thread.UncaughtExceptionHandler;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
public class PluginManagerTest extends SysuiTestCase {
private PluginInstanceManagerFactory mMockFactory;
@@ -59,6 +65,8 @@
@Before
public void setup() throws Exception {
+ mDependency.injectTestDependency(Dependency.BG_LOOPER,
+ TestableLooper.get(this).getLooper());
mRealExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
mMockFactory = mock(PluginInstanceManagerFactory.class);
@@ -72,7 +80,7 @@
mMockListener = mock(PluginListener.class);
}
- @UiThreadTest
+ @RunWithLooper(setAsMainLooper = true)
@Test
public void testOneShot() {
Plugin mockPlugin = mock(Plugin.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
index c2c63363..31b9bae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
@@ -18,6 +18,8 @@
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
+import android.testing.ViewUtils;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -35,10 +37,11 @@
@Test
public void testAttachDetach() {
- NotificationMenuRow row = new NotificationMenuRow(mContext);
- ViewUtils.attachView(row);
+ NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ row.createMenu(null);
+ ViewUtils.attachView(row.getMenuView());
TestableLooper.get(this).processAllMessages();
- ViewUtils.detachView(row);
+ ViewUtils.detachView(row.getMenuView());
TestableLooper.get(this).processAllMessages();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index f516d74..8808988 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -20,9 +20,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -33,13 +31,10 @@
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
public class BluetoothControllerImplTest extends SysuiTestCase {
private LocalBluetoothManager mMockBluetoothManager;
@@ -52,7 +47,7 @@
@Before
public void setup() throws Exception {
- mTestableLooper = TestableLooper.get(this);
+ mTestableLooper = new TestableLooper();
mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
mDevices = new ArrayList<>();
mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index fb714b9..c091c41 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -97,7 +97,9 @@
// OS: 6.0
ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE = 6;
- // OPEN: Settings > Accessibility > Magnification gestures
+ // OPEN: Settings > Accessibility > Magnification gestures (Renamed in O)
+ // OPEN: Settings > Accessibility > Magnification > Magnify with triple-tap
+ // OPEN: Settings > Accessibility > Magnification > Magnify with button
// CATEGORY: SETTINGS
// OS: 6.0
ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION = 7;
@@ -1881,7 +1883,9 @@
// OS: N
SUW_ACCESSIBILITY = 367;
- // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification gesture
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification gestures (Renamed in O)
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification -> Magnify with triple-tap
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification -> Magnify with button
// ACTION: New magnification gesture configuration is chosen
// SUBTYPE: 0 is off, 1 is on
// CATEGORY: SETTINGS
@@ -3780,6 +3784,19 @@
// meta-event: a reader has checkpointed the log here.
METRICS_CHECKPOINT = 920;
+ // OPEN: Settings -> Display -> When in VR Mode
+ VR_DISPLAY_PREFERENCE = 921;
+
+ // OPEN: Settings > Accessibility > Magnification
+ // CATEGORY: SETTINGS
+ // OS: O
+ ACCESSIBILITY_SCREEN_MAGNIFICATION_SETTINGS = 922;
+
+ // ACTION: Logs pressing the "Clear app" button in the app info settings page for an instant
+ // app.
+ // VALUE: The package name of the app
+ ACTION_SETTINGS_CLEAR_INSTANT_APP = 923;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 397938a..3666763 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1920,7 +1920,8 @@
}
ComponentName componentNameToEnable =
ComponentName.unflattenFromString(componentNameToEnableString);
- if (componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
+ if ((componentNameToEnable != null)
+ && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
return false;
}
userState.mServiceToEnableWithShortcut = componentNameToEnable;
@@ -1948,7 +1949,9 @@
if (!shortcutServiceIsInstalled) {
userState.mServiceToEnableWithShortcut = null;
Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", userState.mUserId);
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, userState.mUserId);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0, userState.mUserId);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 776fa1e..375a726 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -18,6 +18,8 @@
import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
+import static com.android.server.autofill.ui.Helper.DEBUG;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -160,7 +162,11 @@
*/
public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
@NonNull Rect anchorBounds, @Nullable String filterText, @NonNull String packageName) {
- LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
+ if (DEBUG) {
+ Slog.d(TAG, "showFillUi(): id=" + focusedId + ", bounds=" + anchorBounds + " filter="
+ + filterText);
+ }
+ final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
.setPackageName(packageName)
.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
filterText == null ? 0 : filterText.length())
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 85eecdf..98a02f2 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -100,6 +100,7 @@
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
content.measure(widthMeasureSpec, heightMeasureSpec);
content.setOnClickListener(v -> mCallback.onResponsePicked(response));
+ content.setElevation(context.getResources().getDimension(R.dimen.floating_window_z));
mContentWidth = content.getMeasuredWidth();
mContentHeight = content.getMeasuredHeight();
@@ -138,8 +139,7 @@
};
final LayoutInflater inflater = LayoutInflater.from(context);
- mListView = (ListView) inflater.inflate(
- com.android.internal.R.layout.autofill_dataset_picker, null);
+ mListView = (ListView) inflater.inflate(R.layout.autofill_dataset_picker, null);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener((adapter, view, position, id) -> {
final ViewItem vi = mAdapter.getItem(position);
@@ -326,6 +326,16 @@
}
public void show(int desiredWidth, int desiredHeight, Rect anchorBounds) {
+ try {
+ // TODO: temporary workaround to avoud system_server crashes.
+ unsafelyShow(desiredWidth, desiredHeight, anchorBounds);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Error showing Anchored window: w=" + desiredWidth + ", h="
+ + desiredHeight + ", b=" + anchorBounds, e);
+ }
+ }
+
+ private void unsafelyShow(int desiredWidth, int desiredHeight, Rect anchorBounds) {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.setTitle("FillUi");
diff --git a/services/core/Android.mk b/services/core/Android.mk
index d312902..e35a171 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -13,6 +13,7 @@
../../../../system/netd/server/binder/android/net/INetd.aidl \
../../../../system/netd/server/binder/android/net/metrics/INetdEventListener.aidl \
../../../native/cmds/installd/binder/android/os/IInstalld.aidl \
+ ../../../native/services/vr/vr_window_manager/aidl/android/service/vr/IVrWindowManager.aidl \
LOCAL_AIDL_INCLUDES += \
system/netd/server/binder
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 8e6310f..891a13b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -97,6 +97,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.os.AppFuseMount;
+import com.android.internal.os.FuseAppLoop;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
@@ -350,7 +351,7 @@
private int mNextAppFuseName = 0;
@GuardedBy("mAppFuseLock")
- private final SparseArray<Integer> mAppFusePids = new SparseArray<>();
+ private AppFuseBridge mAppFuseBridge = null;
private VolumeInfo findVolumeByIdOrThrow(String id) {
synchronized (mLock) {
@@ -2991,124 +2992,79 @@
}
}
- class CloseableHolder<T extends AutoCloseable> implements AutoCloseable {
- @Nullable T mCloseable;
-
- CloseableHolder(T closeable) {
- mCloseable = closeable;
+ private ParcelFileDescriptor mountAppFuse(int uid, int mountId)
+ throws NativeDaemonConnectorException {
+ final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute(
+ "appfuse", "mount", uid, Process.myPid(), mountId);
+ if (event.getFileDescriptors() == null ||
+ event.getFileDescriptors().length == 0) {
+ throw new NativeDaemonConnectorException("Cannot obtain device FD");
}
-
- @Nullable T get() {
- return mCloseable;
- }
-
- @Nullable T release() {
- final T result = mCloseable;
- mCloseable = null;
- return result;
- }
-
- @Override
- public void close() {
- if (mCloseable != null) {
- IoUtils.closeQuietly(mCloseable);
- }
- }
+ return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
}
- class AppFuseMountScope implements AppFuseBridge.IMountScope {
- final int mUid;
- final int mName;
- final ParcelFileDescriptor mDeviceFd;
-
- AppFuseMountScope(int uid, int pid, int name) throws NativeDaemonConnectorException {
- final NativeDaemonEvent event = mConnector.execute(
- "appfuse", "mount", uid, Process.myPid(), name);
- mUid = uid;
- mName = name;
- synchronized (mLock) {
- mAppFusePids.put(name, pid);
- }
- if (event.getFileDescriptors() != null &&
- event.getFileDescriptors().length > 0) {
- mDeviceFd = new ParcelFileDescriptor(event.getFileDescriptors()[0]);
- } else {
- mDeviceFd = null;
- }
+ class AppFuseMountScope extends AppFuseBridge.MountScope {
+ public AppFuseMountScope(int uid, int pid, int mountId)
+ throws NativeDaemonConnectorException {
+ super(uid, pid, mountId, mountAppFuse(uid, mountId));
}
@Override
- public void close() throws NativeDaemonConnectorException {
- try {
- IoUtils.closeQuietly(mDeviceFd);
- mConnector.execute(
- "appfuse", "unmount", mUid, Process.myPid(), mName);
- } finally {
- synchronized (mLock) {
- mAppFusePids.delete(mName);
- }
- }
- }
-
- @Override
- public ParcelFileDescriptor getDeviceFileDescriptor() {
- return mDeviceFd;
+ public void close() throws Exception {
+ super.close();
+ mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
}
}
@Override
public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+ Slog.v(TAG, "mountProxyFileDescriptorBridge");
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- final int name;
- synchronized (mAppFuseLock) {
- name = mNextAppFuseName++;
- }
- try (CloseableHolder<AppFuseMountScope> mountScope =
- new CloseableHolder<>(new AppFuseMountScope(uid, pid, name))) {
- if (mountScope.get().getDeviceFileDescriptor() == null) {
- throw new RemoteException("Failed to obtain device FD");
- }
- // Create communication channel.
- final ArrayBlockingQueue<Boolean> channel = new ArrayBlockingQueue<>(1);
- final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair();
- try (CloseableHolder<ParcelFileDescriptor> remote = new CloseableHolder<>(fds[0])) {
- new Thread(
- new AppFuseBridge(mountScope.release(), fds[1], channel),
- AppFuseBridge.TAG).start();
- if (!channel.take()) {
- throw new RemoteException("Failed to init AppFuse mount point");
+ while (true) {
+ synchronized (mAppFuseLock) {
+ boolean newlyCreated = false;
+ if (mAppFuseBridge == null) {
+ mAppFuseBridge = new AppFuseBridge();
+ new Thread(mAppFuseBridge, AppFuseBridge.TAG).start();
+ newlyCreated = true;
}
-
- return new AppFuseMount(name, remote.release());
+ try {
+ final int name = mNextAppFuseName++;
+ try {
+ return new AppFuseMount(
+ name,
+ mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name)));
+ } catch (AppFuseBridge.BridgeException e) {
+ if (newlyCreated) {
+ // If newly created bridge fails, it's a real error.
+ throw new RemoteException(e.getMessage());
+ }
+ // It seems the thread of mAppFuseBridge has already been terminated.
+ mAppFuseBridge = null;
+ }
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
}
- } catch (NativeDaemonConnectorException e){
- throw e.rethrowAsParcelableException();
- } catch (IOException | InterruptedException error) {
- throw new RemoteException(error.getMessage());
}
}
@Override
- public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode) {
- final int uid = Binder.getCallingUid();
+ public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode)
+ throws RemoteException {
+ Slog.v(TAG, "mountProxyFileDescriptorBridge");
final int pid = Binder.getCallingPid();
try {
synchronized (mAppFuseLock) {
- final int expectedPid = mAppFusePids.get(mountId, -1);
- if (expectedPid == -1) {
- Slog.i(TAG, "The mount point has already been unmounted");
- return null;
+ if (mAppFuseBridge == null) {
+ throw new RemoteException("Cannot find mount point");
}
- if (expectedPid != pid) {
- throw new SecurityException("Mount point was not created by this process.");
- }
+ return mAppFuseBridge.openFile(pid, mountId, fileId, mode);
}
- return AppFuseBridge.openFile(uid, mountId, fileId, mode);
- } catch (FileNotFoundException error) {
- Slog.e(TAG, "Failed to openProxyFileDescriptor", error);
- return null;
+ } catch (FileNotFoundException | SecurityException | InterruptedException error) {
+ throw new RemoteException(error.getMessage());
}
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 5115fde..e4f4687 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -323,52 +323,6 @@
}
@Override
- public void setTheme(String theme) {
- if (getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_THEME_OVERLAY)
- != PackageManager.PERMISSION_GRANTED) {
- Slog.e(TAG, "setTheme requires MODIFY_THEME_OVERLAY permission");
- return;
- }
- SystemProperties.set("persist.vendor.overlay.theme", theme);
- mHandler.post(() -> ShutdownThread.reboot(getContext(),
- PowerManager.SHUTDOWN_USER_REQUESTED, false));
- }
-
- @Override
- public String getTheme() {
- if (getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_THEME_OVERLAY)
- != PackageManager.PERMISSION_GRANTED) {
- Slog.e(TAG, "setTheme requires MODIFY_THEME_OVERLAY permission");
- return null;
- }
- return SystemProperties.get("persist.vendor.overlay.theme");
- }
-
- @Override
- public String[] getAvailableThemes() {
- if (getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_THEME_OVERLAY)
- != PackageManager.PERMISSION_GRANTED) {
- Slog.e(TAG, "getAvailableThemes requires MODIFY_THEME_OVERLAY permission");
- return null;
- }
- String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
- if (TextUtils.isEmpty(def)) {
- def = null;
- }
- String[] fileList = new File("/vendor/overlay").list();
- if (fileList == null) return new String[0];
- ArrayList<String> options = new ArrayList(fileList.length + 1);
- Collections.addAll(options, fileList);
- if (!options.contains(def)) {
- options.add(0, def);
- }
- return options.toArray(new String[options.size()]);
- }
-
- @Override
public int getNightMode() {
synchronized (mLock) {
return mNightMode;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3437512..4fc60f9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19032,26 +19032,13 @@
break;
case android.hardware.Camera.ACTION_NEW_PICTURE:
case android.hardware.Camera.ACTION_NEW_VIDEO:
- // These broadcasts are no longer allowed by the system, since they can
- // cause significant thrashing at a crictical point (using the camera).
- // Apps should use JobScehduler to monitor for media provider changes.
- Slog.w(TAG, action + " no longer allowed; dropping from "
- + UserHandle.formatUid(callingUid));
- if (resultTo != null) {
- final BroadcastQueue queue = broadcastQueueForIntent(intent);
- try {
- queue.performReceiveLocked(callerApp, resultTo, intent,
- Activity.RESULT_CANCELED, null, null,
- false, false, userId);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure ["
- + queue.mQueueName + "] sending broadcast result of "
- + intent, e);
-
- }
- }
- // Lie; we don't want to crash the app.
- return ActivityManager.BROADCAST_SUCCESS;
+ // In N we just turned these off; in O we are turing them back on partly,
+ // only for registered receivers. This will still address the main problem
+ // (a spam of apps waking up when a picture is taken putting significant
+ // memory pressure on the system at a bad point), while still allowing apps
+ // that are already actively running to know about this happening.
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ break;
case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
break;
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 6cea483..9a1cd8c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -47,6 +47,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.res.Configuration.EMPTY;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
@@ -83,6 +84,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
@@ -130,7 +132,7 @@
/**
* An entry in the history stack, representing an activity.
*/
-final class ActivityRecord implements AppWindowContainerListener {
+final class ActivityRecord extends ConfigurationContainer implements AppWindowContainerListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_AM;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_SAVED_STATE = TAG + POSTFIX_SAVED_STATE;
@@ -286,11 +288,19 @@
// on the window.
int mRotationAnimationHint = -1;
+ // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+ // TODO(b/36505427): Every level on ConfigurationContainer now has bounds information, which
+ // directly affects the configuration. We should probably move this into that class and have it
+ // handle calculating override configuration from the bounds.
+ private final Rect mBounds = new Rect();
+
/**
* Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)}
*/
private final Configuration mTmpConfig1 = new Configuration();
private final Configuration mTmpConfig2 = new Configuration();
+ private final Point mTmpPoint = new Point();
+ private final Rect mTmpBounds = new Rect();
private static String startingWindowStateToString(int state) {
switch (state) {
@@ -344,6 +354,13 @@
pw.println(mLastReportedConfiguration);
pw.print(prefix); pw.print("mLastReportedOverrideConfiguration=");
pw.println(mLastReportedOverrideConfiguration);
+ pw.print(prefix); pw.print("CurrentConfiguration="); pw.println(getConfiguration());
+ if (!getOverrideConfiguration().equals(EMPTY)) {
+ pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration());
+ }
+ if (!mBounds.isEmpty()) {
+ pw.println(prefix + "mBounds=" + mBounds);
+ }
if (resultTo != null || resultWho != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
pw.print(" resultWho="); pw.print(resultWho);
@@ -461,10 +478,15 @@
}
if (info != null) {
pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
- pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
+ if (info.supportsPictureInPicture()) {
+ pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
+ pw.println(prefix + "supportsPictureInPictureWhilePausing: "
+ + supportsPictureInPictureWhilePausing);
+ }
+ if (info.maxAspectRatio != 0) {
+ pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio);
+ }
}
- pw.println(prefix + "supportsPictureInPictureWhilePausing: "
- + supportsPictureInPictureWhilePausing);
}
private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
@@ -579,6 +601,22 @@
return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
}
+ @Override
+ protected int getChildCount() {
+ // {@link ActivityRecord} is a leaf node and has no children.
+ return 0;
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return null;
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return task;
+ }
+
static class Token extends IApplicationToken.Stub {
private final WeakReference<ActivityRecord> weakActivity;
@@ -764,15 +802,20 @@
inHistory = true;
- task.updateOverrideConfigurationFromLaunchBounds();
final TaskWindowContainerController taskController = task.getWindowContainerController();
+ // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration()
+ task.updateOverrideConfigurationFromLaunchBounds();
+ // Make sure override configuration is up-to-date before using to create window controller.
+ updateOverrideConfiguration();
+
mWindowContainerController = new AppWindowContainerController(taskController, appToken,
this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen,
(info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
appInfo.targetSdkVersion, mRotationAnimationHint,
- ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);
+ ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L,
+ getOverrideConfiguration(), mBounds);
task.addActivityToTop(this);
@@ -1994,8 +2037,73 @@
}
/** Call when override config was sent to the Window Manager to update internal records. */
+ // TODO(b/36505427): Why do we set last reported based on sending the config to WM? Seems like
+ // we should only set this when we actually report to the activity which is what the method
+ // setLastReportedMergedOverrideConfiguration() does. Investigate if this is really needed.
void onOverrideConfigurationSent() {
- mLastReportedOverrideConfiguration.setTo(task.getMergedOverrideConfiguration());
+ mLastReportedOverrideConfiguration.setTo(getMergedOverrideConfiguration());
+ }
+
+ @Override
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ super.onOverrideConfigurationChanged(overrideConfiguration);
+ if (mWindowContainerController != null) {
+ mWindowContainerController.onOverrideConfigurationChanged(
+ overrideConfiguration, mBounds);
+ // TODO(b/36505427): Can we consolidate the call points of onOverrideConfigurationSent()
+ // to just use this method instead?
+ onOverrideConfigurationSent();
+ }
+ }
+
+ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+ private boolean updateOverrideConfiguration() {
+ computeBounds(mTmpBounds);
+ if (mTmpBounds.equals(mBounds)) {
+ return false;
+ }
+ mBounds.set(mTmpBounds);
+ // Bounds changed...update configuration to match.
+ mTmpConfig1.unset();
+ task.computeOverrideConfiguration(mTmpConfig1, mBounds, null /* insetBounds */,
+ false /* overrideWidth */, false /* overrideHeight */);
+ onOverrideConfigurationChanged(mTmpConfig1);
+ return true;
+ }
+
+ /** Computes the override configuration for this activity */
+ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+ private void computeBounds(Rect outBounds) {
+ outBounds.setEmpty();
+ final float maxAspectRatio = info.maxAspectRatio;
+ final ActivityStack stack = getStack();
+ if ((task != null && !task.mFullscreen) || maxAspectRatio == 0 || stack == null) {
+ // We don't set override configuration if that activity task isn't fullscreen. I.e. the
+ // activity is in multi-window mode. Or, there isn't a max aspect ratio specified for
+ // the activity.
+ return;
+ }
+
+ stack.getDisplaySize(mTmpPoint);
+ int maxActivityWidth = mTmpPoint.x;
+ int maxActivityHeight = mTmpPoint.y;
+ if (mTmpPoint.x < mTmpPoint.y) {
+ // Width is the shorter side, so we use that to figure-out what the max. height should
+ // be given the aspect ratio.
+ maxActivityHeight = (int) ((maxActivityWidth * maxAspectRatio) + 0.5f);
+ } else {
+ // Height is the shorter side, so we use that to figure-out what the max. width should
+ // be given the aspect ratio.
+ maxActivityWidth = (int) ((maxActivityHeight * maxAspectRatio) + 0.5f);
+ }
+
+ if (mTmpPoint.x <= maxActivityWidth && mTmpPoint.y <= maxActivityHeight) {
+ // The display matches or is less than the activity aspect ratio, so nothing else to do.
+ return;
+ }
+
+ // Compute configuration based on max supported width and height.
+ outBounds.set(0, 0, maxActivityWidth, maxActivityHeight);
}
/**
@@ -2028,13 +2136,16 @@
if (displayChanged) {
mLastReportedDisplayId = newDisplayId;
}
+ // TODO(b/36505427): Is there a better place to do this?
+ updateOverrideConfiguration();
+
// Short circuit: if the two full configurations are equal (the common case), then there is
// nothing to do. We test the full configuration instead of the global and merged override
// configurations because there are cases (like moving a task to the pinned stack) where
// the combine configurations are equal, but would otherwise differ in the override config
mTmpConfig1.setTo(mLastReportedConfiguration);
mTmpConfig1.updateFrom(mLastReportedOverrideConfiguration);
- if (task.getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
+ if (getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration & display unchanged in " + this);
return true;
@@ -2045,15 +2156,15 @@
// Find changes between last reported merged configuration and the current one. This is used
// to decide whether to relaunch an activity or just report a configuration change.
- final int changes = getTaskConfigurationChanges(mTmpConfig1);
+ final int changes = getConfigurationChanges(mTmpConfig1);
// Update last reported values.
final Configuration newGlobalConfig = service.getGlobalConfiguration();
- final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
+ final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();
mTmpConfig1.setTo(mLastReportedConfiguration);
mTmpConfig2.setTo(mLastReportedOverrideConfiguration);
mLastReportedConfiguration.setTo(newGlobalConfig);
- mLastReportedOverrideConfiguration.setTo(newTaskMergedOverrideConfig);
+ mLastReportedOverrideConfiguration.setTo(newMergedOverrideConfig);
if (changes == 0 && !forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
@@ -2061,9 +2172,9 @@
// There are no significant differences, so we won't relaunch but should still deliver
// the new configuration to the client process.
if (displayChanged) {
- scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+ scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
- scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+ scheduleConfigurationChanged(newMergedOverrideConfig);
}
return true;
}
@@ -2088,9 +2199,9 @@
+ Integer.toHexString(changes) + ", handles=0x"
+ Integer.toHexString(info.getRealConfigChanged())
+ ", newGlobalConfig=" + newGlobalConfig
- + ", newTaskMergedOverrideConfig=" + newTaskMergedOverrideConfig);
+ + ", newMergedOverrideConfig=" + newMergedOverrideConfig);
- if (shouldRelaunchLocked(changes, newGlobalConfig, newTaskMergedOverrideConfig)
+ if (shouldRelaunchLocked(changes, newGlobalConfig, newMergedOverrideConfig)
|| forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
@@ -2133,13 +2244,13 @@
}
// Default case: the activity can handle this new configuration, so hand it over.
- // NOTE: We only forward the task override configuration as the system level configuration
+ // NOTE: We only forward the override configuration as the system level configuration
// changes is always sent to all processes when they happen so it can just use whatever
// system level configuration it last got.
if (displayChanged) {
- scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+ scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
- scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+ scheduleConfigurationChanged(newMergedOverrideConfig);
}
stopFreezingScreenLocked(false);
@@ -2167,31 +2278,31 @@
return (changes&(~configChanged)) != 0;
}
- private int getTaskConfigurationChanges(Configuration lastReportedConfig) {
+ private int getConfigurationChanges(Configuration lastReportedConfig) {
// Determine what has changed. May be nothing, if this is a config that has come back from
// the app after going idle. In that case we just want to leave the official config object
// now in the activity and do nothing else.
- final Configuration currentConfig = task.getConfiguration();
- int taskChanges = lastReportedConfig.diff(currentConfig);
+ final Configuration currentConfig = getConfiguration();
+ int changes = lastReportedConfig.diff(currentConfig);
// We don't want to use size changes if they don't cross boundaries that are important to
// the app.
- if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) {
+ if ((changes & CONFIG_SCREEN_SIZE) != 0) {
final boolean crosses = crossesHorizontalSizeThreshold(lastReportedConfig.screenWidthDp,
currentConfig.screenWidthDp)
|| crossesVerticalSizeThreshold(lastReportedConfig.screenHeightDp,
currentConfig.screenHeightDp);
if (!crosses) {
- taskChanges &= ~CONFIG_SCREEN_SIZE;
+ changes &= ~CONFIG_SCREEN_SIZE;
}
}
- if ((taskChanges & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
+ if ((changes & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
final int oldSmallest = lastReportedConfig.smallestScreenWidthDp;
final int newSmallest = currentConfig.smallestScreenWidthDp;
if (!crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
- taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE;
+ changes &= ~CONFIG_SMALLEST_SCREEN_SIZE;
}
}
- return taskChanges;
+ return changes;
}
private static boolean isResizeOnlyChange(int change) {
@@ -2232,7 +2343,7 @@
app.thread.scheduleRelaunchActivity(appToken, pendingResults, pendingNewIntents,
configChangeFlags, !andResume,
new Configuration(service.getGlobalConfiguration()),
- new Configuration(task.getMergedOverrideConfiguration()), preserveWindow);
+ new Configuration(getMergedOverrideConfiguration()), preserveWindow);
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
// pass in 'andResume' if this activity is currently resumed, which implies we aren't
// sleeping.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 97d0aa3..27b8e91 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1380,7 +1380,7 @@
new Configuration(mService.getGlobalConfiguration());
r.setLastReportedGlobalConfiguration(globalConfiguration);
final Configuration mergedOverrideConfiguration =
- new Configuration(task.getMergedOverrideConfiguration());
+ new Configuration(r.getMergedOverrideConfiguration());
r.setLastReportedMergedOverrideConfiguration(mergedOverrideConfiguration);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
diff --git a/services/core/java/com/android/server/am/ConfigurationContainer.java b/services/core/java/com/android/server/am/ConfigurationContainer.java
index a3e95b8..3d60681 100644
--- a/services/core/java/com/android/server/am/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/am/ConfigurationContainer.java
@@ -22,6 +22,8 @@
* Contains common logic for classes that have override configurations and are organized in a
* hierarchy.
*/
+// TODO(b/36505427): Move to wm package and have WindowContainer use this instead of having its own
+// implementation for merging configuration.
abstract class ConfigurationContainer<E extends ConfigurationContainer> {
/** Contains override configuration settings applied to this configuration container. */
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a668fea..fd65c10 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -924,12 +924,12 @@
@Override
protected int getChildCount() {
- return 0;
+ return mActivities.size();
}
@Override
protected ConfigurationContainer getChildAt(int index) {
- return null;
+ return mActivities.get(index);
}
@Override
@@ -944,7 +944,7 @@
}
// Close up recents linked list.
- void closeRecentsChain() {
+ private void closeRecentsChain() {
if (mPrevAffiliate != null) {
mPrevAffiliate.setNextAffiliate(mNextAffiliate);
}
@@ -1188,7 +1188,10 @@
throw new IllegalArgumentException("Can not add r=" + " to task=" + this
+ " current parent=" + r.task);
}
+ // TODO(b/36505427): Maybe make task private to ActivityRecord so we can also do
+ // onParentChanged() within the setter?
r.task = this;
+ r.onParentChanged();
// Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
if (!mActivities.remove(r) && r.fullscreen) {
@@ -1995,7 +1998,7 @@
if (mStack == null || StackId.persistTaskBounds(mStack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
- calculateOverrideConfig(newConfig, mTmpRect, insetBounds,
+ computeOverrideConfiguration(newConfig, mTmpRect, insetBounds,
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
onOverrideConfigurationChanged(newConfig);
@@ -2008,7 +2011,10 @@
}
/** Clears passed config and fills it with new override values. */
- private void calculateOverrideConfig(Configuration config, Rect bounds, Rect insetBounds,
+ // TODO(b/36505427): TaskRecord.computeOverrideConfiguration() is a utility method that doesn't
+ // depend on task or stacks, but uses those object to get the display to base the calculation
+ // on. Probably best to centralize calculations like this in ConfigurationContainer.
+ void computeOverrideConfiguration(Configuration config, Rect bounds, Rect insetBounds,
boolean overrideWidth, boolean overrideHeight) {
mTmpNonDecorBounds.set(bounds);
mTmpStableBounds.set(bounds);
@@ -2027,7 +2033,7 @@
config.smallestScreenWidthDp =
mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
config.screenWidthDp = config.screenHeightDp = config.smallestScreenWidthDp;
- Slog.wtf(TAG, "Expected stack when caclulating override config");
+ Slog.wtf(TAG, "Expected stack when calculating override config");
}
config.orientation = (config.screenWidthDp <= config.screenHeightDp)
@@ -2048,23 +2054,6 @@
}
- /**
- * Using the existing configuration {@param config}, creates a new task override config such
- * that all the fields that are usually set in an override config are set to the ones in
- * {@param config}.
- */
- Configuration extractOverrideConfig(Configuration config) {
- final Configuration extracted = new Configuration();
- extracted.screenWidthDp = config.screenWidthDp;
- extracted.screenHeightDp = config.screenHeightDp;
- extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
- extracted.orientation = config.orientation;
- // We're only overriding LONG, SIZE and COMPAT parts of screenLayout.
- extracted.screenLayout = config.screenLayout & (Configuration.SCREENLAYOUT_LONG_MASK
- | Configuration.SCREENLAYOUT_SIZE_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED);
- return extracted;
- }
-
Rect updateOverrideConfigurationFromLaunchBounds() {
final Rect bounds = validateBounds(getLaunchBounds());
updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index f953a36..67b80f6 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -87,6 +87,7 @@
public UidRecord(int _uid) {
uid = _uid;
+ idle = true;
reset();
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 8a4f3f7..fc45344 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -322,6 +322,12 @@
private INotificationManager mNotifManager;
private PowerManagerInternal mPowerManagerInternal;
private IDeviceIdleController mDeviceIdleController;
+ @GuardedBy("mUidRulesFirstLock")
+ private PowerSaveState mRestrictBackgroundPowerState;
+
+ // Store the status of restrict background before turning on battery saver.
+ // Used to restore mRestrictBackground when battery saver is turned off.
+ private boolean mRestrictBackgroundBeforeBsm;
// See main javadoc for instructions on how to use these locks.
final Object mUidRulesFirstLock = new Object();
@@ -332,6 +338,8 @@
@GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictBackground;
@GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictPower;
@GuardedBy("mUidRulesFirstLock") volatile boolean mDeviceIdleMode;
+ // Store whether user flipped restrict background in battery saver mode
+ @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictBackgroundChangedInBsm;
private final boolean mSuppressDefaultPolicy;
@@ -617,8 +625,9 @@
@Override
public void onLowPowerModeChanged(PowerSaveState result) {
final boolean enabled = result.batterySaverEnabled;
- if (LOGD) Slog.d(TAG,
- "onLowPowerModeChanged(" + enabled + ")");
+ if (LOGD) {
+ Slog.d(TAG, "onLowPowerModeChanged(" + enabled + ")");
+ }
synchronized (mUidRulesFirstLock) {
if (mRestrictPower != enabled) {
mRestrictPower = enabled;
@@ -626,7 +635,7 @@
}
}
}
- });
+ });
mRestrictPower = mPowerManagerInternal.getLowPowerState(
ServiceType.NETWORK_FIREWALL).batterySaverEnabled;
@@ -635,6 +644,32 @@
// read policy from disk
readPolicyAL();
+ // Update the restrictBackground if battery saver is turned on
+ mRestrictBackgroundBeforeBsm = mRestrictBackground;
+ mRestrictBackgroundPowerState = mPowerManagerInternal
+ .getLowPowerState(ServiceType.DATA_SAVER);
+ final boolean localRestrictBackground =
+ mRestrictBackgroundPowerState.batterySaverEnabled;
+ if (localRestrictBackground && localRestrictBackground != mRestrictBackground) {
+ mRestrictBackground = localRestrictBackground;
+ mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED,
+ mRestrictBackground ? 1 : 0, 0).sendToTarget();
+ }
+ mPowerManagerInternal.registerLowPowerModeObserver(
+ new PowerManagerInternal.LowPowerModeListener() {
+ @Override
+ public int getServiceType() {
+ return ServiceType.DATA_SAVER;
+ }
+
+ @Override
+ public void onLowPowerModeChanged(PowerSaveState result) {
+ synchronized (mUidRulesFirstLock) {
+ updateRestrictBackgroundByLowPowerModeUL(result);
+ }
+ }
+ });
+
if (addDefaultRestrictBackgroundWhitelistUidsUL()) {
writePolicyAL();
}
@@ -2159,6 +2194,10 @@
} catch (RemoteException e) {
// ignored; service lives in system_server
}
+
+ if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) {
+ mRestrictBackgroundChangedInBsm = true;
+ }
synchronized (mNetworkPoliciesSecondLock) {
updateNotificationsNL();
writePolicyAL();
@@ -3645,6 +3684,35 @@
mHandler.getLooper().getQueue().addIdleHandler(handler);
}
+ @VisibleForTesting
+ public void updateRestrictBackgroundByLowPowerModeUL(final PowerSaveState result) {
+ mRestrictBackgroundPowerState = result;
+
+ boolean restrictBackground = result.batterySaverEnabled;
+ boolean shouldInvokeRestrictBackground;
+ // store the temporary mRestrictBackgroundChangedInBsm and update it at last
+ boolean localRestrictBgChangedInBsm = mRestrictBackgroundChangedInBsm;
+
+ if (result.globalBatterySaverEnabled) {
+ // Try to turn on restrictBackground if (1) it is off and (2) batter saver need to
+ // turn it on.
+ shouldInvokeRestrictBackground = !mRestrictBackground && result.batterySaverEnabled;
+ mRestrictBackgroundBeforeBsm = mRestrictBackground;
+ localRestrictBgChangedInBsm = false;
+ } else {
+ // Try to restore the restrictBackground if it doesn't change in bsm
+ shouldInvokeRestrictBackground = !mRestrictBackgroundChangedInBsm;
+ restrictBackground = mRestrictBackgroundBeforeBsm;
+ }
+
+ if (shouldInvokeRestrictBackground) {
+ setRestrictBackground(restrictBackground);
+ }
+
+ // Change it at last so setRestrictBackground() won't affect this variable
+ mRestrictBackgroundChangedInBsm = localRestrictBgChangedInBsm;
+ }
+
private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f5ea74d..8cc9375 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -842,7 +842,7 @@
/** Component used to install ephemeral applications */
ComponentName mInstantAppInstallerComponent;
- final ActivityInfo mInstantAppInstallerActivity = new ActivityInfo();
+ ActivityInfo mInstantAppInstallerActivity;
final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo();
final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
@@ -2931,13 +2931,16 @@
private void updateInstantAppInstallerLocked() {
final ComponentName oldInstantAppInstallerComponent = mInstantAppInstallerComponent;
- final ComponentName newInstantAppInstallerComponent = getEphemeralInstallerLPr();
+ final ActivityInfo newInstantAppInstaller = getEphemeralInstallerLPr();
+ ComponentName newInstantAppInstallerComponent = newInstantAppInstaller == null
+ ? null : newInstantAppInstaller.getComponentName();
+
if (newInstantAppInstallerComponent != null
&& !newInstantAppInstallerComponent.equals(oldInstantAppInstallerComponent)) {
if (DEBUG_EPHEMERAL) {
Slog.d(TAG, "Set ephemeral installer: " + newInstantAppInstallerComponent);
}
- setUpInstantAppInstallerActivityLP(newInstantAppInstallerComponent);
+ setUpInstantAppInstallerActivityLP(newInstantAppInstaller);
} else if (DEBUG_EPHEMERAL && newInstantAppInstallerComponent == null) {
Slog.d(TAG, "Unset ephemeral installer; none available");
}
@@ -3160,7 +3163,7 @@
return null;
}
- private @Nullable ComponentName getEphemeralInstallerLPr() {
+ private @Nullable ActivityInfo getEphemeralInstallerLPr() {
final Intent intent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
@@ -3186,7 +3189,7 @@
if (matches.size() == 0) {
return null;
} else if (matches.size() == 1) {
- return matches.get(0).getComponentInfo().getComponentName();
+ return (ActivityInfo) matches.get(0).getComponentInfo();
} else {
throw new RuntimeException(
"There must be at most one ephemeral installer; found " + matches);
@@ -10642,28 +10645,23 @@
}
}
- private void setUpInstantAppInstallerActivityLP(ComponentName installerComponent) {
- if (installerComponent == null) {
+ private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) {
+ if (installerActivity == null) {
if (DEBUG_EPHEMERAL) {
Slog.d(TAG, "Clear ephemeral installer activity");
}
- mInstantAppInstallerActivity.applicationInfo = null;
+ mInstantAppInstallerActivity = null;
return;
}
if (DEBUG_EPHEMERAL) {
- Slog.d(TAG, "Set ephemeral installer activity: " + installerComponent);
+ Slog.d(TAG, "Set ephemeral installer activity: "
+ + installerActivity.getComponentName());
}
- final PackageParser.Package pkg = mPackages.get(installerComponent.getPackageName());
// Set up information for ephemeral installer activity
- mInstantAppInstallerActivity.applicationInfo = pkg.applicationInfo;
- mInstantAppInstallerActivity.name = installerComponent.getClassName();
- mInstantAppInstallerActivity.packageName = pkg.applicationInfo.packageName;
- mInstantAppInstallerActivity.processName = pkg.applicationInfo.packageName;
- mInstantAppInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
- mInstantAppInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
+ mInstantAppInstallerActivity = installerActivity;
+ mInstantAppInstallerActivity.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
| ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
- mInstantAppInstallerActivity.theme = 0;
mInstantAppInstallerActivity.exported = true;
mInstantAppInstallerActivity.enabled = true;
mInstantAppInstallerInfo.activityInfo = mInstantAppInstallerActivity;
diff --git a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
index cd55f50..7d53310 100644
--- a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
+++ b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
@@ -27,6 +27,7 @@
import android.media.AudioAttributes;
import android.media.Ringtone;
import android.media.RingtoneManager;
+import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -58,6 +59,9 @@
private final Context mContext;
private AlertDialog mAlertDialog;
private boolean mIsShortcutEnabled;
+ private boolean mEnabledOnLockScreen;
+ private int mUserId;
+
// Visible for testing
public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
@@ -72,29 +76,55 @@
return context.getString(R.string.config_defaultAccessibilityService);
}
- public AccessibilityShortcutController(Context context, Handler handler) {
+ public AccessibilityShortcutController(Context context, Handler handler, int initialUserId) {
mContext = context;
- // Keep track of state of shortcut
+ // Keep track of state of shortcut settings
+ final ContentObserver co = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (userId == mUserId) {
+ onSettingsChanged();
+ }
+ }
+ };
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
- false,
- new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange) {
- onSettingsChanged();
- }
- },
- UserHandle.USER_ALL);
- updateShortcutEnabled();
+ false, co, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED),
+ false, co, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN),
+ false, co, UserHandle.USER_ALL);
+ setCurrentUser(mUserId);
}
- public boolean isAccessibilityShortcutAvailable() {
- return mIsShortcutEnabled;
+ public void setCurrentUser(int currentUserId) {
+ mUserId = currentUserId;
+ onSettingsChanged();
+ }
+
+ /**
+ * Check if the shortcut is available.
+ *
+ * @param onLockScreen Whether or not the phone is currently locked.
+ *
+ * @return {@code true} if the shortcut is available
+ */
+ public boolean isAccessibilityShortcutAvailable(boolean phoneLocked) {
+ return mIsShortcutEnabled && (!phoneLocked || mEnabledOnLockScreen);
}
public void onSettingsChanged() {
- updateShortcutEnabled();
+ final boolean haveValidService =
+ !TextUtils.isEmpty(getTargetServiceComponentNameString(mContext, mUserId));
+ final ContentResolver cr = mContext.getContentResolver();
+ final boolean enabled = Settings.Secure.getIntForUser(
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
+ mEnabledOnLockScreen = Settings.Secure.getIntForUser(
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, 0, mUserId) == 1;
+ mIsShortcutEnabled = enabled && haveValidService;
}
/**
@@ -171,11 +201,6 @@
}
}
- private void updateShortcutEnabled() {
- mIsShortcutEnabled = !TextUtils.isEmpty(getTargetServiceComponentNameString(
- mContext, UserHandle.myUserId()));
- }
-
private AlertDialog createShortcutWarningDialog(int userId) {
final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 31e22b9..52f6955 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1547,7 +1547,7 @@
}
private void interceptAccessibilityShortcutChord() {
- if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable()
+ if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(isKeyguardLocked())
&& mScreenshotChordVolumeDownKeyTriggered && mA11yShortcutChordVolumeUpKeyTriggered
&& !mScreenshotChordPowerKeyTriggered) {
final long now = SystemClock.uptimeMillis();
@@ -1771,7 +1771,7 @@
mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
mAccessibilityShortcutController =
- new AccessibilityShortcutController(mContext, new Handler());
+ new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
// Init display burn-in protection
boolean burnInProtectionEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_enableBurnInProtection);
@@ -3243,7 +3243,7 @@
// If an accessibility shortcut might be partially complete, hold off dispatching until we
// know if it is complete or not
- if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable()
+ if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(false)
&& (flags & KeyEvent.FLAG_FALLBACK) == 0) {
if (mScreenshotChordVolumeDownKeyTriggered ^ mA11yShortcutChordVolumeUpKeyTriggered) {
final long now = SystemClock.uptimeMillis();
@@ -5823,9 +5823,7 @@
mScreenshotChordVolumeDownKeyConsumed = false;
cancelPendingPowerKeyAction();
interceptScreenshotChord();
- if (!isKeyguardLocked()) {
- interceptAccessibilityShortcutChord();
- }
+ interceptAccessibilityShortcutChord();
}
} else {
mScreenshotChordVolumeDownKeyTriggered = false;
@@ -5841,9 +5839,7 @@
mA11yShortcutChordVolumeUpKeyConsumed = false;
cancelPendingPowerKeyAction();
cancelPendingScreenshotChordAction();
- if (!isKeyguardLocked()) {
- interceptAccessibilityShortcutChord();
- }
+ interceptAccessibilityShortcutChord();
}
} else {
mA11yShortcutChordVolumeUpKeyTriggered = false;
@@ -7945,6 +7941,9 @@
if (mKeyguardDelegate != null) {
mKeyguardDelegate.setCurrentUser(newUserId);
}
+ if (mAccessibilityShortcutController != null) {
+ mAccessibilityShortcutController.setCurrentUser(newUserId);
+ }
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
if (statusBar != null) {
statusBar.setCurrentUser(newUserId);
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 8d20531..1781d8c 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -43,7 +43,8 @@
ServiceType.NETWORK_FIREWALL,
ServiceType.SCREEN_BRIGHTNESS,
ServiceType.SOUND,
- ServiceType.BATTERY_STATS})
+ ServiceType.BATTERY_STATS,
+ ServiceType.DATA_SAVER})
public @interface ServiceType {
int NULL = 0;
int GPS = 1;
@@ -55,6 +56,7 @@
int SCREEN_BRIGHTNESS = 7;
int SOUND = 8;
int BATTERY_STATS = 9;
+ int DATA_SAVER = 10;
}
private static final String TAG = "BatterySaverPolicy";
@@ -73,6 +75,7 @@
private static final String KEY_SOUNDTRIGGER_DISABLED = "soundtrigger_disabled";
private static final String KEY_FIREWALL_DISABLED = "firewall_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled";
+ private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
@@ -137,6 +140,14 @@
private boolean mAdjustBrightnessDisabled;
/**
+ * {@code true} if data saver is disabled in battery saver mode.
+ *
+ * @see Settings.Global#BATTERY_SAVER_CONSTANTS
+ * @see #KEY_DATASAVER_DISABLED
+ */
+ private boolean mDataSaverDisabled;
+
+ /**
* This is the flag to decide the gps mode in battery saver mode.
*
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
@@ -191,6 +202,7 @@
mFireWallDisabled = mParser.getBoolean(KEY_FIREWALL_DISABLED, false);
mAdjustBrightnessDisabled = mParser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
mAdjustBrightnessFactor = mParser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
+ mDataSaverDisabled = mParser.getBoolean(KEY_DATASAVER_DISABLED, true);
// Get default value from Settings.Secure
final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
@@ -210,7 +222,8 @@
*/
public PowerSaveState getBatterySaverPolicy(@ServiceType int type, boolean realMode) {
synchronized (BatterySaverPolicy.this) {
- final PowerSaveState.Builder builder = new PowerSaveState.Builder();
+ final PowerSaveState.Builder builder = new PowerSaveState.Builder()
+ .setGlobalBatterySaverEnabled(realMode);
if (!realMode) {
return builder.setBatterySaverEnabled(realMode)
.build();
@@ -236,6 +249,9 @@
return builder.setBatterySaverEnabled(!mAdjustBrightnessDisabled)
.setBrightnessFactor(mAdjustBrightnessFactor)
.build();
+ case ServiceType.DATA_SAVER:
+ return builder.setBatterySaverEnabled(!mDataSaverDisabled)
+ .build();
case ServiceType.SOUND:
return builder.setBatterySaverEnabled(mSoundTriggerDisabled)
.build();
@@ -262,6 +278,7 @@
pw.println(" " + KEY_FULLBACKUP_DEFERRED + "=" + mFullBackupDeferred);
pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
+ pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
diff --git a/services/core/java/com/android/server/security/KeyChainSystemService.java b/services/core/java/com/android/server/security/KeyChainSystemService.java
index bfeaee6..2f681a3 100644
--- a/services/core/java/com/android/server/security/KeyChainSystemService.java
+++ b/services/core/java/com/android/server/security/KeyChainSystemService.java
@@ -22,10 +22,13 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.os.Process;
import android.os.UserHandle;
import android.security.IKeyChainService;
import android.util.Slog;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
/**
@@ -45,6 +48,11 @@
private static final String TAG = "KeyChainSystemService";
+ /**
+ * Maximum time limit for the KeyChain app to deal with packages being removed.
+ */
+ private static final int KEYCHAIN_IDLE_WHITELIST_DURATION_MS = 30 * 1000;
+
public KeyChainSystemService(final Context context) {
super(context);
}
@@ -77,10 +85,25 @@
}
intent.setComponent(service);
intent.setAction(broadcastIntent.getAction());
- getContext().startServiceAsUser(intent, UserHandle.of(getSendingUserId()));
+ startServiceInBackgroundAsUser(intent, UserHandle.of(getSendingUserId()));
} catch (RuntimeException e) {
Slog.e(TAG, "Unable to forward package removed broadcast to KeyChain", e);
}
}
};
+
+
+ private void startServiceInBackgroundAsUser(final Intent intent, final UserHandle user) {
+ if (intent.getComponent() == null) {
+ return;
+ }
+
+ final String packageName = intent.getComponent().getPackageName();
+ final DeviceIdleController.LocalService idleController =
+ LocalServices.getService(DeviceIdleController.LocalService.class);
+ idleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName,
+ KEYCHAIN_IDLE_WHITELIST_DURATION_MS, user.getIdentifier(), false, "keychain");
+
+ getContext().startServiceAsUser(intent, user);
+ }
}
diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java
index 5a1f473..904d9159 100644
--- a/services/core/java/com/android/server/storage/AppFuseBridge.java
+++ b/services/core/java/com/android/server/storage/AppFuseBridge.java
@@ -19,18 +19,20 @@
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileNotFoundException;
-import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
/**
* Runnable that delegates FUSE command from the kernel to application.
* run() blocks until all opened files on the FUSE mount point are closed. So this should be run in
* a separated thread.
*/
-public class AppFuseBridge implements Runnable, AutoCloseable {
+public class AppFuseBridge implements Runnable {
public static final String TAG = "AppFuseBridge";
/**
@@ -41,71 +43,138 @@
*/
private static final String APPFUSE_MOUNT_NAME_TEMPLATE = "/mnt/appfuse/%d_%d";
- private final IMountScope mMountScope;
- private final ParcelFileDescriptor mProxyFd;
- private final BlockingQueue<Boolean> mChannel;
+ @GuardedBy("this")
+ private final SparseArray<MountScope> mScopes = new SparseArray<>();
- /**
- * @param mountScope Listener to unmount mount point.
- * @param proxyFd FD of socket pair. Ownership of FD is taken by AppFuseBridge.
- * @param channel Channel that the runnable send mount result to.
- */
- public AppFuseBridge(
- IMountScope mountScope, ParcelFileDescriptor proxyFd, BlockingQueue<Boolean> channel) {
- Preconditions.checkNotNull(mountScope);
- Preconditions.checkNotNull(proxyFd);
- Preconditions.checkNotNull(channel);
- mMountScope = mountScope;
- mProxyFd = proxyFd;
- mChannel = channel;
+ @GuardedBy("this")
+ private long mNativeLoop;
+
+ public AppFuseBridge() {
+ mNativeLoop = native_new();
+ }
+
+ public ParcelFileDescriptor addBridge(MountScope mountScope)
+ throws BridgeException {
+ try {
+ synchronized (this) {
+ Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0);
+ if (mNativeLoop == 0) {
+ throw new BridgeException("The thread has already been terminated");
+ }
+ final int fd = native_add_bridge(
+ mNativeLoop, mountScope.mountId, mountScope.deviceFd.detachFd());
+ if (fd == -1) {
+ throw new BridgeException("Failed to invoke native_add_bridge");
+ }
+ final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd);
+ mScopes.put(mountScope.mountId, mountScope);
+ mountScope = null;
+ return result;
+ }
+ } finally {
+ IoUtils.closeQuietly(mountScope);
+ }
}
@Override
public void run() {
- try {
- // deviceFd and proxyFd must be closed in native_start_loop.
- native_start_loop(
- mMountScope.getDeviceFileDescriptor().detachFd(),
- mProxyFd.detachFd());
- } finally {
- close();
+ native_start_loop(mNativeLoop);
+ synchronized (this) {
+ native_delete(mNativeLoop);
+ mNativeLoop = 0;
}
}
- public static ParcelFileDescriptor openFile(int uid, int mountId, int fileId, int mode)
- throws FileNotFoundException {
- final File mountPoint = getMountPoint(uid, mountId);
+ public ParcelFileDescriptor openFile(int pid, int mountId, int fileId, int mode)
+ throws FileNotFoundException, SecurityException, InterruptedException {
+ final MountScope scope;
+ synchronized (this) {
+ scope = mScopes.get(mountId);
+ if (scope == null) {
+ throw new FileNotFoundException("Cannot find mount point");
+ }
+ }
+ if (scope.pid != pid) {
+ throw new SecurityException("PID does not match");
+ }
+ final boolean result = scope.waitForMount();
+ if (result == false) {
+ throw new FileNotFoundException("Mount failed");
+ }
try {
- if (Os.stat(mountPoint.getPath()).st_ino != 1) {
+ if (Os.stat(scope.mountPoint.getPath()).st_ino != 1) {
throw new FileNotFoundException("Could not find bridge mount point.");
}
} catch (ErrnoException e) {
throw new FileNotFoundException(
- "Failed to stat mount point: " + mountPoint.getParent());
+ "Failed to stat mount point: " + scope.mountPoint.getParent());
}
- return ParcelFileDescriptor.open(new File(mountPoint, String.valueOf(fileId)), mode);
- }
-
- private static File getMountPoint(int uid, int mountId) {
- return new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId));
- }
-
- @Override
- public void close() {
- IoUtils.closeQuietly(mMountScope);
- IoUtils.closeQuietly(mProxyFd);
- // Invoke countDown here in case where close is invoked before mount.
- mChannel.offer(false);
+ return ParcelFileDescriptor.open(new File(scope.mountPoint, String.valueOf(fileId)), mode);
}
// Used by com_android_server_storage_AppFuse.cpp.
- private void onMount() {
- mChannel.offer(true);
+ synchronized private void onMount(int mountId) {
+ final MountScope scope = mScopes.get(mountId);
+ if (scope != null) {
+ scope.setMountResultLocked(true);
+ }
}
- public static interface IMountScope extends AutoCloseable {
- ParcelFileDescriptor getDeviceFileDescriptor();
+ // Used by com_android_server_storage_AppFuse.cpp.
+ synchronized private void onClosed(int mountId) {
+ final MountScope scope = mScopes.get(mountId);
+ if (scope != null) {
+ scope.setMountResultLocked(false);
+ IoUtils.closeQuietly(scope);
+ mScopes.remove(mountId);
+ }
}
- private native boolean native_start_loop(int deviceFd, int proxyFd);
+ public static class MountScope implements AutoCloseable {
+ public final int uid;
+ public final int pid;
+ public final int mountId;
+ public final ParcelFileDescriptor deviceFd;
+ public final File mountPoint;
+ private final CountDownLatch mMounted = new CountDownLatch(1);
+ private boolean mMountResult = false;
+
+ public MountScope(int uid, int pid, int mountId, ParcelFileDescriptor deviceFd) {
+ this.uid = uid;
+ this.pid = pid;
+ this.mountId = mountId;
+ this.deviceFd = deviceFd;
+ this.mountPoint = new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId));
+ }
+
+ @GuardedBy("AppFuseBridge.this")
+ void setMountResultLocked(boolean result) {
+ if (mMounted.getCount() == 0) {
+ return;
+ }
+ mMountResult = result;
+ mMounted.countDown();
+ }
+
+ boolean waitForMount() throws InterruptedException {
+ mMounted.await();
+ return mMountResult;
+ }
+
+ @Override
+ public void close() throws Exception {
+ deviceFd.close();
+ }
+ }
+
+ public static class BridgeException extends Exception {
+ public BridgeException(String message) {
+ super(message);
+ }
+ }
+
+ private native long native_new();
+ private native void native_delete(long loop);
+ private native void native_start_loop(long loop);
+ private native int native_add_bridge(long loop, int mountId, int deviceId);
}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index e1df0ba..731f53f 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -37,6 +37,7 @@
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
@@ -44,6 +45,7 @@
import android.service.vr.IVrListener;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
+import android.service.vr.IVrWindowManager;
import android.service.vr.VrListenerService;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -427,6 +429,18 @@
}
@Override
+ public void connectController(FileDescriptor fd) throws android.os.RemoteException {
+ enforceCallerPermission(Manifest.permission.RESTRICTED_VR_ACCESS);
+ VrManagerService.this.connectController(fd);
+ }
+
+ @Override
+ public void disconnectController() throws android.os.RemoteException {
+ enforceCallerPermission(Manifest.permission.RESTRICTED_VR_ACCESS);
+ VrManagerService.this.disconnectController();
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -1151,4 +1165,20 @@
return mVrModeEnabled;
}
}
+
+ private void connectController(FileDescriptor fd) throws android.os.RemoteException {
+ // TODO(b/36506799): move vr_wm code to VrCore and remove this.
+ IVrWindowManager remote =
+ IVrWindowManager.Stub.asInterface(
+ ServiceManager.getService(IVrWindowManager.SERVICE_NAME));
+ remote.connectController(fd);
+ }
+
+ private void disconnectController() throws android.os.RemoteException {
+ // TODO(b/36506799): move vr_wm code to VrCore and remove this.
+ IVrWindowManager remote =
+ IVrWindowManager.Stub.asInterface(
+ ServiceManager.getService(IVrWindowManager.SERVICE_NAME));
+ remote.disconnectController();
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index ef3d87c..e60295d 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -32,9 +32,11 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Trace;
import android.util.Slog;
import android.view.IApplicationToken;
@@ -180,12 +182,13 @@
IApplicationToken token, AppWindowContainerListener listener, int index,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
- int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) {
+ int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
+ Configuration overrideConfig, Rect bounds) {
this(taskController, token, listener, index, requestedOrientation, fullscreen,
showForAllUsers,
configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
- WindowManagerService.getInstance());
+ WindowManagerService.getInstance(), overrideConfig, bounds);
}
public AppWindowContainerController(TaskWindowContainerController taskController,
@@ -193,7 +196,7 @@
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
- WindowManagerService service) {
+ WindowManagerService service, Configuration overrideConfig, Rect bounds) {
super(listener, service);
mHandler = new Handler(service.mH.getLooper());
mToken = token;
@@ -214,7 +217,7 @@
atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
- alwaysFocusable, this);
+ alwaysFocusable, this, overrideConfig, bounds);
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
+ " controller=" + taskController + " at " + index);
task.addChild(atoken, index);
@@ -226,11 +229,12 @@
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller) {
+ boolean alwaysFocusable, AppWindowContainerController controller,
+ Configuration overrideConfig, Rect bounds) {
return new AppWindowToken(service, token, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
- controller);
+ controller, overrideConfig, bounds);
}
public void removeContainer(int displayId) {
@@ -297,6 +301,17 @@
}
}
+ // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as
+ // a generic way to set override config. Need to untangle current ways the override config is
+ // currently set for tasks and displays before we are doing that though.
+ public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+ synchronized(mWindowMap) {
+ if (mContainer != null) {
+ mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds);
+ }
+ }
+ }
+
public void setDisablePreviewScreenshots(boolean disable) {
synchronized (mWindowMap) {
if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index d1f1305..72ae90d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -164,6 +164,11 @@
private boolean mLastContainsShowWhenLockedWindow;
private boolean mLastContainsDismissKeyguardWindow;
+ // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+ // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly
+ // affects the configuration. We should probably move this into that class.
+ private final Rect mBounds = new Rect();
+
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
@@ -173,8 +178,8 @@
DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
- AppWindowContainerController controller) {
- this(service, token, voiceInteraction, dc, fullscreen);
+ AppWindowContainerController controller, Configuration overrideConfig, Rect bounds) {
+ this(service, token, voiceInteraction, dc, fullscreen, overrideConfig, bounds);
setController(controller);
mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
mShowForAllUsers = showForAllUsers;
@@ -191,7 +196,7 @@
}
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
- DisplayContent dc, boolean fillsParent) {
+ DisplayContent dc, boolean fillsParent, Configuration overrideConfig, Rect bounds) {
super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
false /* ownerCanManageAppTokens */);
appToken = token;
@@ -199,6 +204,30 @@
mFillsParent = fillsParent;
mInputApplicationHandle = new InputApplicationHandle(this);
mAppAnimator = new AppWindowAnimator(this, service);
+ if (overrideConfig != null) {
+ onOverrideConfigurationChanged(overrideConfig);
+ }
+ if (bounds != null) {
+ mBounds.set(bounds);
+ }
+ }
+
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+ onOverrideConfigurationChanged(overrideConfiguration);
+ if (mBounds.equals(bounds)) {
+ return;
+ }
+ // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set.
+ mBounds.set(bounds);
+ onResize();
+ }
+
+ void getBounds(Rect outBounds) {
+ outBounds.set(mBounds);
+ }
+
+ boolean hasBounds() {
+ return !mBounds.isEmpty();
}
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e5b00f3..8f391a7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3044,6 +3044,12 @@
mTaskStackContainers.removeExistingAppTokensIfPossible();
}
+ @Override
+ void onDescendantOverrideConfigurationChanged() {
+ setLayoutNeeded();
+ mService.requestTraversal();
+ }
+
static final class TaskForResizePointSearchResult {
boolean searchDone;
Task taskForResize;
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index fc37797..85eae02 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -540,12 +540,14 @@
checkMinimizeChanged(true /* animate */);
// We were minimized, and now we are still minimized, but somebody is trying to launch an
- // app in docked stack, better show recent apps so we actually get unminimized! This catches
+ // app in docked stack, better show recent apps so we actually get unminimized! However do
+ // not do this if keyguard is dismissed such as when the device is unlocking. This catches
// any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because
// we couldn't retrace the launch of the app in the docked stack to the launch from
// homescreen.
if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps)
- && appTransition != TRANSIT_NONE) {
+ && appTransition != TRANSIT_NONE &&
+ !AppTransition.isKeyguardGoingAwayTransit(appTransition)) {
mService.showRecentApps(true /* fromHome */);
}
}
@@ -579,6 +581,12 @@
if (homeTask == null || !isWithinDisplay(homeTask)) {
return;
}
+
+ // Do not minimize when dock is already minimized while keyguard is showing and not
+ // occluded such as unlocking the screen
+ if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
+ return;
+ }
final TaskStack fullscreenStack =
mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6973c3c..2a02359 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.EMPTY;
/**
* Defines common functionality for classes that can hold windows directly or through their
@@ -315,9 +316,22 @@
void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
mOverrideConfiguration.setTo(overrideConfiguration);
// Update full configuration of this container and all its children.
- onConfigurationChanged(mParent != null ? mParent.getConfiguration() : Configuration.EMPTY);
+ onConfigurationChanged(mParent != null ? mParent.getConfiguration() : EMPTY);
// Update merged override config of this container and all its children.
onMergedOverrideConfigurationChanged();
+
+ if (mParent != null) {
+ mParent.onDescendantOverrideConfigurationChanged();
+ }
+ }
+
+ /**
+ * Notify that a descendant's overrideConfiguration has changed.
+ */
+ void onDescendantOverrideConfigurationChanged() {
+ if (mParent != null) {
+ mParent.onDescendantOverrideConfigurationChanged();
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d4c8b1f..6fd95a4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -717,16 +717,16 @@
mHaveFrame = true;
final Task task = getTask();
- final boolean fullscreenTask = !isInMultiWindowMode();
+ final boolean inFullscreenContainer = inFullscreenContainer();
final boolean windowsAreFloating = task != null && task.isFloating();
final DisplayContent dc = getDisplayContent();
// If the task has temp inset bounds set, we have to make sure all its windows uses
// the temp inset frame. Otherwise different display frames get applied to the main
// window and the child window, making them misaligned.
- if (fullscreenTask) {
+ if (inFullscreenContainer) {
mInsetFrame.setEmpty();
- } else {
+ } else if (task != null && isInMultiWindowMode()) {
task.getTempInsetBounds(mInsetFrame);
}
@@ -740,7 +740,7 @@
// The offset from the layout containing frame to the actual containing frame.
final int layoutXDiff;
final int layoutYDiff;
- if (fullscreenTask || layoutInParentFrame()) {
+ if (inFullscreenContainer || layoutInParentFrame()) {
// We use the parent frame as the containing frame for fullscreen and child windows
mContainingFrame.set(parentFrame);
mDisplayFrame.set(displayFrame);
@@ -749,7 +749,7 @@
layoutXDiff = 0;
layoutYDiff = 0;
} else {
- task.getBounds(mContainingFrame);
+ getContainerBounds(mContainingFrame);
if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
// If the bounds are frozen, we still want to translate the window freely and only
@@ -883,7 +883,7 @@
Math.min(mStableFrame.bottom, mFrame.bottom));
}
- if (fullscreenTask && !windowsAreFloating) {
+ if (inFullscreenContainer && !windowsAreFloating) {
// Windows that are not fullscreen can be positioned outside of the display frame,
// but that is not a reason to provide them with overscan insets.
mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
@@ -908,10 +908,10 @@
getDisplayContent().getLogicalDisplayRect(mTmpRect);
// Override right and/or bottom insets in case if the frame doesn't fit the screen in
// non-fullscreen mode.
- boolean overrideRightInset = !windowsAreFloating && !fullscreenTask &&
- mFrame.right > mTmpRect.right;
- boolean overrideBottomInset = !windowsAreFloating && !fullscreenTask &&
- mFrame.bottom > mTmpRect.bottom;
+ boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
+ && mFrame.right > mTmpRect.right;
+ boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
+ && mFrame.bottom > mTmpRect.bottom;
mContentInsets.set(mContentFrame.left - mFrame.left,
mContentFrame.top - mFrame.top,
overrideRightInset ? mTmpRect.right - mContentFrame.right
@@ -3122,6 +3122,28 @@
return task != null && !task.isFullscreen();
}
+ /** Is this window in a container that takes up the entire screen space? */
+ private boolean inFullscreenContainer() {
+ if (mAppToken == null) {
+ return true;
+ }
+ if (mAppToken.hasBounds()) {
+ return false;
+ }
+ return !isInMultiWindowMode();
+ }
+
+ /** Returns the appropriate bounds to use for computing frames. */
+ private void getContainerBounds(Rect outBounds) {
+ if (isInMultiWindowMode()) {
+ getTask().getBounds(outBounds);
+ } else if (mAppToken != null){
+ mAppToken.getBounds(outBounds);
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}
@@ -3137,7 +3159,7 @@
/**
* @return Whether we reported a drag resize change to the application or not already.
*/
- boolean isDragResizingChangeReported() {
+ private boolean isDragResizingChangeReported() {
return mDragResizingChangeReported;
}
@@ -3154,7 +3176,7 @@
* Set whether we got resized but drag resizing flag was false.
* @see #isResizedWhileNotDragResizing().
*/
- void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
+ private void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
mResizedWhileNotDragResizing = resizedWhileNotDragResizing;
mResizedWhileNotDragResizingReported = !resizedWhileNotDragResizing;
}
@@ -3459,17 +3481,17 @@
final int pw = containingFrame.width();
final int ph = containingFrame.height();
final Task task = getTask();
- final boolean nonFullscreenTask = isInMultiWindowMode();
+ final boolean inNonFullscreenContainer = !inFullscreenContainer();
final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
// We need to fit it to the display if either
- // a) The task is fullscreen, or we don't have a task (we assume fullscreen for the taskless
- // windows)
+ // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
+ // for the taskless windows)
// b) If it's a secondary app window, we also need to fit it to the display unless
- // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on screen,
- // but SurfaceViews want to be always at a specific location so we don't fit it to the
- // display.
- final boolean fitToDisplay = (task == null || !nonFullscreenTask)
+ // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
+ // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
+ // the display.
+ final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
|| ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
float x, y;
int w,h;
@@ -3514,7 +3536,7 @@
y = mAttrs.y;
}
- if (nonFullscreenTask && !layoutInParentFrame()) {
+ if (inNonFullscreenContainer && !layoutInParentFrame()) {
// Make sure window fits in containing frame since it is in a non-fullscreen task as
// required by {@link Gravity#apply} call.
w = Math.min(w, pw);
diff --git a/services/core/jni/com_android_server_storage_AppFuseBridge.cpp b/services/core/jni/com_android_server_storage_AppFuseBridge.cpp
index 2f20ecd..c8f842d 100644
--- a/services/core/jni/com_android_server_storage_AppFuseBridge.cpp
+++ b/services/core/jni/com_android_server_storage_AppFuseBridge.cpp
@@ -19,16 +19,19 @@
#include <android_runtime/Log.h>
#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
#include <core_jni_helpers.h>
#include <libappfuse/FuseBridgeLoop.h>
+#include <libappfuse/FuseBuffer.h>
#include <nativehelper/JNIHelp.h>
namespace android {
namespace {
constexpr const char* CLASS_NAME = "com/android/server/storage/AppFuseBridge";
-static jclass appFuseClass;
-static jmethodID appFuseOnMount;
+static jclass gAppFuseClass;
+static jmethodID gAppFuseOnMount;
+static jmethodID gAppFuseOnClosed;
class Callback : public fuse::FuseBridgeLoopCallback {
JNIEnv* mEnv;
@@ -36,8 +39,16 @@
public:
Callback(JNIEnv* env, jobject self) : mEnv(env), mSelf(self) {}
- void OnMount() override {
- mEnv->CallVoidMethod(mSelf, appFuseOnMount);
+ void OnMount(int mount_id) override {
+ mEnv->CallVoidMethod(mSelf, gAppFuseOnMount, mount_id);
+ if (mEnv->ExceptionCheck()) {
+ LOGE_EX(mEnv, nullptr);
+ mEnv->ExceptionClear();
+ }
+ }
+
+ void OnClosed(int mount_id) override {
+ mEnv->CallVoidMethod(mSelf, gAppFuseOnClosed, mount_id);
if (mEnv->ExceptionCheck()) {
LOGE_EX(mEnv, nullptr);
mEnv->ExceptionClear();
@@ -45,17 +56,93 @@
}
};
-jboolean com_android_server_storage_AppFuseBridge_start_loop(
- JNIEnv* env, jobject self, jint devJavaFd, jint proxyJavaFd) {
+class MonitorScope final {
+public:
+ MonitorScope(JNIEnv* env, jobject obj) : mEnv(env), mObj(obj), mLocked(false) {
+ if (mEnv->MonitorEnter(obj) == JNI_OK) {
+ mLocked = true;
+ } else {
+ LOG(ERROR) << "Failed to enter monitor.";
+ }
+ }
+
+ ~MonitorScope() {
+ if (mLocked) {
+ if (mEnv->MonitorExit(mObj) != JNI_OK) {
+ LOG(ERROR) << "Failed to exit monitor.";
+ }
+ }
+ }
+
+ operator bool() {
+ return mLocked;
+ }
+
+private:
+ // Lifetime of |MonitorScope| must be shorter than the reference of mObj.
+ JNIEnv* mEnv;
+ jobject mObj;
+ bool mLocked;
+
+ DISALLOW_COPY_AND_ASSIGN(MonitorScope);
+};
+
+jlong com_android_server_storage_AppFuseBridge_new(JNIEnv* env, jobject self) {
+ return reinterpret_cast<jlong>(new fuse::FuseBridgeLoop());
+}
+
+void com_android_server_storage_AppFuseBridge_delete(JNIEnv* env, jobject self, jlong java_loop) {
+ fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
+ CHECK(loop);
+ delete loop;
+}
+
+void com_android_server_storage_AppFuseBridge_start_loop(
+ JNIEnv* env, jobject self, jlong java_loop) {
+ fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
+ CHECK(loop);
Callback callback(env, self);
- return fuse::StartFuseBridgeLoop(devJavaFd, proxyJavaFd, &callback);
+ loop->Start(&callback);
+}
+
+jint com_android_server_storage_AppFuseBridge_add_bridge(
+ JNIEnv* env, jobject self, jlong java_loop, jint mountId, jint javaDevFd) {
+ base::unique_fd devFd(javaDevFd);
+ fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
+ CHECK(loop);
+
+ base::unique_fd proxyFd[2];
+ if (!fuse::SetupMessageSockets(&proxyFd)) {
+ return -1;
+ }
+
+ if (!loop->AddBridge(mountId, std::move(devFd), std::move(proxyFd[0]))) {
+ return -1;
+ }
+
+ return proxyFd[1].release();
}
const JNINativeMethod methods[] = {
{
+ "native_new",
+ "()J",
+ reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_new)
+ },
+ {
+ "native_delete",
+ "(J)V",
+ reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_delete)
+ },
+ {
"native_start_loop",
- "(II)Z",
- (void *) com_android_server_storage_AppFuseBridge_start_loop
+ "(J)V",
+ reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_start_loop)
+ },
+ {
+ "native_add_bridge",
+ "(JII)I",
+ reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_add_bridge)
}
};
@@ -64,8 +151,9 @@
void register_android_server_storage_AppFuse(JNIEnv* env) {
CHECK(env != nullptr);
- appFuseClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
- appFuseOnMount = GetMethodIDOrDie(env, appFuseClass, "onMount", "()V");
+ gAppFuseClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
+ gAppFuseOnMount = GetMethodIDOrDie(env, gAppFuseClass, "onMount", "(I)V");
+ gAppFuseOnClosed = GetMethodIDOrDie(env, gAppFuseClass, "onClosed", "(I)V");
RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods));
}
} // namespace android
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ecbd312..ab86966 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10088,9 +10088,11 @@
return false;
}
- Preconditions.checkNotNull(admin);
synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ if (!isCallerWithSystemUid()) {
+ Preconditions.checkNotNull(admin);
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
return mInjector.securityLogGetLoggingEnabledProperty();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index f8d105e..29c6f89 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -37,6 +37,7 @@
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -51,10 +52,15 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -89,6 +95,7 @@
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
import android.os.UserHandle;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
@@ -198,6 +205,7 @@
private IUidObserver mUidObserver;
private INetworkManagementEventObserver mNetworkObserver;
+ private PowerManagerInternal mPowerManagerInternal;
private NetworkPolicyListenerAnswer mPolicyListener;
private NetworkPolicyManagerService mService;
@@ -227,12 +235,16 @@
@BeforeClass
public static void registerLocalServices() {
- addLocalServiceMock(PowerManagerInternal.class);
addLocalServiceMock(DeviceIdleController.LocalService.class);
final UsageStatsManagerInternal usageStats =
addLocalServiceMock(UsageStatsManagerInternal.class);
when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
mActivityManagerInternal = addLocalServiceMock(ActivityManagerInternal.class);
+
+ final PowerSaveState state = new PowerSaveState.Builder()
+ .setBatterySaverEnabled(false).build();
+ final PowerManagerInternal pmInternal = addLocalServiceMock(PowerManagerInternal.class);
+ when(pmInternal.getLowPowerState(anyInt())).thenReturn(state);
}
@Before
@@ -401,6 +413,85 @@
removeRestrictBackgroundWhitelist(false);
}
+ @Test
+ public void testLowPowerModeObserver_ListenersRegistered()
+ throws Exception {
+ PowerManagerInternal pmInternal = LocalServices.getService(PowerManagerInternal.class);
+
+ verify(pmInternal, atLeast(2)).registerLowPowerModeObserver(any());
+ }
+
+ @Test
+ public void updateRestrictBackgroundByLowPowerMode_RestrictOnBeforeBsm_RestrictOnAfterBsm()
+ throws Exception {
+ setRestrictBackground(true);
+ PowerSaveState stateOn = new PowerSaveState.Builder()
+ .setGlobalBatterySaverEnabled(true)
+ .setBatterySaverEnabled(false)
+ .build();
+ mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
+
+ // RestrictBackground should be on even though battery saver want to turn it off
+ assertThat(mService.getRestrictBackground()).isTrue();
+
+ PowerSaveState stateOff = new PowerSaveState.Builder()
+ .setGlobalBatterySaverEnabled(false)
+ .setBatterySaverEnabled(false)
+ .build();
+ mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
+
+ // RestrictBackground should be on, following its previous state
+ assertThat(mService.getRestrictBackground()).isTrue();
+ }
+
+ @Test
+ public void updateRestrictBackgroundByLowPowerMode_RestrictOffBeforeBsm_RestrictOffAfterBsm()
+ throws Exception {
+ setRestrictBackground(false);
+ PowerSaveState stateOn = new PowerSaveState.Builder()
+ .setGlobalBatterySaverEnabled(true)
+ .setBatterySaverEnabled(true)
+ .build();
+
+ doReturn(true).when(mNetworkManager).setDataSaverModeEnabled(true);
+ mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
+
+ // RestrictBackground should be turned on because of battery saver
+ assertThat(mService.getRestrictBackground()).isTrue();
+
+ PowerSaveState stateOff = new PowerSaveState.Builder()
+ .setGlobalBatterySaverEnabled(false)
+ .setBatterySaverEnabled(false)
+ .build();
+ mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
+
+ // RestrictBackground should be off, following its previous state
+ assertThat(mService.getRestrictBackground()).isFalse();
+ }
+
+ @Test
+ public void updateRestrictBackgroundByLowPowerMode_StatusChangedInBsm_DoNotRestore()
+ throws Exception {
+ setRestrictBackground(true);
+ PowerSaveState stateOn = new PowerSaveState.Builder()
+ .setGlobalBatterySaverEnabled(true)
+ .setBatterySaverEnabled(true)
+ .build();
+ mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
+
+ // RestrictBackground should still be on
+ assertThat(mService.getRestrictBackground()).isTrue();
+
+ // User turns off RestrictBackground manually
+ setRestrictBackground(false);
+ PowerSaveState stateOff = new PowerSaveState.Builder().setBatterySaverEnabled(
+ false).build();
+ mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
+
+ // RestrictBackground should be off because user changes it manually
+ assertThat(mService.getRestrictBackground()).isFalse();
+ }
+
private void removeRestrictBackgroundWhitelist(boolean expectIntent) throws Exception {
// Sanity checks.
assertWhitelistUids(UID_A);
@@ -1231,7 +1322,7 @@
private void setRestrictBackground(boolean flag) throws Exception {
// Must set expectation, otherwise NMPS will reset value to previous one.
- when(mNetworkManager.setDataSaverModeEnabled(flag)).thenReturn(true);
+ doReturn(true).when(mNetworkManager).setDataSaverModeEnabled(flag);
mService.setRestrictBackground(flag);
// Sanity check.
assertEquals("restrictBackground not set", flag, mService.getRestrictBackground());
diff --git a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
index 4d5f783..a4e3988 100644
--- a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
@@ -52,6 +52,8 @@
import java.util.Collections;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -76,6 +78,12 @@
(int) VIBRATOR_PATTERN_2};
private static final long[] VIBRATOR_PATTERN_LONG = {VIBRATOR_PATTERN_1, VIBRATOR_PATTERN_2};
+ // Convenience values for enabling/disabling to make code more readable
+ private static final int DISABLED = 0;
+ private static final int ENABLED_EXCEPT_LOCK_SCREEN = 1;
+ private static final int ENABLED_INCLUDING_LOCK_SCREEN = 2;
+ private static final int DISABLED_BUT_LOCK_SCREEN_ON = 3;
+
private @Mock Context mContext;
private @Mock FrameworkObjectProvider mFrameworkObjectProvider;
private @Mock IAccessibilityManager mAccessibilityManagerService;
@@ -158,38 +166,103 @@
}
@Test
- public void testShortcutAvailable_withNullServiceIdWhenCreated_shouldReturnFalse() {
- configureShortcutDisabled();
- assertFalse(getController().isAccessibilityShortcutAvailable());
+ public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse() {
+ configureNoShortcutService();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ assertFalse(getController().isAccessibilityShortcutAvailable(false));
}
@Test
- public void testShortcutAvailable_withNonNullServiceIdWhenCreated_shouldReturnTrue() {
- configureShortcutEnabled();
- assertTrue(getController().isAccessibilityShortcutAvailable());
+ public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue() {
+ configureValidShortcutService();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ assertTrue(getController().isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_disabledWithValidServiceWhenCreated_shouldReturnFalse() {
+ configureValidShortcutService();
+ configureShortcutEnabled(DISABLED_BUT_LOCK_SCREEN_ON);
+ assertFalse(getController().isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse() {
+ configureValidShortcutService();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ assertFalse(getController().isAccessibilityShortcutAvailable(true));
+ }
+
+ @Test
+ public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue() {
+ configureValidShortcutService();
+ configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+ assertTrue(getController().isAccessibilityShortcutAvailable(true));
}
@Test
public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse() {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
accessibilityShortcutController.onSettingsChanged();
- assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable());
+ assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
}
@Test
public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue() {
- configureShortcutDisabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureNoShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
- configureShortcutEnabled();
+ configureValidShortcutService();
accessibilityShortcutController.onSettingsChanged();
- assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable());
+ assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_whenShortcutBecomesDisabled_shouldReturnFalse() {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled(DISABLED);
+ accessibilityShortcutController.onSettingsChanged();
+ assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue() {
+ configureShortcutEnabled(DISABLED);
+ configureValidShortcutService();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ accessibilityShortcutController.onSettingsChanged();
+ assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse() {
+ configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+ configureValidShortcutService();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ accessibilityShortcutController.onSettingsChanged();
+ assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
+ }
+
+ @Test
+ public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue() {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+ accessibilityShortcutController.onSettingsChanged();
+ assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
}
@Test
public void testOnAccessibilityShortcut_vibrates() {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
AccessibilityShortcutController accessibilityShortcutController = getController();
accessibilityShortcutController.performAccessibilityShortcut();
verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject());
@@ -198,7 +271,8 @@
@Test
public void testOnAccessibilityShortcut_firstTime_showsWarningDialog()
throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
accessibilityShortcutController.performAccessibilityShortcut();
@@ -214,7 +288,8 @@
@Test
public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
accessibilityShortcutController.performAccessibilityShortcut();
@@ -229,7 +304,8 @@
@Test
public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
accessibilityShortcutController.performAccessibilityShortcut();
@@ -245,7 +321,8 @@
@Test
public void testClickingDisableButtonInDialog_shouldClearShortcutId() {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
getController().performAccessibilityShortcut();
@@ -261,7 +338,8 @@
@Test
public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
getController().performAccessibilityShortcut();
@@ -281,7 +359,8 @@
@Test
public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
getController().performAccessibilityShortcut();
@@ -290,18 +369,48 @@
verify(mAccessibilityManagerService).performAccessibilityShortcut();
}
- private void configureShortcutDisabled() {
+ private void configureNoShortcutService() {
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
}
- private void configureShortcutEnabled() {
+ private void configureValidShortcutService() {
Settings.Secure.putString(
mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
}
+ private void configureShortcutEnabled(int enabledValue) {
+ final boolean enabled;
+ final boolean lockscreen;
+
+ switch (enabledValue) {
+ case DISABLED:
+ enabled = false;
+ lockscreen = false;
+ break;
+ case DISABLED_BUT_LOCK_SCREEN_ON:
+ enabled = false;
+ lockscreen = true;
+ break;
+ case ENABLED_INCLUDING_LOCK_SCREEN:
+ enabled = true;
+ lockscreen = true;
+ break;
+ case ENABLED_EXCEPT_LOCK_SCREEN:
+ enabled = true;
+ lockscreen = false;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_ENABLED, enabled ? 1 : 0);
+ Settings.Secure.putInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, lockscreen ? 1 : 0);
+ }
+
private AccessibilityShortcutController getController() {
AccessibilityShortcutController accessibilityShortcutController =
- new AccessibilityShortcutController(mContext, mHandler);
+ new AccessibilityShortcutController(mContext, mHandler, 0);
accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
return accessibilityShortcutController;
}
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 7282b3e..69589e7 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -19,7 +19,9 @@
import android.os.Handler;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+
import com.android.server.power.BatterySaverPolicy.ServiceType;
+
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -40,6 +42,7 @@
+ "animation_disabled=false,"
+ "soundtrigger_disabled=true,"
+ "firewall_disabled=false,"
+ + "datasaver_disabled=false,"
+ "adjust_brightness_disabled=true,"
+ "adjust_brightness_factor=0.7,"
+ "fullbackup_deferred=true,"
@@ -99,6 +102,18 @@
}
@SmallTest
+ public void testGetBatterySaverPolicy_PolicyDataSaver_DefaultValueCorrect() {
+ mBatterySaverPolicy.updateConstants("");
+ final PowerSaveState batterySaverStateOn =
+ mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.DATA_SAVER, BATTERY_SAVER_ON);
+ assertThat(batterySaverStateOn.batterySaverEnabled).isFalse();
+
+ final PowerSaveState batterySaverStateOff = mBatterySaverPolicy.getBatterySaverPolicy(
+ ServiceType.DATA_SAVER, BATTERY_SAVER_OFF);
+ assertThat(batterySaverStateOff.batterySaverEnabled).isFalse();
+ }
+
+ @SmallTest
public void testGetBatterySaverPolicy_PolicyScreenBrightness_DefaultValueCorrect() {
testServiceDefaultValue(ServiceType.SCREEN_BRIGHTNESS);
@@ -137,7 +152,8 @@
assertThat(networkState.batterySaverEnabled).isTrue();
final PowerSaveState screenState =
- mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS, BATTERY_SAVER_ON);
+ mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS,
+ BATTERY_SAVER_ON);
assertThat(screenState.batterySaverEnabled).isFalse();
assertThat(screenState.brightnessFactor).isWithin(PRECISION).of(BRIGHTNESS_FACTOR);
@@ -149,6 +165,10 @@
ServiceType.KEYVALUE_BACKUP, BATTERY_SAVER_ON);
assertThat(keyValueBackupState.batterySaverEnabled).isFalse();
+ final PowerSaveState dataSaverState = mBatterySaverPolicy.getBatterySaverPolicy(
+ ServiceType.DATA_SAVER, BATTERY_SAVER_ON);
+ assertThat(dataSaverState.batterySaverEnabled).isTrue();
+
final PowerSaveState gpsState =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.GPS, BATTERY_SAVER_ON);
assertThat(gpsState.batterySaverEnabled).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 74557e2..80b2e7d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -361,6 +361,18 @@
}
@Test
+ public void testOverrideConfigurationAncestorNotification() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer grandparent = builder.setLayer(0).build();
+
+ final TestWindowContainer parent = grandparent.addChildWindow();
+ final TestWindowContainer child = parent.addChildWindow();
+ child.onOverrideConfigurationChanged(new Configuration());
+
+ assertTrue(grandparent.mOnDescendantOverrideCalled);
+ }
+
+ @Test
public void testRemoveChild() throws Exception {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
final TestWindowContainer root = builder.setLayer(0).build();
@@ -718,6 +730,7 @@
private Integer mOrientation;
private boolean mOnParentSetCalled;
+ private boolean mOnDescendantOverrideCalled;
/**
* Compares 2 window layers and returns -1 if the first is lesser than the second in terms
@@ -776,6 +789,12 @@
}
@Override
+ void onDescendantOverrideConfigurationChanged() {
+ mOnDescendantOverrideCalled = true;
+ super.onDescendantOverrideConfigurationChanged();
+ }
+
+ @Override
boolean isAnimating() {
return mIsAnimating || super.isAnimating();
}
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 28b6e45..1b1984d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -23,7 +23,6 @@
import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.graphics.Rect;
-import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -34,7 +33,6 @@
import android.view.WindowManager;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.FILL_PARENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -47,9 +45,8 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class WindowFrameTests {
+public class WindowFrameTests extends WindowTestsBase {
- private static WindowManagerService sWm = null;
private WindowToken mWindowToken;
private final IWindow mIWindow = new TestIWindow();
@@ -105,8 +102,7 @@
// Just any non zero value.
sWm.mSystemDecorLayer = 10000;
- mWindowToken = new WindowToken(sWm, new Binder(), 0, false,
- sWm.getDefaultDisplayContentLocked(), false /* ownerCanManageAppTokens */);
+ mWindowToken = new TestAppWindowToken(sWm.getDefaultDisplayContentLocked());
mStubStack = new TaskStack(sWm, 0);
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 6f78245..48799d2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -303,17 +303,19 @@
static class TestAppWindowToken extends AppWindowToken {
TestAppWindowToken(DisplayContent dc) {
- super(sWm, null, false, dc, true /* fillsParent */);
+ super(sWm, null, false, dc, true /* fillsParent */, null /* overrideConfig */,
+ null /* bounds */);
}
TestAppWindowToken(WindowManagerService service, IApplicationToken token,
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller) {
+ boolean alwaysFocusable, AppWindowContainerController controller,
+ Configuration overrideConfig, Rect bounds) {
super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen,
showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges,
- launchTaskBehind, alwaysFocusable, controller);
+ launchTaskBehind, alwaysFocusable, controller, overrideConfig, bounds);
}
int getWindowsCount() {
@@ -428,7 +430,8 @@
true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */,
false /* launchTaskBehind */, false /* alwaysFocusable */,
0 /* targetSdkVersion */, 0 /* rotationAnimationHint */,
- 0 /* inputDispatchingTimeoutNanos */, sWm);
+ 0 /* inputDispatchingTimeoutNanos */, sWm, null /* overrideConfig */,
+ null /* bounds */);
mToken = token;
}
@@ -437,12 +440,13 @@
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller) {
+ boolean alwaysFocusable, AppWindowContainerController controller,
+ Configuration overrideConfig, Rect bounds) {
return new TestAppWindowToken(service, token, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk,
orientation,
rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
- controller);
+ controller, overrideConfig, bounds);
}
AppWindowToken getAppWindowToken() {
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 4da2853..f1cf4414 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -99,6 +99,11 @@
*/
private static final String USB_STATE_PROPERTY = "sys.usb.state";
+ /**
+ * ro.bootmode value when phone boots into usual Android.
+ */
+ private static final String NORMAL_BOOT = "normal";
+
private static final String USB_STATE_MATCH =
"DEVPATH=/devices/virtual/android_usb/android0";
private static final String ACCESSORY_START_MATCH =
@@ -157,7 +162,7 @@
private boolean mMidiEnabled;
private int mMidiCard;
private int mMidiDevice;
- private Map<String, List<Pair<String, String>>> mOemModeMap;
+ private HashMap<String, HashMap<String, Pair<String, String>>> mOemModeMap;
private String[] mAccessoryStrings;
private UsbDebuggingManager mDebuggingManager;
private final UsbAlsaManager mUsbAlsaManager;
@@ -374,16 +379,32 @@
private boolean mAdbNotificationShown;
private int mCurrentUser = UserHandle.USER_NULL;
private boolean mUsbCharging;
+ private String mCurrentOemFunctions;
public UsbHandler(Looper looper) {
super(looper);
try {
// Restore default functions.
- mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
- UsbManager.USB_FUNCTION_NONE);
- mCurrentFunctionsApplied = mCurrentFunctions.equals(
- SystemProperties.get(USB_STATE_PROPERTY));
- mAdbEnabled = UsbManager.containsFunction(getDefaultFunctions(),
+
+ if (isNormalBoot()) {
+ mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
+ UsbManager.USB_FUNCTION_NONE);
+ mCurrentFunctionsApplied = mCurrentFunctions.equals(
+ SystemProperties.get(USB_STATE_PROPERTY));
+ } else {
+ mCurrentFunctions = SystemProperties.get(getPersistProp(true),
+ UsbManager.USB_FUNCTION_NONE);
+ mCurrentFunctionsApplied = SystemProperties.get(USB_CONFIG_PROPERTY,
+ UsbManager.USB_FUNCTION_NONE).equals(
+ SystemProperties.get(USB_STATE_PROPERTY));
+ }
+
+ /**
+ * Use the normal bootmode persistent prop to maintain state of adb across
+ * all boot modes.
+ */
+ mAdbEnabled = UsbManager.containsFunction(
+ SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY),
UsbManager.USB_FUNCTION_ADB);
/**
@@ -577,18 +598,36 @@
Slog.e(TAG, "Unable to set any USB functions!");
}
+ private boolean isNormalBoot() {
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ if (bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown")) {
+ return true;
+ }
+ return false;
+ }
+
private boolean trySetEnabledFunctions(String functions, boolean forceRestart) {
if (functions == null || applyAdbFunction(functions)
.equals(UsbManager.USB_FUNCTION_NONE)) {
functions = getDefaultFunctions();
}
functions = applyAdbFunction(functions);
- functions = applyOemOverrideFunction(functions);
- if (!mCurrentFunctions.equals(functions) || !mCurrentFunctionsApplied
+ String oemFunctions = applyOemOverrideFunction(functions);
+
+ if (!isNormalBoot() && !mCurrentFunctions.equals(functions)) {
+ SystemProperties.set(getPersistProp(true), functions);
+ }
+
+ if ((!functions.equals(oemFunctions) &&
+ (mCurrentOemFunctions == null ||
+ !mCurrentOemFunctions.equals(oemFunctions)))
+ || !mCurrentFunctions.equals(functions)
+ || !mCurrentFunctionsApplied
|| forceRestart) {
Slog.i(TAG, "Setting USB config to " + functions);
mCurrentFunctions = functions;
+ mCurrentOemFunctions = oemFunctions;
mCurrentFunctionsApplied = false;
// Kick the USB stack to close existing connections.
@@ -600,12 +639,12 @@
}
// Set the new USB configuration.
- setUsbConfig(functions);
+ setUsbConfig(oemFunctions);
// Start up dependent services.
updateUsbStateBroadcastIfNeeded(true);
- if (!waitForState(functions)) {
+ if (!waitForState(oemFunctions)) {
Slog.e(TAG, "Failed to switch USB config to " + functions);
return false;
}
@@ -616,6 +655,11 @@
}
private String applyAdbFunction(String functions) {
+ // Do not pass null pointer to the UsbManager.
+ // There isnt a check there.
+ if (functions == null) {
+ functions = "";
+ }
if (mAdbEnabled) {
functions = UsbManager.addFunction(functions, UsbManager.USB_FUNCTION_ADB);
} else {
@@ -1010,7 +1054,7 @@
}
private String getDefaultFunctions() {
- String func = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY,
+ String func = SystemProperties.get(getPersistProp(true),
UsbManager.USB_FUNCTION_NONE);
if (UsbManager.USB_FUNCTION_NONE.equals(func)) {
func = UsbManager.USB_FUNCTION_MTP;
@@ -1021,6 +1065,7 @@
public void dump(IndentingPrintWriter pw) {
pw.println("USB Device State:");
pw.println(" mCurrentFunctions: " + mCurrentFunctions);
+ pw.println(" mCurrentOemFunctions: " + mCurrentOemFunctions);
pw.println(" mCurrentFunctionsApplied: " + mCurrentFunctionsApplied);
pw.println(" mConnected: " + mConnected);
pw.println(" mConfigured: " + mConfigured);
@@ -1082,39 +1127,99 @@
if (configList != null) {
for (String config : configList) {
String[] items = config.split(":");
- if (items.length == 3) {
+ if (items.length == 3 || items.length == 4) {
if (mOemModeMap == null) {
- mOemModeMap = new HashMap<String, List<Pair<String, String>>>();
+ mOemModeMap = new HashMap<String, HashMap<String,
+ Pair<String, String>>>();
}
- List<Pair<String, String>> overrideList = mOemModeMap.get(items[0]);
- if (overrideList == null) {
- overrideList = new LinkedList<Pair<String, String>>();
- mOemModeMap.put(items[0], overrideList);
+ HashMap<String, Pair<String, String>> overrideMap
+ = mOemModeMap.get(items[0]);
+ if (overrideMap == null) {
+ overrideMap = new HashMap<String,
+ Pair<String, String>>();
+ mOemModeMap.put(items[0], overrideMap);
}
- overrideList.add(new Pair<String, String>(items[1], items[2]));
+
+ // Favoring the first combination if duplicate exists
+ if (!overrideMap.containsKey(items[1])) {
+ if (items.length == 3) {
+ overrideMap.put(items[1],
+ new Pair<String, String>(items[2], ""));
+ } else {
+ overrideMap.put(items[1],
+ new Pair<String, String>(items[2], items[3]));
+ }
+ }
}
}
}
}
private String applyOemOverrideFunction(String usbFunctions) {
- if ((usbFunctions == null) || (mOemModeMap == null)) return usbFunctions;
+ if ((usbFunctions == null) || (mOemModeMap == null)) {
+ return usbFunctions;
+ }
String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode);
- List<Pair<String, String>> overrides = mOemModeMap.get(bootMode);
- if (overrides != null) {
- for (Pair<String, String> pair : overrides) {
- if (pair.first.equals(usbFunctions)) {
- Slog.d(TAG, "OEM USB override: " + pair.first + " ==> " + pair.second);
- return pair.second;
+ Map<String, Pair<String, String>> overridesMap =
+ mOemModeMap.get(bootMode);
+ // Check to ensure that the oem is not overriding in the normal
+ // boot mode
+ if (overridesMap != null && !(bootMode.equals(NORMAL_BOOT) ||
+ bootMode.equals("unknown"))) {
+ Pair<String, String> overrideFunctions =
+ overridesMap.get(usbFunctions);
+ if (overrideFunctions != null) {
+ Slog.d(TAG, "OEM USB override: " + usbFunctions
+ + " ==> " + overrideFunctions.first
+ + " persist across reboot "
+ + overrideFunctions.second);
+ if (!overrideFunctions.second.equals("")) {
+ String newFunction;
+ if (mAdbEnabled) {
+ newFunction = UsbManager.addFunction(overrideFunctions.second,
+ UsbManager.USB_FUNCTION_ADB);
+ } else {
+ newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
+ UsbManager.USB_FUNCTION_ADB);
+ }
+ Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: "
+ + UsbDeviceManager.getPersistProp(false));
+ SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+ newFunction);
}
+ return overrideFunctions.first;
+ } else if (mAdbEnabled) {
+ String newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
+ UsbManager.USB_FUNCTION_ADB);
+ SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+ newFunction);
+ } else {
+ SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+ UsbManager.USB_FUNCTION_NONE);
}
}
// return passed in functions as is.
return usbFunctions;
}
+ public static String getPersistProp(boolean functions) {
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ String persistProp = USB_PERSISTENT_CONFIG_PROPERTY;
+ if (!(bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"))) {
+ if (functions == true) {
+ persistProp = "persist.sys.usb." + bootMode + ".func";
+ } else {
+ persistProp = "persist.sys.usb." + bootMode + ".config";
+ }
+ }
+
+ return persistProp;
+ }
+
+
public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
if (mDebuggingManager != null) {
mDebuggingManager.allowUsbDebugging(alwaysAllow, publicKey);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index dd23850..26c9430 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -23,8 +23,8 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.WorkerThread;
import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
import android.app.ActivityThread;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -50,6 +50,7 @@
import com.android.ims.internal.IImsServiceController;
import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.ITelecomService;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IPhoneSubInfo;
@@ -940,7 +941,11 @@
*
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
+ * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns
+ * MEID for CDMA.
*/
+ @Deprecated
public String getDeviceId() {
try {
ITelephony telephony = getITelephony();
@@ -962,7 +967,11 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*
* @param slotIndex of which deviceID is returned
+ *
+ * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns
+ * MEID for CDMA.
*/
+ @Deprecated
public String getDeviceId(int slotIndex) {
// FIXME this assumes phoneId == slotIndex
try {
@@ -978,29 +987,25 @@
}
/**
- * Returns the IMEI. Return null if IMEI is not available.
+ * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
+ * available.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- *
- * @hide
*/
- @SystemApi
public String getImei() {
return getImei(getDefaultSim());
}
/**
- * Returns the IMEI. Return null if IMEI is not available.
+ * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
+ * available.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*
- * @param slotIndex of which deviceID is returned
- *
- * @hide
+ * @param slotIndex of which IMEI is returned
*/
- @SystemApi
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
@@ -1015,6 +1020,37 @@
}
/**
+ * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getMeid() {
+ return getMeid(getDefaultSim());
+ }
+
+ /**
+ * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
+ * @param slotIndex of which MEID is returned
+ */
+ public String getMeid(int slotIndex) {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return null;
+
+ try {
+ return telephony.getMeidForSlot(slotIndex, getOpPackageName());
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ return null;
+ }
+ }
+
+ /**
* Returns the NAI. Return null if NAI is not available.
*
*/
@@ -4056,9 +4092,19 @@
return SubscriptionManager.getPhoneId(SubscriptionManager.getDefaultSubscriptionId());
}
- /** {@hide} */
+ /**
+ * @return default SIM's slot index. If SIM is not inserted, return default SIM slot index.
+ *
+ * {@hide}
+ */
+ @VisibleForTesting
public int getDefaultSim() {
- return SubscriptionManager.getSlotIndex(SubscriptionManager.getDefaultSubscriptionId());
+ int slotIndex = SubscriptionManager.getSlotIndex(
+ SubscriptionManager.getDefaultSubscriptionId());
+ if (slotIndex == SubscriptionManager.SIM_NOT_INSERTED) {
+ slotIndex = SubscriptionManager.DEFAULT_SIM_SLOT_INDEX;
+ }
+ return slotIndex;
}
/**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7613837..cd15c44 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1096,6 +1096,16 @@
String getImeiForSlot(int slotIndex, String callingPackage);
/**
+ * Returns the MEID for the given slot.
+ *
+ * @param slotIndex - device slot.
+ * @param callingPackage The package making the call.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ String getMeidForSlot(int slotIndex, String callingPackage);
+
+ /**
* Returns the device software version.
*
* @param slotIndex - device slot.
diff --git a/tests/testables/src/android/testing/AndroidTestingRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java
index a425f70..816ed03 100644
--- a/tests/testables/src/android/testing/AndroidTestingRunner.java
+++ b/tests/testables/src/android/testing/AndroidTestingRunner.java
@@ -18,7 +18,7 @@
import android.support.test.internal.runner.junit4.statement.RunBefores;
import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
-import android.testing.TestableLooper.LooperFrameworkMethod;
+import android.testing.TestableLooper.LooperStatement;
import android.testing.TestableLooper.RunWithLooper;
import org.junit.After;
@@ -30,7 +30,6 @@
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -50,21 +49,28 @@
@Override
protected Statement methodInvoker(FrameworkMethod method, Object test) {
- method = looperWrap(method, test, method);
- final Statement statement = super.methodInvoker(method, test);
- return shouldRunOnUiThread(method) ? new UiThreadStatement(statement, true) : statement;
+ return shouldRunOnUiThread(method) ? new UiThreadStatement(
+ methodInvokerInt(method, test), true) : methodInvokerInt(method, test);
+ }
+
+ protected Statement methodInvokerInt(FrameworkMethod method, Object test) {
+ RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
+ if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
+ if (annotation != null) {
+ return new LooperStatement(super.methodInvoker(method, test),
+ annotation.setAsMainLooper(), test);
+ }
+ return super.methodInvoker(method, test);
}
protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
- List befores = looperWrap(method, target,
- this.getTestClass().getAnnotatedMethods(Before.class));
+ List befores = this.getTestClass().getAnnotatedMethods(Before.class);
return befores.isEmpty() ? statement : new RunBefores(method, statement,
befores, target);
}
protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
- List afters = looperWrap(method, target,
- this.getTestClass().getAnnotatedMethods(After.class));
+ List afters = this.getTestClass().getAnnotatedMethods(After.class);
return afters.isEmpty() ? statement : new RunAfters(method, statement, afters,
target);
}
@@ -82,30 +88,6 @@
return annotation == null ? 0L : annotation.timeout();
}
- protected List<FrameworkMethod> looperWrap(FrameworkMethod method, Object test,
- List<FrameworkMethod> methods) {
- RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
- if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
- if (annotation != null) {
- methods = new ArrayList<>(methods);
- for (int i = 0; i < methods.size(); i++) {
- methods.set(i, LooperFrameworkMethod.get(methods.get(i),
- annotation.setAsMainLooper(), test));
- }
- }
- return methods;
- }
-
- protected FrameworkMethod looperWrap(FrameworkMethod method, Object test,
- FrameworkMethod base) {
- RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
- if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
- if (annotation != null) {
- return LooperFrameworkMethod.get(base, annotation.setAsMainLooper(), test);
- }
- return base;
- }
-
public boolean shouldRunOnUiThread(FrameworkMethod method) {
if (mKlass.getAnnotation(UiThreadTest.class) != null) {
return true;
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 62490bc..8a33cf9 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -15,21 +15,20 @@
package android.testing;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
-import android.os.TestLooperManager;
-import android.support.test.InstrumentationRegistry;
import android.util.ArrayMap;
-import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.util.Map;
/**
@@ -39,35 +38,65 @@
*/
public class TestableLooper {
+ private final Method mNext;
+ private final Method mRecycleUnchecked;
+
private Looper mLooper;
private MessageQueue mQueue;
private boolean mMain;
private Object mOriginalMain;
private MessageHandler mMessageHandler;
+ private int mParsedCount;
private Handler mHandler;
private Message mEmptyMessage;
- private TestLooperManager mQueueWrapper;
- public TestableLooper(Looper l) throws Exception {
- this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
+ public TestableLooper() throws Exception {
+ this(true);
}
- private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
- mQueueWrapper = wrapper;
- setupQueue(l);
- }
-
- private TestableLooper(Looper looper, boolean b) throws Exception {
- setupQueue(looper);
+ public TestableLooper(boolean setMyLooper) throws Exception {
+ setupQueue(setMyLooper);
+ mNext = mQueue.getClass().getDeclaredMethod("next");
+ mNext.setAccessible(true);
+ mRecycleUnchecked = Message.class.getDeclaredMethod("recycleUnchecked");
+ mRecycleUnchecked.setAccessible(true);
}
public Looper getLooper() {
return mLooper;
}
- private void setupQueue(Looper l) throws Exception {
- mLooper = l;
+ private void clearLooper() throws NoSuchFieldException, IllegalAccessException {
+ Field field = Looper.class.getDeclaredField("sThreadLocal");
+ field.setAccessible(true);
+ ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null);
+ sThreadLocal.set(null);
+ }
+
+ private boolean setForCurrentThread() throws NoSuchFieldException, IllegalAccessException {
+ if (Looper.myLooper() != mLooper) {
+ Field field = Looper.class.getDeclaredField("sThreadLocal");
+ field.setAccessible(true);
+ ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null);
+ sThreadLocal.set(mLooper);
+ return true;
+ }
+ return false;
+ }
+
+ private void setupQueue(boolean setMyLooper) throws Exception {
+ if (setMyLooper) {
+ clearLooper();
+ Looper.prepare();
+ mLooper = Looper.myLooper();
+ } else {
+ Constructor<Looper> constructor = Looper.class.getDeclaredConstructor(
+ boolean.class);
+ constructor.setAccessible(true);
+ mLooper = constructor.newInstance(true);
+ }
+
mQueue = mLooper.getQueue();
mHandler = new Handler(mLooper);
}
@@ -92,7 +121,9 @@
* tests.
*/
public void destroy() throws NoSuchFieldException, IllegalAccessException {
- mQueueWrapper.release();
+ if (Looper.myLooper() == mLooper) {
+ clearLooper();
+ }
if (mMain && mOriginalMain != null) {
Field field = mLooper.getClass().getDeclaredField("sMainLooper");
field.setAccessible(true);
@@ -133,26 +164,26 @@
private boolean parseMessageInt() {
try {
- Message result = mQueueWrapper.next();
+ Message result = (Message) mNext.invoke(mQueue);
if (result != null) {
// This is a break message.
if (result == mEmptyMessage) {
- mQueueWrapper.recycle(result);
+ mRecycleUnchecked.invoke(result);
return false;
}
if (mMessageHandler != null) {
if (mMessageHandler.onMessageHandled(result)) {
result.getTarget().dispatchMessage(result);
- mQueueWrapper.recycle(result);
+ mRecycleUnchecked.invoke(result);
} else {
- mQueueWrapper.recycle(result);
+ mRecycleUnchecked.invoke(result);
// Message handler indicated it doesn't want us to continue.
return false;
}
} else {
result.getTarget().dispatchMessage(result);
- mQueueWrapper.recycle(result);
+ mRecycleUnchecked.invoke(result);
}
} else {
// No messages, don't continue parsing
@@ -168,14 +199,10 @@
* Runs an executable with myLooper set and processes all messages added.
*/
public void runWithLooper(RunnableWithException runnable) throws Exception {
- new Handler(getLooper()).post(() -> {
- try {
- runnable.run();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ boolean set = setForCurrentThread();
+ runnable.run();
processAllMessages();
+ if (set) clearLooper();
}
public interface RunnableWithException {
@@ -194,131 +221,33 @@
return sLoopers.get(test);
}
- public static class LooperFrameworkMethod extends FrameworkMethod {
- private HandlerThread mHandlerThread;
+ public static class LooperStatement extends Statement {
+ private final boolean mSetAsMain;
+ private final Statement mBase;
+ private final TestableLooper mLooper;
- private final TestableLooper mTestableLooper;
- private final Looper mLooper;
- private final Handler mHandler;
-
- public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
- super(base.getMethod());
+ public LooperStatement(Statement base, boolean setAsMain, Object test) {
+ mBase = base;
try {
- mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
- mTestableLooper = new TestableLooper(mLooper, false);
+ mLooper = new TestableLooper(false);
+ sLoopers.put(test, mLooper);
+ mSetAsMain = setAsMain;
} catch (Exception e) {
throw new RuntimeException(e);
}
- sLoopers.put(test, mTestableLooper);
- mHandler = new Handler(mLooper);
- }
-
- public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
- super(base.getMethod());
- mLooper = other.mLooper;
- mTestableLooper = other;
- mHandler = new Handler(mLooper);
- }
-
- public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
- if (sLoopers.containsKey(test)) {
- return new LooperFrameworkMethod(sLoopers.get(test), base);
- }
- return new LooperFrameworkMethod(base, setAsMain, test);
}
@Override
- public Object invokeExplosively(Object target, Object... params) throws Throwable {
- if (Looper.myLooper() == mLooper) {
- // Already on the right thread from another statement, just execute then.
- return super.invokeExplosively(target, params);
+ public void evaluate() throws Throwable {
+ mLooper.setForCurrentThread();
+ if (mSetAsMain) {
+ mLooper.setAsMainLooper();
}
- boolean set = mTestableLooper.mQueueWrapper == null;
- if (set) {
- mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation()
- .acquireLooperManager(mLooper);
- }
+
try {
- Object[] ret = new Object[1];
- // Run the execution on the looper thread.
- Runnable execute = () -> {
- try {
- ret[0] = super.invokeExplosively(target, params);
- } catch (Throwable throwable) {
- throw new LooperException(throwable);
- }
- };
- mHandler.post(execute);
- // Try to wait for the message to be queued.
- for (int i = 0; i < 10; i++) {
- if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
- Thread.sleep(1);
- }
- }
- if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
- throw new RuntimeException("Message didn't queue...");
- }
- Message m = mTestableLooper.mQueueWrapper.next();
- // Parse all other messages until we get to ours.
- while (m.getTarget() != mHandler) {
- try {
- mTestableLooper.mQueueWrapper.execute(m);
- } catch (LooperException e) {
- throw e.getSource();
- } finally {
- mTestableLooper.mQueueWrapper.recycle(m);
- }
- m = mTestableLooper.mQueueWrapper.next();
- }
- // Dispatch our message.
- try {
- mTestableLooper.mQueueWrapper.execute(m);
- } catch (LooperException e) {
- throw e.getSource();
- } catch (RuntimeException re) {
- // If the TestLooperManager has to post, it will wrap what it throws in a
- // RuntimeException, make sure we grab the actual source.
- if (re.getCause() instanceof LooperException) {
- throw ((LooperException) re.getCause()).getSource();
- } else {
- throw re.getCause();
- }
- } finally {
- mTestableLooper.mQueueWrapper.recycle(m);
- }
- return ret[0];
+ mBase.evaluate();
} finally {
- if (set) {
- mTestableLooper.mQueueWrapper.release();
- mTestableLooper.mQueueWrapper = null;
- }
- }
- }
-
- private Looper createLooper() {
- // TODO: Find way to share these.
- mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
- mHandlerThread.start();
- return mHandlerThread.getLooper();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- if (mHandlerThread != null) {
- mHandlerThread.quit();
- }
- }
-
- private static class LooperException extends RuntimeException {
- private final Throwable mSource;
-
- public LooperException(Throwable t) {
- mSource = t;
- }
-
- public Throwable getSource() {
- return mSource;
+ mLooper.destroy();
}
}
}
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 12f1d0a..18e5fff 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -24,16 +24,17 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.testing.TestableLooper.MessageHandler;
import android.testing.TestableLooper.RunWithLooper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class TestableLooperTest {
@@ -45,6 +46,11 @@
mTestableLooper = TestableLooper.get(this);
}
+ @After
+ public void tearDown() throws Exception {
+ mTestableLooper.destroy();
+ }
+
@Test
public void testMessageExecuted() throws Exception {
Handler h = new Handler();
@@ -127,23 +133,39 @@
@Test
public void testMainLooper() throws Exception {
assertNotEquals(Looper.myLooper(), Looper.getMainLooper());
+
+ Looper originalMain = Looper.getMainLooper();
+ mTestableLooper.setAsMainLooper();
+ assertEquals(Looper.myLooper(), Looper.getMainLooper());
+ Runnable r = mock(Runnable.class);
+
+ new Handler(Looper.getMainLooper()).post(r);
+ mTestableLooper.processAllMessages();
+
+ verify(r).run();
+ mTestableLooper.destroy();
+
+ assertEquals(originalMain, Looper.getMainLooper());
+ }
+
+ @Test
+ public void testNotMyLooper() throws Exception {
+ TestableLooper looper = new TestableLooper(false);
+
+ assertEquals(Looper.myLooper(), mTestableLooper.getLooper());
+ assertNotEquals(Looper.myLooper(), looper.getLooper());
+
Runnable r = mock(Runnable.class);
Runnable r2 = mock(Runnable.class);
- TestableLooper testableLooper = new TestableLooper(Looper.getMainLooper());
+ new Handler().post(r);
+ new Handler(looper.getLooper()).post(r2);
- try {
- testableLooper.setMessageHandler(m -> {
- if (m.getCallback() == r) return true;
- return false;
- });
- new Handler(Looper.getMainLooper()).post(r);
- testableLooper.processAllMessages();
+ looper.processAllMessages();
+ verify(r2).run();
+ verify(r, never()).run();
- verify(r).run();
- verify(r2, never()).run();
- } finally {
- testableLooper.destroy();
- }
+ mTestableLooper.processAllMessages();
+ verify(r).run();
}
@Test
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 57036aa..ef3797c 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -87,6 +87,7 @@
"flatten/Archive.cpp",
"flatten/TableFlattener.cpp",
"flatten/XmlFlattener.cpp",
+ "io/BigBufferStreams.cpp",
"io/File.cpp",
"io/FileSystem.cpp",
"io/Io.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 1d04b35..b855f8f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -20,6 +20,7 @@
#include "ValueVisitor.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
+#include "io/BigBufferInputStream.h"
namespace aapt {
@@ -27,8 +28,7 @@
const android::StringPiece& path) {
Source source(path);
std::string error;
- std::unique_ptr<io::ZipFileCollection> apk =
- io::ZipFileCollection::Create(path, &error);
+ std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
if (!apk) {
context->GetDiagnostics()->Error(DiagMessage(source) << error);
return {};
@@ -36,21 +36,18 @@
io::IFile* file = apk->FindFile("resources.arsc");
if (!file) {
- context->GetDiagnostics()->Error(DiagMessage(source)
- << "no resources.arsc found");
+ context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found");
return {};
}
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
- context->GetDiagnostics()->Error(DiagMessage(source)
- << "could not open resources.arsc");
+ context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc");
return {};
}
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(context, table.get(), source, data->data(),
- data->size());
+ BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
if (!parser.Parse()) {
return {};
}
@@ -92,9 +89,9 @@
continue;
}
- // The resource table needs to be reserialized since it might have changed.
+ // The resource table needs to be re-serialized since it might have changed.
if (path == "resources.arsc") {
- BigBuffer buffer = BigBuffer(1024);
+ BigBuffer buffer(4096);
// TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
// with sparse entries) b/35389232.
TableFlattener flattener(options, &buffer);
@@ -102,8 +99,8 @@
return false;
}
- if (!writer->StartEntry(path, ArchiveEntry::kAlign) || !writer->WriteEntry(buffer) ||
- !writer->FinishEntry()) {
+ io::BigBufferInputStream input_stream(&buffer);
+ if (!writer->WriteFile(path, ArchiveEntry::kAlign, &input_stream)) {
context->GetDiagnostics()->Error(DiagMessage()
<< "Error when writing file '" << path << "' in APK.");
return false;
@@ -113,14 +110,12 @@
std::unique_ptr<io::IData> data = file->OpenAsData();
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
- if (!writer->StartEntry(path, compression_flags) ||
- !writer->WriteEntry(data->data(), data->size()) || !writer->FinishEntry()) {
+ if (!writer->WriteFile(path, compression_flags, data.get())) {
context->GetDiagnostics()->Error(DiagMessage()
<< "Error when writing file '" << path << "' in APK.");
return false;
}
}
-
return true;
}
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 456f686..5e9b81a29 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -25,7 +25,7 @@
static const char* sMajorVersion = "2";
// Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "10";
+static const char* sMinorVersion = "11";
int PrintVersion() {
std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 8027f42..1fe30f0 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -37,6 +37,7 @@
#include "compile/XmlIdCollector.h"
#include "flatten/Archive.h"
#include "flatten/XmlFlattener.h"
+#include "io/BigBufferOutputStream.h"
#include "proto/ProtoSerialize.h"
#include "util/Files.h"
#include "util/Maybe.h"
@@ -46,7 +47,6 @@
using android::StringPiece;
using google::protobuf::io::CopyingOutputStreamAdaptor;
-using google::protobuf::io::ZeroCopyOutputStream;
namespace aapt {
@@ -142,10 +142,10 @@
IAaptContext* context, const CompileOptions& options,
std::vector<ResourcePathData>* out_path_data) {
const std::string& root_dir = options.res_dir.value();
- std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()),
- closedir);
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
if (!d) {
- context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+ context->GetDiagnostics()->Error(DiagMessage()
+ << android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -161,10 +161,10 @@
continue;
}
- std::unique_ptr<DIR, decltype(closedir)*> subdir(
- opendir(prefix_path.data()), closedir);
+ std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
if (!subdir) {
- context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+ context->GetDiagnostics()->Error(DiagMessage()
+ << android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -177,8 +177,7 @@
file::AppendPath(&full_path, leaf_entry->d_name);
std::string err_str;
- Maybe<ResourcePathData> path_data =
- ExtractResourcePathData(full_path, &err_str);
+ Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
if (!path_data) {
context->GetDiagnostics()->Error(DiagMessage() << err_str);
return false;
@@ -199,7 +198,7 @@
std::ifstream fin(path_data.source.path, std::ifstream::binary);
if (!fin) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
- << strerror(errno));
+ << android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -249,8 +248,7 @@
// Create the file/zip entry.
if (!writer->StartEntry(output_path, 0)) {
- context->GetDiagnostics()->Error(DiagMessage(output_path)
- << "failed to open");
+ context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open");
return false;
}
@@ -258,21 +256,18 @@
// writer->FinishEntry().
{
// Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream
- // interface.
+ // ZeroCopyOutputStream interface.
CopyingOutputStreamAdaptor copying_adaptor(writer);
std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table);
if (!pb_table->SerializeToZeroCopyStream(©ing_adaptor)) {
- context->GetDiagnostics()->Error(DiagMessage(output_path)
- << "failed to write");
+ context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
return false;
}
}
if (!writer->FinishEntry()) {
- context->GetDiagnostics()->Error(DiagMessage(output_path)
- << "failed to finish entry");
+ context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
return false;
}
return true;
@@ -293,16 +288,14 @@
// writer->FinishEntry().
{
// Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream
- // interface.
+ // ZeroCopyOutputStream interface.
CopyingOutputStreamAdaptor copying_adaptor(writer);
CompiledFileOutputStream output_stream(©ing_adaptor);
// Number of CompiledFiles.
output_stream.WriteLittleEndian32(1);
- std::unique_ptr<pb::CompiledFile> compiled_file =
- SerializeCompiledFileToPb(file);
+ std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
output_stream.WriteCompiledFile(compiled_file.get());
output_stream.WriteData(&buffer);
@@ -371,14 +364,12 @@
return false;
}
- std::unique_ptr<pb::CompiledFile> pb_compiled_file =
- SerializeCompiledFileToPb(xmlres->file);
+ std::unique_ptr<pb::CompiledFile> pb_compiled_file = SerializeCompiledFileToPb(xmlres->file);
out->WriteCompiledFile(pb_compiled_file.get());
out->WriteData(&buffer);
if (out->HadError()) {
- context->GetDiagnostics()->Error(DiagMessage(output_path)
- << "failed to write data");
+ context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data");
return false;
}
return true;
@@ -388,8 +379,7 @@
const ResourcePathData& path_data,
IArchiveWriter* writer, const std::string& output_path) {
if (context->IsVerbose()) {
- context->GetDiagnostics()->Note(DiagMessage(path_data.source)
- << "compiling XML");
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
}
std::unique_ptr<xml::XmlResource> xmlres;
@@ -397,7 +387,7 @@
std::ifstream fin(path_data.source.path, std::ifstream::binary);
if (!fin) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
- << strerror(errno));
+ << android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -470,31 +460,6 @@
return true;
}
-class BigBufferOutputStream : public io::OutputStream {
- public:
- explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
-
- bool Next(void** data, int* len) override {
- size_t count;
- *data = buffer_->NextBlock(&count);
- *len = static_cast<int>(count);
- return true;
- }
-
- void BackUp(int count) override { buffer_->BackUp(count); }
-
- google::protobuf::int64 ByteCount() const override {
- return buffer_->size();
- }
-
- bool HadError() const override { return false; }
-
- private:
- BigBuffer* buffer_;
-
- DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
-};
-
static bool CompilePng(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data,
IArchiveWriter* writer, const std::string& output_path) {
@@ -520,7 +485,7 @@
}
BigBuffer crunched_png_buffer(4096);
- BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
+ io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
// Ensure that we only keep the chunks we care about if we end up
// using the original PNG instead of the crunched one.
@@ -533,8 +498,7 @@
std::unique_ptr<NinePatch> nine_patch;
if (path_data.extension == "9.png") {
std::string err;
- nine_patch = NinePatch::Create(image->rows.get(), image->width,
- image->height, &err);
+ nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
if (!nine_patch) {
context->GetDiagnostics()->Error(DiagMessage() << err);
return false;
@@ -547,8 +511,7 @@
// width - 2.
image->width -= 2;
image->height -= 2;
- memmove(image->rows.get(), image->rows.get() + 1,
- image->height * sizeof(uint8_t**));
+ memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
for (int32_t h = 0; h < image->height; h++) {
memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
}
@@ -560,8 +523,7 @@
}
// Write the crunched PNG.
- if (!WritePng(context, image.get(), nine_patch.get(),
- &crunched_png_buffer_out, {})) {
+ if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
return false;
}
@@ -574,24 +536,21 @@
// The re-encoded PNG is larger than the original, and there is
// no mandatory transformation. Use the original.
if (context->IsVerbose()) {
- context->GetDiagnostics()->Note(
- DiagMessage(path_data.source)
- << "original PNG is smaller than crunched PNG"
- << ", using original");
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "original PNG is smaller than crunched PNG"
+ << ", using original");
}
- PngChunkFilter png_chunk_filter_again(content);
+ png_chunk_filter.Rewind();
BigBuffer filtered_png_buffer(4096);
- BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
- io::Copy(&filtered_png_buffer_out, &png_chunk_filter_again);
+ io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
+ io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
buffer.AppendBuffer(std::move(filtered_png_buffer));
}
if (context->IsVerbose()) {
- // For debugging only, use the legacy PNG cruncher and compare the
- // resulting file sizes.
- // This will help catch exotic cases where the new code may generate
- // larger PNGs.
+ // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
+ // This will help catch exotic cases where the new code may generate larger PNGs.
std::stringstream legacy_stream(content);
BigBuffer legacy_buffer(4096);
Png png(context->GetDiagnostics());
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 5e15c88..6d6147d 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -33,7 +33,6 @@
namespace aapt {
constexpr bool kDebug = false;
-constexpr size_t kPngSignatureSize = 8u;
struct PngInfo {
~PngInfo() {
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index a820051..e4255e7 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -31,6 +31,9 @@
namespace aapt {
+// Size in bytes of the PNG signature.
+constexpr size_t kPngSignatureSize = 8u;
+
struct PngOptions {
int grayscale_tolerance = 0;
};
@@ -46,9 +49,9 @@
const PngOptions& options);
private:
- IDiagnostics* mDiag;
-
DISALLOW_COPY_AND_ASSIGN(Png);
+
+ IDiagnostics* mDiag;
};
/**
@@ -57,26 +60,26 @@
class PngChunkFilter : public io::InputStream {
public:
explicit PngChunkFilter(const android::StringPiece& data);
+ virtual ~PngChunkFilter() = default;
- bool Next(const void** buffer, int* len) override;
- void BackUp(int count) override;
- bool Skip(int count) override;
+ bool Next(const void** buffer, size_t* len) override;
+ void BackUp(size_t count) override;
- google::protobuf::int64 ByteCount() const override {
- return static_cast<google::protobuf::int64>(window_start_);
- }
+ bool CanRewind() const override { return true; }
+ bool Rewind() override;
+ size_t ByteCount() const override { return window_start_; }
bool HadError() const override { return error_; }
private:
- bool ConsumeWindow(const void** buffer, int* len);
+ DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
+
+ bool ConsumeWindow(const void** buffer, size_t* len);
android::StringPiece data_;
size_t window_start_ = 0;
size_t window_end_ = 0;
bool error_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
};
/**
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index edec123..f9043b5 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -71,16 +71,16 @@
PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
if (util::StartsWith(data_, kPngSignature)) {
window_start_ = 0;
- window_end_ = strlen(kPngSignature);
+ window_end_ = kPngSignatureSize;
} else {
error_ = true;
}
}
-bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) {
+bool PngChunkFilter::ConsumeWindow(const void** buffer, size_t* len) {
if (window_start_ != window_end_) {
// We have bytes to give from our window.
- const int bytes_read = (int)(window_end_ - window_start_);
+ const size_t bytes_read = window_end_ - window_start_;
*buffer = data_.data() + window_start_;
*len = bytes_read;
window_start_ = window_end_;
@@ -89,7 +89,7 @@
return false;
}
-bool PngChunkFilter::Next(const void** buffer, int* len) {
+bool PngChunkFilter::Next(const void** buffer, size_t* len) {
if (error_) {
return false;
}
@@ -113,16 +113,14 @@
// Verify the chunk length.
const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
- if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) >
- data_.size()) {
+ if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) > data_.size()) {
// Overflow.
error_ = true;
return false;
}
// Do we strip this chunk?
- const uint32_t chunk_type =
- Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+ const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
if (IsPngChunkWhitelisted(chunk_type)) {
// Advance the window to include this chunk.
window_end_ += kMinChunkHeaderSize + chunk_len;
@@ -146,31 +144,19 @@
return false;
}
-void PngChunkFilter::BackUp(int count) {
+void PngChunkFilter::BackUp(size_t count) {
if (error_) {
return;
}
window_start_ -= count;
}
-bool PngChunkFilter::Skip(int count) {
+bool PngChunkFilter::Rewind() {
if (error_) {
return false;
}
-
- const void* buffer;
- int len;
- while (count > 0) {
- if (!Next(&buffer, &len)) {
- return false;
- }
- if (len > count) {
- BackUp(len - count);
- count = 0;
- } else {
- count -= len;
- }
- }
+ window_start_ = 0;
+ window_end_ = kPngSignatureSize;
return true;
}
diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
index 3b46d8b4..ae98afc 100644
--- a/tools/aapt2/compile/PngCrunch.cpp
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -29,12 +29,7 @@
namespace aapt {
-// Size in bytes of the PNG signature.
-constexpr size_t kPngSignatureSize = 8u;
-
-/**
- * Custom deleter that destroys libpng read and info structs.
- */
+// Custom deleter that destroys libpng read and info structs.
class PngReadStructDeleter {
public:
PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
@@ -51,9 +46,7 @@
DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
};
-/**
- * Custom deleter that destroys libpng write and info structs.
- */
+// Custom deleter that destroys libpng write and info structs.
class PngWriteStructDeleter {
public:
PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
@@ -82,12 +75,11 @@
diag->Error(DiagMessage() << error_msg);
}
-static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer,
- png_size_t len) {
+static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
const void* in_buffer;
- int in_len;
+ size_t in_len;
if (!in->Next(&in_buffer, &in_len)) {
if (in->HadError()) {
std::string err = in->GetError();
@@ -96,19 +88,18 @@
return;
}
- const size_t bytes_read = std::min(static_cast<size_t>(in_len), len);
+ const size_t bytes_read = std::min(in_len, len);
memcpy(buffer, in_buffer, bytes_read);
- if (bytes_read != static_cast<size_t>(in_len)) {
- in->BackUp(in_len - static_cast<int>(bytes_read));
+ if (bytes_read != in_len) {
+ in->BackUp(in_len - bytes_read);
}
}
-static void WriteDataToStream(png_structp png_ptr, png_bytep buffer,
- png_size_t len) {
+static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
void* out_buffer;
- int out_len;
+ size_t out_len;
while (len > 0) {
if (!out->Next(&out_buffer, &out_len)) {
if (out->HadError()) {
@@ -118,7 +109,7 @@
return;
}
- const size_t bytes_written = std::min(static_cast<size_t>(out_len), len);
+ const size_t bytes_written = std::min(out_len, len);
memcpy(out_buffer, buffer, bytes_written);
// Advance the input buffer.
@@ -126,7 +117,7 @@
len -= bytes_written;
// Advance the output buffer.
- out_len -= static_cast<int>(bytes_written);
+ out_len -= bytes_written;
}
// If the entire output buffer wasn't used, backup.
@@ -139,41 +130,35 @@
// Read the first 8 bytes of the file looking for the PNG signature.
// Bail early if it does not match.
const png_byte* signature;
- int buffer_size;
+ size_t buffer_size;
if (!in->Next((const void**)&signature, &buffer_size)) {
- context->GetDiagnostics()->Error(
- DiagMessage() << android::base::SystemErrorCodeToString(errno));
+ context->GetDiagnostics()->Error(DiagMessage()
+ << android::base::SystemErrorCodeToString(errno));
return {};
}
- if (static_cast<size_t>(buffer_size) < kPngSignatureSize ||
- png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "file signature does not match PNG signature");
+ if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "file signature does not match PNG signature");
return {};
}
// Start at the beginning of the first chunk.
- in->BackUp(buffer_size - static_cast<int>(kPngSignatureSize));
+ in->BackUp(buffer_size - kPngSignatureSize);
- // Create and initialize the png_struct with the default error and warning
- // handlers.
- // The header version is also passed in to ensure that this was built against
- // the same
+ // Create and initialize the png_struct with the default error and warning handlers.
+ // The header version is also passed in to ensure that this was built against the same
// version of libpng.
- png_structp read_ptr =
- png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (read_ptr == nullptr) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "failed to create libpng read png_struct");
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_struct");
return {};
}
// Create and initialize the memory for image header and data.
png_infop info_ptr = png_create_info_struct(read_ptr);
if (info_ptr == nullptr) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "failed to create libpng read png_info");
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_info");
png_destroy_read_struct(&read_ptr, nullptr, nullptr);
return {};
}
@@ -189,8 +174,7 @@
}
// Handle warnings ourselves via IDiagnostics.
- png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError,
- LogWarning);
+ png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning);
// Set up the read functions which read from our custom data sources.
png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
@@ -203,8 +187,7 @@
// Extract image meta-data from the various chunk headers.
uint32_t width, height;
- int bit_depth, color_type, interlace_method, compression_method,
- filter_method;
+ int bit_depth, color_type, interlace_method, compression_method, filter_method;
png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
&interlace_method, &compression_method, &filter_method);
@@ -247,11 +230,9 @@
// 9-patch uses int32_t to index images, so we cap the image dimensions to
// something
// that can always be represented by 9-patch.
- if (width > std::numeric_limits<int32_t>::max() ||
- height > std::numeric_limits<int32_t>::max()) {
- context->GetDiagnostics()->Error(DiagMessage()
- << "PNG image dimensions are too large: "
- << width << "x" << height);
+ if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "PNG image dimensions are too large: " << width << "x" << height);
return {};
}
@@ -263,8 +244,7 @@
CHECK(row_bytes == 4 * width); // RGBA
// Allocate one large block to hold the image.
- output_image->data =
- std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
+ output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
// Create an array of rows that index into the data block.
output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
@@ -281,19 +261,13 @@
return output_image;
}
-/**
- * Experimentally chosen constant to be added to the overhead of using color
- * type
- * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
- * chunk.
- * Without this, many small PNGs encoded with palettes are larger after
- * compression than
- * the same PNGs encoded as RGBA.
- */
+// Experimentally chosen constant to be added to the overhead of using color type
+// PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
+// Without this, many small PNGs encoded with palettes are larger after compression than
+// the same PNGs encoded as RGBA.
constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
-// Pick a color type by which to encode the image, based on which color type
-// will take
+// Pick a color type by which to encode the image, based on which color type will take
// the least amount of disk space.
//
// 9-patch images traditionally have not been encoded with palettes.
@@ -372,20 +346,17 @@
return PNG_COLOR_TYPE_RGBA;
}
-// Assigns indices to the color and alpha palettes, encodes them, and then
-// invokes
+// Assigns indices to the color and alpha palettes, encodes them, and then invokes
// png_set_PLTE/png_set_tRNS.
// This must be done before writing image data.
-// Image data must be transformed to use the indices assigned within the
-// palette.
+// Image data must be transformed to use the indices assigned within the palette.
static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
std::unordered_map<uint32_t, int>* color_palette,
std::unordered_set<uint32_t>* alpha_palette) {
CHECK(color_palette->size() <= 256);
CHECK(alpha_palette->size() <= 256);
- // Populate the PNG palette struct and assign indices to the color
- // palette.
+ // Populate the PNG palette struct and assign indices to the color palette.
// Colors in the alpha palette should have smaller indices.
// This will ensure that we can truncate the alpha palette if it is
@@ -403,13 +374,11 @@
}
// Create the PNG color palette struct.
- auto color_palette_bytes =
- std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
+ auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
std::unique_ptr<png_byte[]> alpha_palette_bytes;
if (!alpha_palette->empty()) {
- alpha_palette_bytes =
- std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
+ alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
}
for (const auto& entry : *color_palette) {
@@ -433,23 +402,20 @@
// The bytes get copied here, so it is safe to release color_palette_bytes at
// the end of function
// scope.
- png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(),
- color_palette->size());
+ png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size());
if (alpha_palette_bytes) {
- png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(),
- alpha_palette->size(), nullptr);
+ png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(),
+ nullptr);
}
}
// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
-// before
-// writing image data.
+// before writing image data.
static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
const NinePatch* nine_patch) {
// The order of the chunks is important.
- // 9-patch code in older platforms expects the 9-patch chunk to
- // be last.
+ // 9-patch code in older platforms expects the 9-patch chunk to be last.
png_unknown_chunk unknown_chunks[3];
memset(unknown_chunks, 0, sizeof(unknown_chunks));
@@ -475,8 +441,7 @@
index++;
}
- std::unique_ptr<uint8_t[]> serialized_nine_patch =
- nine_patch->SerializeBase(&chunk_len);
+ std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len);
strcpy((char*)unknown_chunks[index].name, "npTc");
unknown_chunks[index].size = chunk_len;
unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
@@ -497,22 +462,18 @@
const PngOptions& options) {
// Create and initialize the write png_struct with the default error and
// warning handlers.
- // The header version is also passed in to ensure that this was built against
- // the same
+ // The header version is also passed in to ensure that this was built against the same
// version of libpng.
- png_structp write_ptr =
- png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (write_ptr == nullptr) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "failed to create libpng write png_struct");
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_struct");
return false;
}
// Allocate memory to store image header data.
png_infop write_info_ptr = png_create_info_struct(write_ptr);
if (write_info_ptr == nullptr) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "failed to create libpng write png_info");
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_info");
png_destroy_write_struct(&write_ptr, nullptr);
return false;
}
@@ -527,8 +488,7 @@
}
// Handle warnings with our IDiagnostics.
- png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError,
- LogWarning);
+ png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning);
// Set up the write functions which write to our custom data sources.
png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
@@ -599,8 +559,7 @@
context->GetDiagnostics()->Note(msg);
}
- const bool convertible_to_grayscale =
- max_gray_deviation <= options.grayscale_tolerance;
+ const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
const int new_color_type = PickColorType(
image->width, image->height, grayscale, convertible_to_grayscale,
@@ -715,15 +674,12 @@
}
png_write_row(write_ptr, out_row.get());
}
- } else if (new_color_type == PNG_COLOR_TYPE_RGB ||
- new_color_type == PNG_COLOR_TYPE_RGBA) {
+ } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) {
const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
if (needs_to_zero_rgb_channels_of_transparent_pixels) {
// The source RGBA data can't be used as-is, because we need to zero out
- // the RGB
- // values of transparent pixels.
- auto out_row =
- std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+ // the RGB values of transparent pixels.
+ auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
for (int32_t y = 0; y < image->height; y++) {
png_const_bytep in_row = image->rows[y];
@@ -747,8 +703,7 @@
}
} else {
// The source image can be used as-is, just tell libpng whether or not to
- // ignore
- // the alpha channel.
+ // ignore the alpha channel.
if (new_color_type == PNG_COLOR_TYPE_RGB) {
// Delete the extraneous alpha values that we appended to our buffer
// when reading the original values.
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 5c96a4d..826f91b 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -21,6 +21,7 @@
#include <string>
#include <vector>
+#include "android-base/errors.h"
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
#include "ziparchive/zip_writer.h"
@@ -37,14 +38,14 @@
public:
DirectoryWriter() = default;
- bool Open(IDiagnostics* diag, const StringPiece& out_dir) {
+ bool Open(const StringPiece& out_dir) {
dir_ = out_dir.to_string();
file::FileType type = file::GetFileType(dir_);
if (type == file::FileType::kNonexistant) {
- diag->Error(DiagMessage() << "directory " << dir_ << " does not exist");
+ error_ = "directory does not exist";
return false;
} else if (type != file::FileType::kDirectory) {
- diag->Error(DiagMessage() << dir_ << " is not a directory");
+ error_ = "not a directory";
return false;
}
return true;
@@ -61,27 +62,19 @@
file_ = {fopen(full_path.data(), "wb"), fclose};
if (!file_) {
+ error_ = android::base::SystemErrorCodeToString(errno);
return false;
}
return true;
}
- bool WriteEntry(const BigBuffer& buffer) override {
+ bool Write(const void* data, int len) override {
if (!file_) {
return false;
}
- for (const BigBuffer::Block& b : buffer) {
- if (fwrite(b.buffer.get(), 1, b.size, file_.get()) != b.size) {
- file_.reset(nullptr);
- return false;
- }
- }
- return true;
- }
-
- bool WriteEntry(const void* data, size_t len) override {
- if (fwrite(data, 1, len, file_.get()) != len) {
+ if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) {
+ error_ = android::base::SystemErrorCodeToString(errno);
file_.reset(nullptr);
return false;
}
@@ -96,22 +89,41 @@
return true;
}
+ bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+ if (!StartEntry(path, flags)) {
+ return false;
+ }
+
+ const void* data = nullptr;
+ size_t len = 0;
+ while (in->Next(&data, &len)) {
+ if (!Write(data, static_cast<int>(len))) {
+ return false;
+ }
+ }
+ return !in->HadError();
+ }
+
+ bool HadError() const override { return !error_.empty(); }
+
+ std::string GetError() const override { return error_; }
+
private:
DISALLOW_COPY_AND_ASSIGN(DirectoryWriter);
std::string dir_;
std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
+ std::string error_;
};
class ZipFileWriter : public IArchiveWriter {
public:
ZipFileWriter() = default;
- bool Open(IDiagnostics* diag, const StringPiece& path) {
+ bool Open(const StringPiece& path) {
file_ = {fopen(path.data(), "w+b"), fclose};
if (!file_) {
- diag->Error(DiagMessage() << "failed to Open " << path << ": "
- << strerror(errno));
+ error_ = android::base::SystemErrorCodeToString(errno);
return false;
}
writer_ = util::make_unique<ZipWriter>(file_.get());
@@ -134,37 +146,83 @@
int32_t result = writer_->StartEntry(path.data(), zip_flags);
if (result != 0) {
+ error_ = ZipWriter::ErrorCodeString(result);
return false;
}
return true;
}
- bool WriteEntry(const void* data, size_t len) override {
+ bool Write(const void* data, int len) override {
int32_t result = writer_->WriteBytes(data, len);
if (result != 0) {
+ error_ = ZipWriter::ErrorCodeString(result);
return false;
}
return true;
}
- bool WriteEntry(const BigBuffer& buffer) override {
- for (const BigBuffer::Block& b : buffer) {
- int32_t result = writer_->WriteBytes(b.buffer.get(), b.size);
- if (result != 0) {
- return false;
- }
- }
- return true;
- }
-
bool FinishEntry() override {
int32_t result = writer_->FinishEntry();
if (result != 0) {
+ error_ = ZipWriter::ErrorCodeString(result);
return false;
}
return true;
}
+ bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+ while (true) {
+ if (!StartEntry(path, flags)) {
+ return false;
+ }
+
+ const void* data = nullptr;
+ size_t len = 0;
+ while (in->Next(&data, &len)) {
+ if (!Write(data, static_cast<int>(len))) {
+ return false;
+ }
+ }
+
+ if (in->HadError()) {
+ return false;
+ }
+
+ if (!FinishEntry()) {
+ return false;
+ }
+
+ // Check to see if the file was compressed enough. This is preserving behavior of AAPT.
+ if ((flags & ArchiveEntry::kCompress) != 0 && in->CanRewind()) {
+ ZipWriter::FileEntry last_entry;
+ int32_t result = writer_->GetLastEntry(&last_entry);
+ CHECK(result == 0);
+ if (last_entry.compressed_size + (last_entry.compressed_size / 10) >
+ last_entry.uncompressed_size) {
+ // The file was not compressed enough, rewind and store it uncompressed.
+ if (!in->Rewind()) {
+ // Well we tried, may as well keep what we had.
+ return true;
+ }
+
+ int32_t result = writer_->DiscardLastEntry();
+ if (result != 0) {
+ error_ = ZipWriter::ErrorCodeString(result);
+ return false;
+ }
+ flags &= ~ArchiveEntry::kCompress;
+
+ continue;
+ }
+ }
+ return true;
+ }
+ }
+
+ bool HadError() const override { return !error_.empty(); }
+
+ std::string GetError() const override { return error_; }
+
virtual ~ZipFileWriter() {
if (writer_) {
writer_->Finish();
@@ -176,24 +234,26 @@
std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
std::unique_ptr<ZipWriter> writer_;
+ std::string error_;
};
} // namespace
-std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(
- IDiagnostics* diag, const StringPiece& path) {
- std::unique_ptr<DirectoryWriter> writer =
- util::make_unique<DirectoryWriter>();
- if (!writer->Open(diag, path)) {
+std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+ std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+ if (!writer->Open(path)) {
+ diag->Error(DiagMessage(path) << writer->GetError());
return {};
}
return std::move(writer);
}
-std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(
- IDiagnostics* diag, const StringPiece& path) {
+std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
- if (!writer->Open(diag, path)) {
+ if (!writer->Open(path)) {
+ diag->Error(DiagMessage(path) << writer->GetError());
return {};
}
return std::move(writer);
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index f0681bd..4ee4ce7 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -26,6 +26,7 @@
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "Diagnostics.h"
+#include "io/Io.h"
#include "util/BigBuffer.h"
#include "util/Files.h"
@@ -42,19 +43,31 @@
size_t uncompressed_size;
};
-class IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
+class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream {
public:
virtual ~IArchiveWriter() = default;
+ virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
+
+ // Starts a new entry and allows caller to write bytes to it sequentially.
+ // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
+ // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
- virtual bool WriteEntry(const BigBuffer& buffer) = 0;
- virtual bool WriteEntry(const void* data, size_t len) = 0;
+
+ // Called to finish writing an entry previously started by StartEntry.
+ // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
virtual bool FinishEntry() = 0;
- // CopyingOutputStream implementations.
- bool Write(const void* buffer, int size) override {
- return WriteEntry(buffer, size);
- }
+ // CopyingOutputStream implementation that allows sequential writes to this archive. Only
+ // valid between calls to StartEntry and FinishEntry.
+ virtual bool Write(const void* buffer, int size) = 0;
+
+ // Returns true if there was an error writing to the archive.
+ // The resulting error message can be retrieved from GetError().
+ virtual bool HadError() const = 0;
+
+ // Returns the error message if HadError() returns true.
+ virtual std::string GetError() const = 0;
};
std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferInputStream.h
new file mode 100644
index 0000000..92612c7
--- /dev/null
+++ b/tools/aapt2/io/BigBufferInputStream.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_IO_BIGBUFFERINPUTSTREAM_H
+#define AAPT_IO_BIGBUFFERINPUTSTREAM_H
+
+#include "io/Io.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+namespace io {
+
+class BigBufferInputStream : public InputStream {
+ public:
+ inline explicit BigBufferInputStream(const BigBuffer* buffer)
+ : buffer_(buffer), iter_(buffer->begin()) {}
+ virtual ~BigBufferInputStream() = default;
+
+ bool Next(const void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ bool CanRewind() const override;
+
+ bool Rewind() override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
+
+ const BigBuffer* buffer_;
+ BigBuffer::const_iterator iter_;
+ size_t offset_ = 0;
+ size_t bytes_read_ = 0;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_BIGBUFFERINPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/io/BigBufferOutputStream.h
new file mode 100644
index 0000000..95113bc
--- /dev/null
+++ b/tools/aapt2/io/BigBufferOutputStream.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
+#define AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
+
+#include "io/Io.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+namespace io {
+
+class BigBufferOutputStream : public OutputStream {
+ public:
+ inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
+ virtual ~BigBufferOutputStream() = default;
+
+ bool Next(void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+
+ BigBuffer* buffer_;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferStreams.cpp b/tools/aapt2/io/BigBufferStreams.cpp
new file mode 100644
index 0000000..eb99033
--- /dev/null
+++ b/tools/aapt2/io/BigBufferStreams.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "io/BigBufferInputStream.h"
+#include "io/BigBufferOutputStream.h"
+
+namespace aapt {
+namespace io {
+
+//
+// BigBufferInputStream
+//
+
+bool BigBufferInputStream::Next(const void** data, size_t* size) {
+ if (iter_ == buffer_->end()) {
+ return false;
+ }
+
+ if (offset_ == iter_->size) {
+ ++iter_;
+ if (iter_ == buffer_->end()) {
+ return false;
+ }
+ offset_ = 0;
+ }
+
+ *data = iter_->buffer.get() + offset_;
+ *size = iter_->size - offset_;
+ bytes_read_ += iter_->size - offset_;
+ offset_ = iter_->size;
+ return true;
+}
+
+void BigBufferInputStream::BackUp(size_t count) {
+ if (count > offset_) {
+ bytes_read_ -= offset_;
+ offset_ = 0;
+ } else {
+ offset_ -= count;
+ bytes_read_ -= count;
+ }
+}
+
+bool BigBufferInputStream::CanRewind() const { return true; }
+
+bool BigBufferInputStream::Rewind() {
+ iter_ = buffer_->begin();
+ offset_ = 0;
+ bytes_read_ = 0;
+ return true;
+}
+
+size_t BigBufferInputStream::ByteCount() const { return bytes_read_; }
+
+bool BigBufferInputStream::HadError() const { return false; }
+
+//
+// BigBufferOutputStream
+//
+
+bool BigBufferOutputStream::Next(void** data, size_t* size) {
+ *data = buffer_->NextBlock(size);
+ return true;
+}
+
+void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); }
+
+size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); }
+
+bool BigBufferOutputStream::HadError() const { return false; }
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index fdc044d..09dc7ea 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -22,14 +22,13 @@
#include "android-base/macros.h"
#include "utils/FileMap.h"
+#include "io/Io.h"
+
namespace aapt {
namespace io {
-/**
- * Interface for a block of contiguous memory. An instance of this interface
- * owns the data.
- */
-class IData {
+// Interface for a block of contiguous memory. An instance of this interface owns the data.
+class IData : public InputStream {
public:
virtual ~IData() = default;
@@ -40,7 +39,8 @@
class DataSegment : public IData {
public:
explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len)
- : data_(std::move(data)), offset_(offset), len_(len) {}
+ : data_(std::move(data)), offset_(offset), len_(len), next_read_(offset) {}
+ virtual ~DataSegment() = default;
const void* data() const override {
return static_cast<const uint8_t*>(data_->data()) + offset_;
@@ -48,63 +48,163 @@
size_t size() const override { return len_; }
+ bool Next(const void** data, size_t* size) override {
+ if (next_read_ == offset_ + len_) {
+ return false;
+ }
+ *data = static_cast<const uint8_t*>(data_->data()) + next_read_;
+ *size = len_ - (next_read_ - offset_);
+ next_read_ = offset_ + len_;
+ return true;
+ }
+
+ void BackUp(size_t count) override {
+ if (count > next_read_ - offset_) {
+ next_read_ = offset_;
+ } else {
+ next_read_ -= count;
+ }
+ }
+
+ bool CanRewind() const override { return true; }
+
+ bool Rewind() override {
+ next_read_ = offset_;
+ return true;
+ }
+
+ size_t ByteCount() const override { return next_read_ - offset_; }
+
+ bool HadError() const override { return false; }
+
private:
DISALLOW_COPY_AND_ASSIGN(DataSegment);
std::unique_ptr<IData> data_;
size_t offset_;
size_t len_;
+ size_t next_read_;
};
-/**
- * Implementation of IData that exposes a memory mapped file. The mmapped file
- * is owned by this
- * object.
- */
+// Implementation of IData that exposes a memory mapped file.
+// The mmapped file is owned by this object.
class MmappedData : public IData {
public:
- explicit MmappedData(android::FileMap&& map)
- : map_(std::forward<android::FileMap>(map)) {}
+ explicit MmappedData(android::FileMap&& map) : map_(std::forward<android::FileMap>(map)) {}
+ virtual ~MmappedData() = default;
const void* data() const override { return map_.getDataPtr(); }
size_t size() const override { return map_.getDataLength(); }
+ bool Next(const void** data, size_t* size) override {
+ if (next_read_ == map_.getDataLength()) {
+ return false;
+ }
+ *data = reinterpret_cast<const uint8_t*>(map_.getDataPtr()) + next_read_;
+ *size = map_.getDataLength() - next_read_;
+ next_read_ = map_.getDataLength();
+ return true;
+ }
+
+ void BackUp(size_t count) override {
+ if (count > next_read_) {
+ next_read_ = 0;
+ } else {
+ next_read_ -= count;
+ }
+ }
+
+ bool CanRewind() const override { return true; }
+
+ bool Rewind() override {
+ next_read_ = 0;
+ return true;
+ }
+
+ size_t ByteCount() const override { return next_read_; }
+
+ bool HadError() const override { return false; }
+
private:
+ DISALLOW_COPY_AND_ASSIGN(MmappedData);
+
android::FileMap map_;
+ size_t next_read_ = 0;
};
-/**
- * Implementation of IData that exposes a block of memory that was malloc'ed
- * (new'ed). The
- * memory is owned by this object.
- */
+// Implementation of IData that exposes a block of memory that was malloc'ed (new'ed).
+// The memory is owned by this object.
class MallocData : public IData {
public:
MallocData(std::unique_ptr<const uint8_t[]> data, size_t size)
: data_(std::move(data)), size_(size) {}
+ virtual ~MallocData() = default;
const void* data() const override { return data_.get(); }
size_t size() const override { return size_; }
+ bool Next(const void** data, size_t* size) override {
+ if (next_read_ == size_) {
+ return false;
+ }
+ *data = data_.get() + next_read_;
+ *size = size_ - next_read_;
+ next_read_ = size_;
+ return true;
+ }
+
+ void BackUp(size_t count) override {
+ if (count > next_read_) {
+ next_read_ = 0;
+ } else {
+ next_read_ -= count;
+ }
+ }
+
+ bool CanRewind() const override { return true; }
+
+ bool Rewind() override {
+ next_read_ = 0;
+ return true;
+ }
+
+ size_t ByteCount() const override { return next_read_; }
+
+ bool HadError() const override { return false; }
+
private:
+ DISALLOW_COPY_AND_ASSIGN(MallocData);
+
std::unique_ptr<const uint8_t[]> data_;
size_t size_;
+ size_t next_read_ = 0;
};
-/**
- * When mmap fails because the file has length 0, we use the EmptyData to
- * simulate data of length 0.
- */
+// When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0.
class EmptyData : public IData {
public:
+ virtual ~EmptyData() = default;
+
const void* data() const override {
static const uint8_t d = 0;
return &d;
}
size_t size() const override { return 0u; }
+
+ bool Next(const void** /*data*/, size_t* /*size*/) override { return false; }
+
+ void BackUp(size_t /*count*/) override {}
+
+ bool CanRewind() const override { return true; }
+
+ bool Rewind() override { return true; }
+
+ size_t ByteCount() const override { return 0u; }
+
+ bool HadError() const override { return false; }
};
} // namespace io
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 1ef9743..7ef6d88 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -30,40 +30,27 @@
namespace aapt {
namespace io {
-/**
- * Interface for a file, which could be a real file on the file system, or a
- * file inside
- * a ZIP archive.
- */
+// Interface for a file, which could be a real file on the file system, or a
+// file inside a ZIP archive.
class IFile {
public:
virtual ~IFile() = default;
- /**
- * Open the file and return it as a block of contiguous memory. How this
- * occurs is
- * implementation dependent. For example, if this is a file on the file
- * system, it may
- * simply mmap the contents. If this file represents a compressed file in a
- * ZIP archive,
- * it may need to inflate it to memory, incurring a copy.
- *
- * Returns nullptr on failure.
- */
+ // Open the file and return it as a block of contiguous memory. How this
+ // occurs is implementation dependent. For example, if this is a file on the file
+ // system, it may simply mmap the contents. If this file represents a compressed file in a
+ // ZIP archive, it may need to inflate it to memory, incurring a copy.
+ // Returns nullptr on failure.
virtual std::unique_ptr<IData> OpenAsData() = 0;
- /**
- * Returns the source of this file. This is for presentation to the user and
- * may not be a
- * valid file system path (for example, it may contain a '@' sign to separate
- * the files within
- * a ZIP archive from the path to the containing ZIP archive.
- */
+ // Returns the source of this file. This is for presentation to the user and
+ // may not be a valid file system path (for example, it may contain a '@' sign to separate
+ // the files within a ZIP archive from the path to the containing ZIP archive.
virtual const Source& GetSource() const = 0;
IFile* CreateFileSegment(size_t offset, size_t len);
- /** Returns whether the file was compressed before it was stored in memory. */
+ // Returns whether the file was compressed before it was stored in memory.
virtual bool WasCompressed() {
return false;
}
@@ -77,10 +64,7 @@
std::list<std::unique_ptr<IFile>> segments_;
};
-/**
- * An IFile that wraps an underlying IFile but limits it to a subsection of that
- * file.
- */
+// An IFile that wraps an underlying IFile but limits it to a subsection of that file.
class FileSegment : public IFile {
public:
explicit FileSegment(IFile* file, size_t offset, size_t len)
@@ -106,11 +90,8 @@
virtual IFile* Next() = 0;
};
-/**
- * Interface for a collection of files, all of which share a common source. That
- * source may
- * simply be the filesystem, or a ZIP archive.
- */
+// Interface for a collection of files, all of which share a common source. That source may
+// simply be the filesystem, or a ZIP archive.
class IFileCollection {
public:
virtual ~IFileCollection() = default;
diff --git a/tools/aapt2/io/Io.cpp b/tools/aapt2/io/Io.cpp
index cab4b65..f5c5737 100644
--- a/tools/aapt2/io/Io.cpp
+++ b/tools/aapt2/io/Io.cpp
@@ -16,7 +16,6 @@
#include "io/Io.h"
-#include <algorithm>
#include <cstring>
namespace aapt {
@@ -24,15 +23,15 @@
bool Copy(OutputStream* out, InputStream* in) {
const void* in_buffer;
- int in_len;
+ size_t in_len;
while (in->Next(&in_buffer, &in_len)) {
void* out_buffer;
- int out_len;
+ size_t out_len;
if (!out->Next(&out_buffer, &out_len)) {
return !out->HadError();
}
- const int bytes_to_copy = std::min(in_len, out_len);
+ const size_t bytes_to_copy = in_len < out_len ? in_len : out_len;
memcpy(out_buffer, in_buffer, bytes_to_copy);
out->BackUp(out_len - bytes_to_copy);
in->BackUp(in_len - bytes_to_copy);
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
index 33cdc7b..2a34d4d 100644
--- a/tools/aapt2/io/Io.h
+++ b/tools/aapt2/io/Io.h
@@ -19,42 +19,76 @@
#include <string>
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
namespace aapt {
namespace io {
-/**
- * InputStream interface that inherits from protobuf's ZeroCopyInputStream,
- * but adds error handling methods to better report issues.
- *
- * The code style here matches the protobuf style.
- */
-class InputStream : public ::google::protobuf::io::ZeroCopyInputStream {
+// InputStream interface that mimics protobuf's ZeroCopyInputStream,
+// with added error handling methods to better report issues.
+class InputStream {
public:
+ virtual ~InputStream() = default;
+
+ // Returns a chunk of data for reading. data and size must not be nullptr.
+ // Returns true so long as there is more data to read, returns false if an error occurred
+ // or no data remains. If an error occurred, check HadError().
+ // The stream owns the buffer returned from this method and the buffer is invalidated
+ // anytime another mutable method is called.
+ virtual bool Next(const void** data, size_t* size) = 0;
+
+ // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+ // from Next().
+ // Useful when the last block returned from Next() wasn't fully read.
+ virtual void BackUp(size_t count) = 0;
+
+ // Returns true if this InputStream can rewind. If so, Rewind() can be called.
+ virtual bool CanRewind() const { return false; };
+
+ // Rewinds the stream to the beginning so it can be read again.
+ // Returns true if the rewind succeeded.
+ // This does nothing if CanRewind() returns false.
+ virtual bool Rewind() { return false; }
+
+ // Returns the number of bytes that have been read from the stream.
+ virtual size_t ByteCount() const = 0;
+
+ // Returns an error message if HadError() returned true.
virtual std::string GetError() const { return {}; }
+ // Returns true if an error occurred. Errors are permanent.
virtual bool HadError() const = 0;
};
-/**
- * OutputStream interface that inherits from protobuf's ZeroCopyOutputStream,
- * but adds error handling methods to better report issues.
- *
- * The code style here matches the protobuf style.
- */
-class OutputStream : public ::google::protobuf::io::ZeroCopyOutputStream {
+// OutputStream interface that mimics protobuf's ZeroCopyOutputStream,
+// with added error handling methods to better report issues.
+class OutputStream {
public:
+ virtual ~OutputStream() = default;
+
+ // Returns a buffer to which data can be written to. The data written to this buffer will
+ // eventually be written to the stream. Call BackUp() if the data written doesn't occupy the
+ // entire buffer.
+ // Return false if there was an error.
+ // The stream owns the buffer returned from this method and the buffer is invalidated
+ // anytime another mutable method is called.
+ virtual bool Next(void** data, size_t* size) = 0;
+
+ // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+ // from Next().
+ // Useful for when the last block returned from Next() wasn't fully written to.
+ virtual void BackUp(size_t count) = 0;
+
+ // Returns the number of bytes that have been written to the stream.
+ virtual size_t ByteCount() const = 0;
+
+ // Returns an error message if HadError() returned true.
virtual std::string GetError() const { return {}; }
+ // Returns true if an error occurred. Errors are permanent.
virtual bool HadError() const = 0;
};
-/**
- * Copies the data from in to out. Returns true if there was no error.
- * If there was an error, check the individual streams' HadError/GetError
- * methods.
- */
+// Copies the data from in to out. Returns false if there was an error.
+// If there was an error, check the individual streams' HadError/GetError methods.
bool Copy(OutputStream* out, InputStream* in);
} // namespace io
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 1b4d5bb..7f71589 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -38,6 +38,7 @@
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
+#include "io/BigBufferInputStream.h"
#include "io/FileSystem.h"
#include "io/ZipArchive.h"
#include "java/JavaClassGenerator.h"
@@ -168,34 +169,57 @@
int min_sdk_version_ = 0;
};
+static bool CopyInputStreamToArchive(io::InputStream* in, const std::string& out_path,
+ uint32_t compression_flags, IArchiveWriter* writer,
+ IAaptContext* context) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive");
+ }
+
+ if (!writer->WriteFile(out_path, compression_flags, in)) {
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path
+ << " to archive: " << writer->GetError());
+ return false;
+ }
+ return true;
+}
+
static bool CopyFileToArchive(io::IFile* file, const std::string& out_path,
uint32_t compression_flags,
IArchiveWriter* writer, IAaptContext* context) {
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
- context->GetDiagnostics()->Error(DiagMessage(file->GetSource())
- << "failed to open file");
+ context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file");
return false;
}
+ return CopyInputStreamToArchive(data.get(), out_path, compression_flags, writer, context);
+}
- const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
- const size_t buffer_size = data->size();
-
+static bool CopyProtoToArchive(::google::protobuf::MessageLite* proto_msg,
+ const std::string& out_path, uint32_t compression_flags,
+ IArchiveWriter* writer, IAaptContext* context) {
if (context->IsVerbose()) {
- context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path
- << " to archive");
+ context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive");
}
if (writer->StartEntry(out_path, compression_flags)) {
- if (writer->WriteEntry(buffer, buffer_size)) {
- if (writer->FinishEntry()) {
- return true;
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
+ ::google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+ if (!proto_msg->SerializeToZeroCopyStream(&adaptor)) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "failed to write " << out_path << " to archive");
+ return false;
}
}
- }
- context->GetDiagnostics()->Error(DiagMessage() << "failed to write file "
- << out_path);
+ if (writer->FinishEntry()) {
+ return true;
+ }
+ }
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path
+ << " to archive: " << writer->GetError());
return false;
}
@@ -221,16 +245,9 @@
context->GetDiagnostics()->Note(msg);
}
- if (writer->StartEntry(path, ArchiveEntry::kCompress)) {
- if (writer->WriteEntry(buffer)) {
- if (writer->FinishEntry()) {
- return true;
- }
- }
- }
- context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << path
- << " to archive");
- return false;
+ io::BigBufferInputStream input_stream(&buffer);
+ return CopyInputStreamToArchive(&input_stream, path.to_string(), ArchiveEntry::kCompress, writer,
+ context);
}
static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source,
@@ -243,8 +260,7 @@
return {};
}
- std::unique_ptr<ResourceTable> table =
- DeserializeTableFromPb(pb_table, source, diag);
+ std::unique_ptr<ResourceTable> table = DeserializeTableFromPb(pb_table, source, diag);
if (!table) {
return {};
}
@@ -898,49 +914,18 @@
BigBuffer buffer(1024);
TableFlattener flattener(options_.table_flattener_options, &buffer);
if (!flattener.Consume(context_, table)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
return false;
}
- if (writer->StartEntry("resources.arsc", ArchiveEntry::kAlign)) {
- if (writer->WriteEntry(buffer)) {
- if (writer->FinishEntry()) {
- return true;
- }
- }
- }
-
- context_->GetDiagnostics()->Error(
- DiagMessage() << "failed to write resources.arsc to archive");
- return false;
+ io::BigBufferInputStream input_stream(&buffer);
+ return CopyInputStreamToArchive(&input_stream, "resources.arsc", ArchiveEntry::kAlign, writer,
+ context_);
}
bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
- // Create the file/zip entry.
- if (!writer->StartEntry("resources.arsc.flat", 0)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to open");
- return false;
- }
-
- // Make sure CopyingOutputStreamAdaptor is deleted before we call
- // writer->FinishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream interface.
- CopyingOutputStreamAdaptor adaptor(writer);
-
- std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
- if (!pb_table->SerializeToZeroCopyStream(&adaptor)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to write");
- return false;
- }
- }
-
- if (!writer->FinishEntry()) {
- context_->GetDiagnostics()->Error(DiagMessage()
- << "failed to finish entry");
- return false;
- }
- return true;
+ std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
+ return CopyProtoToArchive(pb_table.get(), "resources.arsc.flat", 0, writer, context_);
}
bool WriteJavaFile(ResourceTable* table,
@@ -971,8 +956,7 @@
JavaClassGenerator generator(context_, table, java_options);
if (!generator.Generate(package_name_to_generate, out_package, &fout)) {
- context_->GetDiagnostics()->Error(DiagMessage(out_path)
- << generator.getError());
+ context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.getError());
return false;
}
@@ -1484,7 +1468,6 @@
if (options_.package_type == PackageType::kStaticLib) {
if (!FlattenTableToPb(table, writer)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc.flat");
return false;
}
} else {
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 1c9a75d..9899f80 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,5 +1,12 @@
# Android Asset Packaging Tool 2.0 (AAPT2) release notes
+## Version 2.11
+### `aapt2 link ...`
+- Adds the ability to specify assets directories with the -A parameter. Assets work just like
+ assets in the original AAPT. It is not recommended to package assets with aapt2, however,
+ since the resulting APK is post-processed by other tools anyways. Assets do not get processed
+ by AAPT2, just copied, so incremental building gets slower if they are included early on.
+
## Version 2.10
### `aapt2 link ...`
- Add ability to specify package ID to compile with for regular apps (not shared or static libs).
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 3b88290..2e14974 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -38,6 +38,7 @@
import android.content.res.BridgeAssetManager;
import android.graphics.Bitmap;
import android.graphics.FontFamily_Delegate;
+import android.graphics.Typeface;
import android.graphics.Typeface_Delegate;
import android.icu.util.ULocale;
import android.os.Looper;
@@ -402,6 +403,7 @@
// dispose of the default typeface.
Typeface_Delegate.resetDefaults();
+ Typeface.sDynamicTypefaceCache.evictAll();
return true;
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
index 67b42a7..00dddee 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -311,6 +311,7 @@
sFrameworkRepo = null;
sProjectResources = null;
sLogger = null;
+ sBridge.dispose();
sBridge = null;
TestUtils.gc();
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index b0aa3c2..cb0bc6d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -334,7 +334,8 @@
private final static String[] PROMOTED_FIELDS = new String[] {
"android.graphics.drawable.VectorDrawable#mVectorState",
"android.view.Choreographer#mLastFrameTimeNanos",
- "android.graphics.FontFamily#mBuilderPtr"
+ "android.graphics.FontFamily#mBuilderPtr",
+ "android.graphics.Typeface#sDynamicTypefaceCache"
};
/**