diff options
160 files changed, 6513 insertions, 2763 deletions
diff --git a/Android.mk b/Android.mk index e324a75801ac..749242d78bd2 100644 --- a/Android.mk +++ b/Android.mk @@ -326,6 +326,7 @@ LOCAL_SRC_FILES += \ core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \ core/java/android/view/autofill/IAutoFillManager.aidl \ core/java/android/view/autofill/IAutoFillManagerClient.aidl \ + core/java/android/view/autofill/IAutofillWindowPresenter.aidl \ core/java/android/view/IApplicationToken.aidl \ core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \ core/java/android/view/IDockedStackListener.aidl \ diff --git a/api/current.txt b/api/current.txt index b400b34170de..8f969c1efb02 100644 --- a/api/current.txt +++ b/api/current.txt @@ -879,6 +879,7 @@ package android { 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 @@ package android.hardware { 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 diff --git a/api/system-current.txt b/api/system-current.txt index 844b897e4507..48b878ec699f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -992,6 +992,7 @@ package android { 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 @@ package android.hardware { 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 diff --git a/api/test-current.txt b/api/test-current.txt index a73ac18ac0c7..4d8d7f2d5f84 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -879,6 +879,7 @@ package android { 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 @@ package android.hardware { 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 diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java index ffc0f875c79c..db17b28b8182 100644 --- a/cmds/bu/src/com/android/commands/bu/Backup.java +++ b/cmds/bu/src/com/android/commands/bu/Backup.java @@ -53,15 +53,15 @@ public final class Backup { String arg = nextArg(); if (arg.equals("backup")) { - doFullBackup(OsConstants.STDOUT_FILENO); + doBackup(OsConstants.STDOUT_FILENO); } else if (arg.equals("restore")) { - doFullRestore(OsConstants.STDIN_FILENO); + doRestore(OsConstants.STDIN_FILENO); } else { Log.e(TAG, "Invalid operation '" + arg + "'"); } } - private void doFullBackup(int socketFd) { + private void doBackup(int socketFd) { ArrayList<String> packages = new ArrayList<String>(); boolean saveApks = false; boolean saveObbs = false; @@ -70,6 +70,7 @@ public final class Backup { boolean doWidgets = false; boolean allIncludesSystem = true; boolean doCompress = true; + boolean doKeyValue = false; String arg; while ((arg = nextArg()) != null) { @@ -100,6 +101,8 @@ public final class Backup { doCompress = true; } else if ("-nocompress".equals(arg)) { doCompress = false; + } else if ("-includekeyvalue".equals(arg)) { + doKeyValue = true; } else { Log.w(TAG, "Unknown backup flag " + arg); continue; @@ -123,8 +126,8 @@ public final class Backup { try { fd = ParcelFileDescriptor.adoptFd(socketFd); String[] packArray = new String[packages.size()]; - mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets, - doEverything, allIncludesSystem, doCompress, packages.toArray(packArray)); + mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything, + allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray)); } catch (RemoteException e) { Log.e(TAG, "Unable to invoke backup manager for backup"); } finally { @@ -136,12 +139,12 @@ public final class Backup { } } - private void doFullRestore(int socketFd) { + private void doRestore(int socketFd) { // No arguments to restore ParcelFileDescriptor fd = null; try { fd = ParcelFileDescriptor.adoptFd(socketFd); - mBackupManager.fullRestore(fd); + mBackupManager.adbRestore(fd); } catch (RemoteException e) { Log.e(TAG, "Unable to invoke backup manager for restore"); } finally { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4fd3f39029a7..fa1de030e729 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -17,9 +17,12 @@ package android.app; import android.metrics.LogMaker; +import android.graphics.Rect; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillPopupWindow; import android.view.autofill.AutofillValue; +import android.view.autofill.IAutofillWindowPresenter; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; @@ -768,7 +771,6 @@ public class Activity extends ContextThemeWrapper /*package*/ Configuration mCurrentConfig; private SearchManager mSearchManager; private MenuInflater mMenuInflater; - private final MetricsLogger mMetricsLogger = new MetricsLogger(); static final class NonConfigurationInstances { Object activity; @@ -850,6 +852,8 @@ public class Activity extends ContextThemeWrapper private boolean mAutoFillResetNeeded; + private AutofillPopupWindow mAutofillPopupWindow; + private static native String getDlWarning(); /** Return the intent that started this activity. */ @@ -7190,60 +7194,7 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - public void autofill(List<AutofillId> ids, List<AutofillValue> values) { - final View root = getWindow().getDecorView(); - final int itemCount = ids.size(); - int numApplied = 0; - ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; - - for (int i = 0; i < itemCount; i++) { - final AutofillId id = ids.get(i); - final AutofillValue value = values.get(i); - final int viewId = id.getViewId(); - final View view = root.findViewByAccessibilityIdTraversal(viewId); - if (view == null) { - Log.w(TAG, "autofill(): no View with id " + viewId); - continue; - } - if (id.isVirtual()) { - final int parentId = id.getViewId(); - if (virtualValues == null) { - // Most likely there will be just one view with virtual children. - virtualValues = new ArrayMap<>(1); - } - SparseArray<AutofillValue> valuesByParent = virtualValues.get(view); - if (valuesByParent == null) { - // We don't know the size yet, but usually it will be just a few fields... - valuesByParent = new SparseArray<>(5); - virtualValues.put(view, valuesByParent); - } - valuesByParent.put(id.getVirtualChildId(), value); - } else { - if (view.autofill(value)) { - numApplied++; - } - } - } - - if (virtualValues != null) { - for (int i = 0; i < virtualValues.size(); i++) { - final View parent = virtualValues.keyAt(i); - final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i); - if (parent.autofill(childrenValues)) { - numApplied += childrenValues.size(); - } - } - } - - final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED); - log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount); - log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied); - mMetricsLogger.write(log); - } - - /** @hide */ - @Override - public void authenticate(IntentSender intent, Intent fillInIntent) { + final public void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent) { try { startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX, 0, fillInIntent, 0, 0, null); @@ -7254,10 +7205,47 @@ public class Activity extends ContextThemeWrapper /** @hide */ @Override - public void resetableStateAvailable() { + final public void autofillCallbackResetableStateAvailable() { mAutoFillResetNeeded = true; } + /** @hide */ + @Override + final public boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, + int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) { + final Rect actualAnchorBounds = new Rect(); + anchor.getBoundsOnScreen(actualAnchorBounds); + + final int offsetX = (anchorBounds != null) + ? anchorBounds.left - actualAnchorBounds.left : 0; + int offsetY = (anchorBounds != null) + ? anchorBounds.top - actualAnchorBounds.top : 0; + + final boolean wasShowing; + + if (mAutofillPopupWindow == null) { + wasShowing = false; + mAutofillPopupWindow = new AutofillPopupWindow(presenter); + } else { + wasShowing = mAutofillPopupWindow.isShowing(); + } + mAutofillPopupWindow.update(anchor, offsetX, offsetY, width, height, anchorBounds, + actualAnchorBounds); + + return !wasShowing && mAutofillPopupWindow.isShowing(); + } + + /** @hide */ + @Override + final public boolean autofillCallbackRequestHideFillUi() { + if (mAutofillPopupWindow == null) { + return false; + } + mAutofillPopupWindow.dismiss(); + mAutofillPopupWindow = null; + return true; + } + /** * If set to true, this indicates to the system that it should never take a * screenshot of the activity to be used as a representation while it is not in a started state. diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index b9c888c79eb6..4c080c9e6a95 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -367,6 +367,7 @@ class ContextImpl extends Context { // STOPSHIP: fix buggy apps if (SystemProperties.getBoolean("fw.ignore_buggy", false)) return false; if ("com.google.android.tts".equals(getApplicationInfo().packageName)) return true; + if ("com.breel.geswallpapers".equals(getApplicationInfo().packageName)) return true; return false; } diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 0d859a11e39f..d710d8bb6221 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -552,6 +552,7 @@ final class FragmentManagerState implements Parcelable { int[] mAdded; BackStackState[] mBackStack; int mPrimaryNavActiveIndex = -1; + int mNextFragmentIndex; public FragmentManagerState() { } @@ -561,6 +562,7 @@ final class FragmentManagerState implements Parcelable { mAdded = in.createIntArray(); mBackStack = in.createTypedArray(BackStackState.CREATOR); mPrimaryNavActiveIndex = in.readInt(); + mNextFragmentIndex = in.readInt(); } public int describeContents() { @@ -572,6 +574,7 @@ final class FragmentManagerState implements Parcelable { dest.writeIntArray(mAdded); dest.writeTypedArray(mBackStack, flags); dest.writeInt(mPrimaryNavActiveIndex); + dest.writeInt(mNextFragmentIndex); } public static final Parcelable.Creator<FragmentManagerState> CREATOR @@ -638,10 +641,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } doPendingDeferredStart(); + burpActive(); return executePop; } @@ -882,10 +886,6 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } doPendingDeferredStart(); + burpActive(); } /** @@ -1983,6 +1972,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } doPendingDeferredStart(); + burpActive(); return didSomething; } @@ -2248,7 +2238,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate fms.mActive = active; fms.mAdded = added; fms.mBackStack = backStack; + fms.mNextFragmentIndex = mNextFragmentIndex; if (mPrimaryNav != null) { fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex; } @@ -2722,10 +2713,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate 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 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } 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 7f0b6fb61f05..cae54b6c0611 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -53,21 +53,6 @@ interface IUiModeManager { 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 af41a7d5ac5d..07e257083fa2 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -242,50 +242,6 @@ public class UiModeManager { } /** - * 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 a0b0eeaaf1aa..4dd578e7deab 100644 --- a/core/java/android/app/VrManager.java +++ b/core/java/android/app/VrManager.java @@ -6,6 +6,8 @@ import android.content.ComponentName; 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 @@ public class VrManager { 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 391885dad45a..6d8d5e93c1ee 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -7207,7 +7207,7 @@ public class DevicePolicyManager { * @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/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 76828eeba785..a5dd5bd30d63 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -56,6 +56,7 @@ public class FullBackup { public static final String APK_TREE_TOKEN = "a"; public static final String OBB_TREE_TOKEN = "obb"; + public static final String KEY_VALUE_DATA_TOKEN = "k"; public static final String ROOT_TREE_TOKEN = "r"; public static final String FILES_TREE_TOKEN = "f"; diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 59a941ac46c8..9c3b11009243 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -144,9 +144,10 @@ interface IBackupManager { void backupNow(); /** - * Write a full backup of the given package to the supplied file descriptor. + * Write a backup of the given package to the supplied file descriptor. * The fd may be a socket or other non-seekable destination. If no package names * are supplied, then every application on the device will be backed up to the output. + * Currently only used by the 'adb backup' command. * * <p>This method is <i>synchronous</i> -- it does not return until the backup has * completed. @@ -167,12 +168,14 @@ interface IBackupManager { * as including packages pre-installed as part of the system. If {@code false}, * then setting {@code allApps} to {@code true} will mean only that all 3rd-party * applications will be included in the dataset. + * @param doKeyValue If {@code true}, also packages supporting key-value backup will be backed + * up. If {@code false}, key-value packages will be skipped. * @param packageNames The package names of the apps whose data (and optionally .apk files) * are to be backed up. The <code>allApps</code> parameter supersedes this. */ - void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, + void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem, - boolean doCompress, in String[] packageNames); + boolean doCompress, boolean doKeyValue, in String[] packageNames); /** * Perform a full-dataset backup of the given applications via the currently active @@ -184,11 +187,12 @@ interface IBackupManager { /** * Restore device content from the data stream passed through the given socket. The - * data stream must be in the format emitted by fullBackup(). + * data stream must be in the format emitted by adbBackup(). + * Currently only used by the 'adb restore' command. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. */ - void fullRestore(in ParcelFileDescriptor fd); + void adbRestore(in ParcelFileDescriptor fd); /** * Confirm that the requested full backup/restore operation can proceed. The system will diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index 6b3ef4a05895..9e2eb84b13f9 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -141,6 +141,7 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { 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 be69df928e17..11a15c66385a 100644 --- a/core/java/android/bluetooth/BluetoothGattCallback.java +++ b/core/java/android/bluetooth/BluetoothGattCallback.java @@ -179,4 +179,22 @@ public abstract class BluetoothGattCallback{ */ 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 1bd7bd4dc6f3..c991e2f71bb8 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -65,6 +65,7 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { } /** - * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * Execute pending writes. * @hide */ + @Override public void onExecuteWrite(String address, int transId, boolean execWrite) { if (DBG) Log.d(TAG, "onExecuteWrite() - " @@ -261,6 +260,7 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { 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 0a890721de66..3b8f962bf73e 100644 --- a/core/java/android/bluetooth/BluetoothGattServerCallback.java +++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java @@ -184,4 +184,23 @@ public abstract class BluetoothGattServerCallback { */ 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 736f4b2b048f..ed69e54671ca 100644 --- a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl +++ b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl @@ -37,4 +37,6 @@ oneway interface IBluetoothGattCallbackExt { 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 091ffb3fe987..267e8824439d 100644 --- a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl +++ b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl @@ -42,4 +42,6 @@ oneway interface IBluetoothGattServerCallbackExt { 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 ecdc0ce902ef..78e3de47df06 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -214,7 +214,7 @@ public final class CompanionDeviceManager { } 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 6dbe5fb2468b..b01e6a156f95 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -223,6 +223,15 @@ public class ActivityInfo extends ComponentInfo 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 @@ public class ActivityInfo extends ComponentInfo requestedVrComponent = orig.requestedVrComponent; rotationAnimation = orig.rotationAnimation; colorMode = orig.colorMode; + maxAspectRatio = orig.maxAspectRatio; } /** @@ -1064,6 +1074,9 @@ public class ActivityInfo extends ComponentInfo 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 @@ public class ActivityInfo extends ComponentInfo dest.writeString(requestedVrComponent); dest.writeInt(rotationAnimation); dest.writeInt(colorMode); + dest.writeFloat(maxAspectRatio); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -1146,6 +1160,7 @@ public class ActivityInfo extends ComponentInfo 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 ffc7719c72fd..939e20fb3f8e 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -603,6 +603,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { 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 fb69986686bd..1fafe6534077 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_MANIFEST_MA 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 class PackageParser { 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 @@ public class PackageParser { 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 @@ public class PackageParser { 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 @@ public class PackageParser { } } + 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 @@ public class PackageParser { 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 acf0677559fd..061346c18bbe 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -178,24 +178,39 @@ public class Camera { 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/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java index f82c9e2b3f35..ad20ce5cf428 100644 --- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java +++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java @@ -130,9 +130,11 @@ public class KeyphraseEnrollmentInfo { continue; } - mKeyphrasePackageMap.put( - getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors), - ai.packageName); + KeyphraseMetadata metadata = + getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors); + if (metadata != null) { + mKeyphrasePackageMap.put(metadata, ai.packageName); + } } catch (PackageManager.NameNotFoundException e) { String error = "error parsing voice enrollment meta-data for " + ri.activityInfo.packageName; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 29884b132e5a..dd11f68ffe50 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -24,14 +24,12 @@ import java.util.Formatter; 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 @@ public abstract class BatteryStats implements Parcelable { * 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 @@ public abstract class BatteryStats implements Parcelable { /** * 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 @@ public abstract class BatteryStats implements Parcelable { /** * 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 class BatteryStats implements Parcelable { 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 class BatteryStats implements Parcelable { 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 class BatteryStats implements Parcelable { 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 @@ public abstract class BatteryStats implements Parcelable { * @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 @@ public abstract class BatteryStats implements Parcelable { 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 @@ public abstract class BatteryStats implements Parcelable { 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 @@ public abstract class BatteryStats implements Parcelable { 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 @@ public abstract class BatteryStats implements Parcelable { } 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 @@ public abstract class BatteryStats implements Parcelable { 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 @@ public abstract class BatteryStats implements Parcelable { 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 @@ public abstract class BatteryStats implements Parcelable { 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/provider/Settings.java b/core/java/android/provider/Settings.java index 83528741dd82..7005d44ee08a 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5482,6 +5482,20 @@ public final class Settings { 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 @@ public final class Settings { "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 */ @@ -5497,6 +5511,16 @@ public final class Settings { "accessibility_shortcut_target_service"; /** + * Setting specifying the accessibility service or feature to be toggled via the + * accessibility button in the navigation bar. This is either a flattened + * {@link ComponentName} or the class name of a system class implementing a supported + * accessibility feature. + * @hide + */ + public static final String ACCESSIBILITY_BUTTON_TARGET_COMPONENT = + "accessibility_button_target_component"; + + /** * If touch exploration is enabled. */ public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled"; @@ -6983,7 +7007,10 @@ public final class Settings { TOUCH_EXPLORATION_ENABLED, ACCESSIBILITY_ENABLED, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, + ACCESSIBILITY_BUTTON_TARGET_COMPONENT, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + ACCESSIBILITY_SHORTCUT_ENABLED, + ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, ACCESSIBILITY_SPEAK_PASSWORD, ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, ACCESSIBILITY_CAPTIONING_PRESET, @@ -9731,6 +9758,15 @@ public final class Settings { public static final String RETAIL_DEMO_MODE_CONSTANTS = "retail_demo_mode_constants"; /** + * Indicates the maximum time that an app is blocked for the network rules to get updated. + * + * Type: long + * + * @hide + */ + public static final String NETWORK_ACCESS_TIMEOUT_MS = "network_access_timeout_ms"; + + /** * The reason for the settings database being downgraded. This is only for * troubleshooting purposes and its value should not be interpreted in any way. * diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl index fce06d6d91c2..6eea07d33c40 100644 --- a/core/java/android/service/vr/IVrManager.aidl +++ b/core/java/android/service/vr/IVrManager.aidl @@ -73,5 +73,17 @@ interface IVrManager { * 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/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index c9f9f310e0b8..35276ccd5172 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -22,6 +22,7 @@ import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -97,6 +98,22 @@ public final class AccessibilityManager { /** @hide */ public static final int AUTOCLICK_DELAY_DEFAULT = 600; + /** + * Activity action: Launch UI to manage which accessibility service or feature is assigned + * to the navigation bar Accessibility button. + * <p> + * Input: Nothing. + * </p> + * <p> + * Output: Nothing. + * </p> + * + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON = + "android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON"; + static final Object sInstanceSync = new Object(); private static AccessibilityManager sInstance; diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 9ed6371404c4..b4d2c6bfe016 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -26,14 +26,20 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.graphics.Rect; +import android.metrics.LogMaker; import android.os.Bundle; import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Log; +import android.util.SparseArray; import android.view.View; import android.view.WindowManagerGlobal; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; @@ -83,7 +89,7 @@ public final class AutofillManager { /** @hide */ public static final int FLAG_VIEW_EXITED = 0x20000000; /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x10000000; - @NonNull private final Rect mTempRect = new Rect(); + private final MetricsLogger mMetricsLogger = new MetricsLogger(); private final IAutoFillManager mService; private IAutoFillManagerClient mServiceClient; @@ -98,25 +104,37 @@ public final class AutofillManager { /** @hide */ public interface AutofillClient { /** - * Asks the client to perform an autofill. - * - * @param ids The values to autofill - * @param values The values to autofill - */ - void autofill(List<AutofillId> ids, List<AutofillValue> values); - - /** * Asks the client to start an authentication flow. * * @param intent The authentication intent. * @param fillInIntent The authentication fill-in intent. */ - void authenticate(IntentSender intent, Intent fillInIntent); + void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent); /** * Tells the client this manager has state to be reset. */ - void resetableStateAvailable(); + void autofillCallbackResetableStateAvailable(); + + /** + * Request showing the autofill UI. + * + * @param anchor The real view the UI needs to anchor to. + * @param width The width of the fill UI content. + * @param height The height of the fill UI content. + * @param virtualBounds The bounds of the virtual decendant of the anchor. + * @param presenter The presenter that controls the fill UI window. + * @return Whether the UI was shown. + */ + boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height, + @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter); + + /** + * Request hiding the autofill UI. + * + * @return Whether the UI was hidden. + */ + boolean autofillCallbackRequestHideFillUi(); } /** @@ -156,12 +174,10 @@ public final class AutofillManager { return; } - final Rect bounds = mTempRect; - view.getBoundsOnScreen(bounds); final AutofillId id = getAutofillId(view); final AutofillValue value = view.getAutofillValue(); - startSession(id, view.getWindowToken(), bounds, value, FLAG_MANUAL_REQUEST); + startSession(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST); } /** @@ -202,17 +218,15 @@ public final class AutofillManager { return; } - final Rect bounds = mTempRect; - view.getBoundsOnScreen(bounds); final AutofillId id = getAutofillId(view); final AutofillValue value = view.getAutofillValue(); if (!mHasSession) { // Starts new session. - startSession(id, view.getWindowToken(), bounds, value, 0); + startSession(id, view.getWindowToken(), null, value, 0); } else { // Update focus on existing session. - updateSession(id, bounds, value, FLAG_VIEW_ENTERED); + updateSession(id, null, value, FLAG_VIEW_ENTERED); } } @@ -389,7 +403,7 @@ public final class AutofillManager { mCallback != null, flags, mContext.getOpPackageName()); AutofillClient client = getClient(); if (client != null) { - client.resetableStateAvailable(); + client.autofillCallbackResetableStateAvailable(); } mHasSession = true; } catch (RemoteException e) { @@ -490,28 +504,114 @@ public final class AutofillManager { } } - private void onAutofillEvent(IBinder windowToken, AutofillId id, int event) { - if (mCallback == null) return; - if (id == null) { - Log.w(TAG, "onAutofillEvent(): no id for event " + event); + private void requestShowFillUi(IBinder windowToken, AutofillId id, int width, int height, + Rect anchorBounds, IAutofillWindowPresenter presenter) { + final View anchor = findAchorView(windowToken, id); + if (anchor == null) { return; } + if (getClient().autofillCallbackRequestShowFillUi(anchor, width, height, + anchorBounds, presenter) && mCallback != null) { + if (id.isVirtual()) { + mCallback.onAutofillEvent(anchor, id.getVirtualChildId(), + AutofillCallback.EVENT_INPUT_SHOWN); + } else { + mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); + } + } + } + private void handleAutofill(IBinder windowToken, List<AutofillId> ids, + List<AutofillValue> values) { final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken); if (root == null) { - Log.w(TAG, "onAutofillEvent() for " + id + ": root view gone"); return; } + + final int itemCount = ids.size(); + int numApplied = 0; + ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null; + + for (int i = 0; i < itemCount; i++) { + final AutofillId id = ids.get(i); + final AutofillValue value = values.get(i); + final int viewId = id.getViewId(); + final View view = root.findViewByAccessibilityIdTraversal(viewId); + if (view == null) { + Log.w(TAG, "autofill(): no View with id " + viewId); + continue; + } + if (id.isVirtual()) { + if (virtualValues == null) { + // Most likely there will be just one view with virtual children. + virtualValues = new ArrayMap<>(1); + } + SparseArray<AutofillValue> valuesByParent = virtualValues.get(view); + if (valuesByParent == null) { + // We don't know the size yet, but usually it will be just a few fields... + valuesByParent = new SparseArray<>(5); + virtualValues.put(view, valuesByParent); + } + valuesByParent.put(id.getVirtualChildId(), value); + } else { + if (view.autofill(value)) { + numApplied++; + } + } + } + + if (virtualValues != null) { + for (int i = 0; i < virtualValues.size(); i++) { + final View parent = virtualValues.keyAt(i); + final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i); + if (parent.autofill(childrenValues)) { + numApplied += childrenValues.size(); + } + } + } + + final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED); + log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount); + log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied); + mMetricsLogger.write(log); + } + + private void requestHideFillUi(IBinder windowToken, AutofillId id) { + if (getClient().autofillCallbackRequestHideFillUi() && mCallback != null) { + final View anchor = findAchorView(windowToken, id); + if (id.isVirtual()) { + mCallback.onAutofillEvent(anchor, id.getVirtualChildId(), + AutofillCallback.EVENT_INPUT_HIDDEN); + } else { + mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); + } + } + } + + private void notifyNoFillUi(IBinder windowToken, AutofillId id) { + if (mCallback != null) { + final View anchor = findAchorView(windowToken, id); + if (id.isVirtual()) { + mCallback.onAutofillEvent(anchor, id.getVirtualChildId(), + AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } else { + mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } + } + } + + private View findAchorView(IBinder windowToken, AutofillId id) { + final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken); + if (root == null) { + Log.w(TAG, "no window with token " + windowToken); + return null; + } final View view = root.findViewByAccessibilityIdTraversal(id.getViewId()); if (view == null) { - Log.w(TAG, "onAutofillEvent() for " + id + ": view gone"); - return; - } - if (id.isVirtual()) { - mCallback.onAutofillEvent(view, id.getVirtualChildId(), event); - } else { - mCallback.onAutofillEvent(view, event); + Log.w(TAG, "no view with id " + id); + return null; } + return view; } /** @@ -590,38 +690,62 @@ public final class AutofillManager { } @Override - public void autofill(List<AutofillId> ids, List<AutofillValue> values) { + public void autofill(IBinder windowToken, List<AutofillId> ids, + List<AutofillValue> values) { // TODO(b/33197203): must keep the dataset so subsequent calls pass the same // dataset.extras to service final AutofillManager afm = mAfm.get(); if (afm != null) { + afm.mContext.getMainThreadHandler().post(() -> + afm.handleAutofill(windowToken, ids, values)); + } + } + + @Override + public void authenticate(IntentSender intent, Intent fillInIntent) { + final AutofillManager afm = mAfm.get(); + if (afm != null) { afm.mContext.getMainThreadHandler().post(() -> { if (afm.getClient() != null) { - afm.getClient().autofill(ids, values); + afm.getClient().autofillCallbackAuthenticate(intent, fillInIntent); } }); } } @Override - public void authenticate(IntentSender intent, Intent fillInIntent) { + public void requestShowFillUi(IBinder windowToken, AutofillId id, + int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) { + final AutofillManager afm = mAfm.get(); + if (afm != null) { + afm.mContext.getMainThreadHandler().post(() -> { + if (afm.getClient() != null) { + afm.requestShowFillUi(windowToken, id, width, + height, anchorBounds, presenter); + } + }); + } + } + + @Override + public void requestHideFillUi(IBinder windowToken, AutofillId id) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.mContext.getMainThreadHandler().post(() -> { if (afm.getClient() != null) { - afm.getClient().authenticate(intent, fillInIntent); + afm.requestHideFillUi(windowToken, id); } }); } } @Override - public void onAutofillEvent(IBinder windowToken, AutofillId id, int event) { + public void notifyNoFillUi(IBinder windowToken, AutofillId id) { final AutofillManager afm = mAfm.get(); if (afm != null) { afm.mContext.getMainThreadHandler().post(() -> { if (afm.getClient() != null) { - afm.onAutofillEvent(windowToken, id, event); + afm.notifyNoFillUi(windowToken, id); } }); } diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java new file mode 100644 index 000000000000..056e540d3bb3 --- /dev/null +++ b/core/java/android/view/autofill/AutofillPopupWindow.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.autofill; + +import android.annotation.NonNull; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.RemoteException; +import android.transition.Transition; +import android.util.Log; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.widget.PopupWindow; + +/** + * Custom {@link PopupWindow} used to isolate its content from the autofilled app - the + * UI is rendered in a framework process, but it's controlled by the app. + * + * TODO(b/34943932): use an app surface control solution. + * + * @hide + */ +public class AutofillPopupWindow extends PopupWindow { + + private static final String TAG = "AutofillPopupWindow"; + + private final WindowPresenter mWindowPresenter; + private WindowManager.LayoutParams mWindowLayoutParams; + + /** + * Creates a popup window with a presenter owning the window and responsible for + * showing/hiding/updating the backing window. This can be useful of the window is + * being shown by another process while the popup logic is in the process hosting + * the anchor view. + * <p> + * Using this constructor means that the presenter completely owns the content of + * the window and the following methods manipulating the window content shouldn't + * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)}, + * {@link #getExitTransition()}, {@link #setExitTransition(Transition)}, + * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()}, + * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()}, + * {@link #setElevation(float)}, ({@link #getAnimationStyle()}, + * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p> + */ + public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) { + mWindowPresenter = new WindowPresenter(presenter); + + setOutsideTouchable(true); + setInputMethodMode(INPUT_METHOD_NEEDED); + } + + @Override + protected boolean hasContentView() { + return true; + } + + @Override + protected boolean hasDecorView() { + return true; + } + + @Override + protected LayoutParams getDecorViewLayoutParams() { + return mWindowLayoutParams; + } + + /** + * The effective {@code update} method that should be called by its clients. + */ + public void update(View anchor, int offsetX, int offsetY, int width, int height, + Rect anchorBounds, Rect actualAnchorBounds) { + if (!isShowing()) { + setWidth(width); + setHeight(height); + showAsDropDown(anchor, offsetX, offsetY); + } else { + update(anchor, offsetX, offsetY, width, height); + } + + if (anchorBounds != null && mWindowLayoutParams.y > anchorBounds.bottom) { + offsetY = anchorBounds.bottom - actualAnchorBounds.bottom; + update(anchor, offsetX, offsetY, width, height); + } + } + + @Override + protected void update(View anchor, WindowManager.LayoutParams params) { + final int layoutDirection = anchor != null ? anchor.getLayoutDirection() + : View.LAYOUT_DIRECTION_LOCALE; + mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(), + layoutDirection); + } + + @Override + public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { + if (isShowing()) { + return; + } + + setShowing(true); + setDropDown(true); + attachToAnchor(anchor, xoff, yoff, gravity); + final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams( + anchor.getWindowToken()); + final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, + p.width, p.height, gravity, getAllowScrollingAnchorParent()); + updateAboveAnchor(aboveAnchor); + p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId(); + p.packageName = anchor.getContext().getPackageName(); + mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(), + anchor.getLayoutDirection()); + return; + } + + @Override + public void dismiss() { + if (!isShowing() || isTransitioningToDismiss()) { + return; + } + + setShowing(false); + setTransitioningToDismiss(true); + + mWindowPresenter.hide(getTransitionEpicenter()); + detachFromAnchor(); + if (getOnDismissListener() != null) { + getOnDismissListener().onDismiss(); + } + } + + @Override + public int getAnimationStyle() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Drawable getBackground() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public View getContentView() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public float getElevation() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Transition getEnterTransition() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Transition getExitTransition() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setAnimationStyle(int animationStyle) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setBackgroundDrawable(Drawable background) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setContentView(View contentView) { + if (contentView != null) { + throw new IllegalStateException("You can't call this!"); + } + } + + @Override + public void setElevation(float elevation) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setEnterTransition(Transition enterTransition) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setExitTransition(Transition exitTransition) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setTouchInterceptor(OnTouchListener l) { + throw new IllegalStateException("You can't call this!"); + } + + /** + * Contract between the popup window and a presenter that is responsible for + * showing/hiding/updating the actual window. + * + * <p>This can be useful if the anchor is in one process and the backing window is owned by + * another process. + */ + private class WindowPresenter { + final IAutofillWindowPresenter mPresenter; + + WindowPresenter(IAutofillWindowPresenter presenter) { + mPresenter = presenter; + } + + /** + * Shows the backing window. + * + * @param p The window layout params. + * @param transitionEpicenter The transition epicenter if animating. + * @param fitsSystemWindows Whether the content view should account for system decorations. + * @param layoutDirection The content layout direction to be consistent with the anchor. + */ + void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, + int layoutDirection) { + try { + mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection); + } catch (RemoteException e) { + Log.w(TAG, "Error showing fill window", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Hides the backing window. + * + * @param transitionEpicenter The transition epicenter if animating. + */ + void hide(Rect transitionEpicenter) { + try { + mPresenter.hide(transitionEpicenter); + } catch (RemoteException e) { + Log.w(TAG, "Error hiding fill window", e); + e.rethrowFromSystemServer(); + } + } + } +} diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index eabf6b10eea0..7bea17466ac5 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -20,9 +20,11 @@ import java.util.List; import android.content.Intent; import android.content.IntentSender; +import android.graphics.Rect; import android.os.IBinder; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import android.view.autofill.IAutofillWindowPresenter; /** * Object running in the application process and responsible for autofilling it. @@ -38,7 +40,7 @@ oneway interface IAutoFillManagerClient { /** * Autofills the activity with the contents of a dataset. */ - void autofill(in List<AutofillId> ids, in List<AutofillValue> values); + void autofill(in IBinder windowToken, in List<AutofillId> ids, in List<AutofillValue> values); /** * Authenticates a fill response or a data set. @@ -46,7 +48,18 @@ oneway interface IAutoFillManagerClient { void authenticate(in IntentSender intent, in Intent fillInIntent); /** - * Notifies the client when the auto-fill UI changed. + * Requests showing the fill UI. */ - void onAutofillEvent(in IBinder windowToken, in AutofillId id, int event); + void requestShowFillUi(in IBinder windowToken, in AutofillId id, int width, + int height, in Rect anchorBounds, in IAutofillWindowPresenter presenter); + + /** + * Requests hiding the fill UI. + */ + void requestHideFillUi(in IBinder windowToken, in AutofillId id); + + /** + * Nitifies no fill UI will be shown. + */ + void notifyNoFillUi(in IBinder windowToken, in AutofillId id); } diff --git a/core/java/android/view/autofill/IAutofillWindowPresenter.aidl b/core/java/android/view/autofill/IAutofillWindowPresenter.aidl new file mode 100644 index 000000000000..172b992eb3ac --- /dev/null +++ b/core/java/android/view/autofill/IAutofillWindowPresenter.aidl @@ -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 android.view.autofill; + +import android.graphics.Rect; +import android.view.WindowManager; + +/** + * This is a handle to the FillUi for controlling + * when its window should be shown and hidden. + * + * {@hide} + */ +oneway interface IAutofillWindowPresenter { + void show(in WindowManager.LayoutParams p, in Rect transitionEpicenter, + boolean fitsSystemWindows, int layoutDirection); + void hide(in Rect transitionEpicenter); +} diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index f9d733201e59..f78d62250029 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -22,6 +22,7 @@ import android.os.Build; 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 @@ public class WebViewZygote { 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 class WebViewZygote { public static ZygoteProcess getProcess() { synchronized (sLock) { - connectToZygoteIfNeededLocked(); + if (sZygote != null) return sZygote; + + waitForServiceStartAndConnect(); return sZygote; } } @@ -95,17 +105,20 @@ public class WebViewZygote { 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 @@ public class WebViewZygote { 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 @@ public class WebViewZygote { } 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 @@ public class WebViewZygote { @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 fae574289b88..8f662ba79d5f 100644 --- a/core/java/android/widget/AbsSpinner.java +++ b/core/java/android/widget/AbsSpinner.java @@ -498,18 +498,30 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { 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(); + } + } + if (size == count) { + structure.setAutofillOptions(options); + } else { + final String[] validOptions = new String[size]; + System.arraycopy(options, 0, validOptions, 0, size); + structure.setAutofillOptions(validOptions); } - structure.setAutofillOptions(options); } } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 9f10531841e3..b63b899d5c92 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -209,7 +209,7 @@ public class PopupWindow { private int mGravity = Gravity.NO_GRAVITY; private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { - com.android.internal.R.attr.state_above_anchor + com.android.internal.R.attr.state_above_anchor }; private final OnAttachStateChangeListener mOnAnchorDetachedListener = @@ -589,7 +589,6 @@ public class PopupWindow { mIgnoreCheekPress = true; } - /** * <p>Change the animation style resource for this popup.</p> * @@ -860,6 +859,11 @@ public class PopupWindow { mAllowScrollingAnchorParent = enabled; } + /** @hide */ + protected final boolean getAllowScrollingAnchorParent() { + return mAllowScrollingAnchorParent; + } + /** * <p>Indicates whether the popup window supports splitting touches.</p> * @@ -960,6 +964,11 @@ public class PopupWindow { mLayoutInsetDecor = enabled; } + /** @hide */ + protected final boolean isLayoutInsetDecor() { + return mLayoutInsetDecor; + } + /** * Set the layout type for this window. * <p> @@ -1122,6 +1131,26 @@ public class PopupWindow { return mIsShowing; } + /** @hide */ + protected final void setShowing(boolean isShowing) { + mIsShowing = isShowing; + } + + /** @hide */ + protected final void setDropDown(boolean isDropDown) { + mIsDropdown = isDropDown; + } + + /** @hide */ + protected final void setTransitioningToDismiss(boolean transitioningToDismiss) { + mIsTransitioningToDismiss = transitioningToDismiss; + } + + /** @hide */ + protected final boolean isTransitioningToDismiss() { + return mIsTransitioningToDismiss; + } + /** * <p> * Display the content view in a popup window at the specified location. If the popup window @@ -1232,7 +1261,7 @@ public class PopupWindow { * @see #dismiss() */ public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { - if (isShowing() || mContentView == null) { + if (isShowing() || !hasContentView()) { return; } @@ -1255,7 +1284,8 @@ public class PopupWindow { invokePopup(p); } - private void updateAboveAnchor(boolean aboveAnchor) { + /** @hide */ + protected final void updateAboveAnchor(boolean aboveAnchor) { if (aboveAnchor != mAboveAnchor) { mAboveAnchor = aboveAnchor; @@ -1426,8 +1456,10 @@ public class PopupWindow { * @param token the window token used to bind the popup's window * * @return the layout parameters to pass to the window manager + * + * @hide */ - private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { + protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); // These gravity settings put the view at the top left corner of the @@ -1510,7 +1542,7 @@ public class PopupWindow { curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; } if (mAttachedInDecor) { - curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR; + curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR; } return curFlags; } @@ -1543,8 +1575,10 @@ public class PopupWindow { * @param allowScroll whether the anchor view's parent may be scrolled * when the popup window doesn't fit on screen * @return true if the popup is translated upwards to fit on screen + * + * @hide */ - private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, + protected final boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) { final int anchorHeight = anchor.getHeight(); final int anchorWidth = anchor.getWidth(); @@ -1853,7 +1887,7 @@ public class PopupWindow { * @see #showAsDropDown(android.view.View) */ public void dismiss() { - if (!isShowing() || mIsTransitioningToDismiss) { + if (!isShowing() || isTransitioningToDismiss()) { return; } @@ -1923,8 +1957,10 @@ public class PopupWindow { * * @return the window-relative epicenter bounds to be used by enter and * exit transitions + * + * @hide */ - private Rect getTransitionEpicenter() { + protected final Rect getTransitionEpicenter() { final View anchor = mAnchor != null ? mAnchor.get() : null; final View decor = mDecorView; if (anchor == null || decor == null) { @@ -1981,6 +2017,11 @@ public class PopupWindow { mOnDismissListener = onDismissListener; } + /** @hide */ + protected final OnDismissListener getOnDismissListener() { + return mOnDismissListener; + } + /** * Updates the state of the popup window, if it is currently being displayed, * from the currently set state. @@ -1996,12 +2037,11 @@ public class PopupWindow { * </ul> */ public void update() { - if (!isShowing() || mContentView == null) { + if (!isShowing() || !hasContentView()) { return; } - final WindowManager.LayoutParams p = - (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + final WindowManager.LayoutParams p = getDecorViewLayoutParams(); boolean update = false; @@ -2024,11 +2064,16 @@ public class PopupWindow { } if (update) { - setLayoutDirectionFromAnchor(); - mWindowManager.updateViewLayout(mDecorView, p); + update(mAnchor.get(), p); } } + /** @hide */ + protected void update(View anchor, WindowManager.LayoutParams params) { + setLayoutDirectionFromAnchor(); + mWindowManager.updateViewLayout(mDecorView, params); + } + /** * Updates the dimension of the popup window. * <p> @@ -2039,8 +2084,7 @@ public class PopupWindow { * @param height the new height in pixels, must be >= 0 or -1 to ignore */ public void update(int width, int height) { - final WindowManager.LayoutParams p = - (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + final WindowManager.LayoutParams p = getDecorViewLayoutParams(); update(p.x, p.y, width, height, false); } @@ -2086,12 +2130,11 @@ public class PopupWindow { setHeight(height); } - if (!isShowing() || mContentView == null) { + if (!isShowing() || !hasContentView()) { return; } - final WindowManager.LayoutParams p = - (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + final WindowManager.LayoutParams p = getDecorViewLayoutParams(); boolean update = force; @@ -2135,19 +2178,34 @@ public class PopupWindow { update = true; } - int newAccessibilityIdOfAnchor = - (mAnchor != null) ? mAnchor.get().getAccessibilityViewId() : -1; + final View anchor = mAnchor.get(); + final int newAccessibilityIdOfAnchor = (anchor != null) + ? anchor.getAccessibilityViewId() : -1; if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) { p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor; update = true; } if (update) { - setLayoutDirectionFromAnchor(); - mWindowManager.updateViewLayout(mDecorView, p); + update(anchor, p); } } + /** @hide */ + protected boolean hasContentView() { + return mContentView != null; + } + + /** @hide */ + protected boolean hasDecorView() { + return mDecorView != null; + } + + /** @hide */ + protected WindowManager.LayoutParams getDecorViewLayoutParams() { + return (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + } + /** * Updates the position and the dimension of the popup window. * <p> @@ -2185,7 +2243,7 @@ public class PopupWindow { private void update(View anchor, boolean updateLocation, int xoff, int yoff, int width, int height) { - if (!isShowing() || mContentView == null) { + if (!isShowing() || !hasContentView()) { return; } @@ -2201,7 +2259,7 @@ public class PopupWindow { mAnchorYoff = yoff; } - final LayoutParams p = (LayoutParams) mDecorView.getLayoutParams(); + final WindowManager.LayoutParams p = getDecorViewLayoutParams(); final int oldGravity = p.gravity; final int oldWidth = p.width; final int oldHeight = p.height; @@ -2243,7 +2301,8 @@ public class PopupWindow { public void onDismiss(); } - private void detachFromAnchor() { + /** @hide */ + protected final void detachFromAnchor() { final View anchor = mAnchor != null ? mAnchor.get() : null; if (anchor != null) { final ViewTreeObserver vto = anchor.getViewTreeObserver(); @@ -2262,7 +2321,8 @@ public class PopupWindow { mIsAnchorRootAttached = false; } - private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) { + /** @hide */ + protected final void attachToAnchor(View anchor, int xoff, int yoff, int gravity) { detachFromAnchor(); final ViewTreeObserver vto = anchor.getViewTreeObserver(); @@ -2287,9 +2347,8 @@ public class PopupWindow { private void alignToAnchor() { final View anchor = mAnchor != null ? mAnchor.get() : null; - if (anchor != null && anchor.isAttachedToWindow() && mDecorView != null) { - final WindowManager.LayoutParams p = (WindowManager.LayoutParams) - mDecorView.getLayoutParams(); + if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) { + final WindowManager.LayoutParams p = getDecorViewLayoutParams(); updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, p.width, p.height, mAnchoredGravity, false)); diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java new file mode 100644 index 000000000000..ee5d339a19d3 --- /dev/null +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.app; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Activity; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; +import android.widget.BaseAdapter; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Activity used to display and persist a service or feature target for the Accessibility button. + */ +public class AccessibilityButtonChooserActivity extends Activity { + + private static final String MAGNIFICATION_COMPONENT_ID = + "com.android.server.accessibility.MagnificationController"; + + private AccessibilityButtonTarget mMagnificationTarget = null; + + private List<AccessibilityButtonTarget> mTargets = null; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.accessibility_button_chooser); + + String component = Settings.Secure.getString(getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT); + if (TextUtils.isEmpty(component)) { + TextView prompt = (TextView) findViewById(R.id.accessibility_button_prompt); + prompt.setVisibility(View.VISIBLE); + } + + mMagnificationTarget = new AccessibilityButtonTarget(this, MAGNIFICATION_COMPONENT_ID, + R.string.accessibility_magnification_chooser_text, + R.drawable.resolver_icon_placeholder); + + mTargets = getServiceAccessibilityButtonTargets(this); + if (Settings.Secure.getInt(getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) { + mTargets.add(mMagnificationTarget); + } + + if (mTargets.size() < 2) { + // Why are we here? + finish(); + } + + GridView gridview = (GridView) findViewById(R.id.accessibility_button_chooser_grid); + gridview.setAdapter(new TargetAdapter()); + gridview.setOnItemClickListener((parent, view, position, id) -> { + onTargetSelected(mTargets.get(position)); + }); + } + + private static List<AccessibilityButtonTarget> getServiceAccessibilityButtonTargets( + @NonNull Context context) { + AccessibilityManager ams = (AccessibilityManager) context.getSystemService( + Context.ACCESSIBILITY_SERVICE); + List<AccessibilityServiceInfo> services = ams.getEnabledAccessibilityServiceList( + AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + if (services == null) { + return Collections.emptyList(); + } + + ArrayList<AccessibilityButtonTarget> targets = new ArrayList<>(services.size()); + for (AccessibilityServiceInfo info : services) { + if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) { + targets.add(new AccessibilityButtonTarget(context, info)); + } + } + + return targets; + } + + private void onTargetSelected(AccessibilityButtonTarget target) { + Settings.Secure.putString(getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, target.getId()); + finish(); + } + + private class TargetAdapter extends BaseAdapter { + @Override + public int getCount() { + return mTargets.size(); + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + LayoutInflater inflater = AccessibilityButtonChooserActivity.this.getLayoutInflater(); + View root = inflater.inflate(R.layout.accessibility_button_chooser_item, parent, false); + final AccessibilityButtonTarget target = mTargets.get(position); + ImageView iconView = root.findViewById(R.id.accessibility_button_target_icon); + TextView labelView = root.findViewById(R.id.accessibility_button_target_label); + iconView.setImageDrawable(target.getDrawable()); + labelView.setText(target.getLabel()); + return root; + } + } + + private static class AccessibilityButtonTarget { + public String mId; + public CharSequence mLabel; + public Drawable mDrawable; + + public AccessibilityButtonTarget(@NonNull Context context, + @NonNull AccessibilityServiceInfo serviceInfo) { + this.mId = serviceInfo.getComponentName().flattenToString(); + this.mLabel = serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()); + this.mDrawable = serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()); + } + + public AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId, + int iconRes) { + this.mId = id; + this.mLabel = context.getText(labelResId); + this.mDrawable = context.getDrawable(iconRes); + } + + public String getId() { + return mId; + } + + public CharSequence getLabel() { + return mLabel; + } + + public Drawable getDrawable() { + return mDrawable; + } + } +}
\ No newline at end of file diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 9d2141d814b9..3d0d6bf0e3ad 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -482,7 +482,8 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { * * 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 class BatteryStatsImpl extends BatteryStats { public void readSummaryFromParcelLocked(Parcel in) { super.readSummaryFromParcelLocked(in); mMaxDurationMs = in.readLong(); + mTotalDurationMs = in.readLong(); mStartTimeMs = -1; mCurrentDurationMs = 0; } @@ -1692,6 +1705,7 @@ public class BatteryStatsImpl extends BatteryStats { 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 class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { /** * 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 @@ public class BatteryStatsImpl extends BatteryStats { } 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 @@ public class BatteryStatsImpl extends BatteryStats { } } + /** + * 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 class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { StopwatchTimer mFlashlightTurnedOnTimer; StopwatchTimer mCameraTurnedOnTimer; StopwatchTimer mForegroundActivityTimer; - StopwatchTimer mBluetoothScanTimer; + DualTimer mBluetoothScanTimer; int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT; StopwatchTimer[] mProcessStateTimer; @@ -5357,6 +5514,10 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { if (mWifiScanTimer == null) { return 0; } - return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + return mWifiScanTimer.getMainTimer().getTotalTimeLocked(elapsedRealtimeUs, which); } @Override @@ -5763,7 +5926,33 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { @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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { } 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { } 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 class BatteryStatsImpl extends BatteryStats { } } - 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 class BatteryStatsImpl extends BatteryStats { } 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { continue; } - totalScanTimeMs += u.mBluetoothScanTimer.getTimeSinceMarkLocked( + totalScanTimeMs += u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked( elapsedRealtimeMs * 1000) / 1000; } @@ -9285,7 +9465,7 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 373bda96afba..90aee9e22f77 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -178,11 +178,7 @@ static void verifySystemIdmaps() // 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/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8abd02235487..8721f3414e4b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3344,6 +3344,19 @@ <category android:name="android.intent.category.VOICE" /> </intent-filter> </activity> + <activity android:name="com.android.internal.app.AccessibilityButtonChooserActivity" + android:theme="@style/Theme.DeviceDefault.Resolver" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true" + android:documentLaunchMode="never" + android:relinquishTaskIdentity="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:process=":ui"> + <intent-filter> + <action android:name="android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> <activity android:name="com.android.internal.app.IntentForwarderActivity" android:finishOnCloseSystemDialogs="true" android:theme="@style/Theme.NoDisplay" diff --git a/core/res/res/layout/accessibility_button_chooser.xml b/core/res/res/layout/accessibility_button_chooser.xml new file mode 100644 index 000000000000..0ef785f102bb --- /dev/null +++ b/core/res/res/layout/accessibility_button_chooser.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* Copyright 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. +*/ +--> +<com.android.internal.widget.ResolverDrawerLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:maxWidth="@dimen/resolver_max_width" + android:maxCollapsedHeight="256dp" + android:maxCollapsedHeightSmall="56dp" + android:id="@id/contentPanel"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="?attr/colorBackground" + android:paddingTop="8dp" + android:paddingBottom="8dp"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="56dp" + android:textAppearance="?attr/textAppearanceMedium" + android:text="@string/accessibility_button_prompt_text" + android:gravity="start|center_vertical" + android:layout_alignParentStart="true" + android:paddingStart="?attr/dialogPreferredPadding" + android:paddingEnd="?attr/dialogPreferredPadding" + android:paddingTop="8dp" + android:paddingBottom="8dp"/> + + <GridView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/accessibility_button_chooser_grid" + android:columnWidth="90dp" + android:numColumns="auto_fit" + android:verticalSpacing="10dp" + android:horizontalSpacing="10dp" + android:stretchMode="columnWidth" + android:paddingStart="?attr/dialogPreferredPadding" + android:paddingEnd="?attr/dialogPreferredPadding" + android:gravity="center"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/accessibility_button_prompt" + android:layout_alwaysShow="true" + android:textAppearance="?attr/textAppearanceMedium" + android:text="@string/accessibility_button_instructional_text" + android:gravity="start|center_vertical" + android:paddingStart="?attr/dialogPreferredPadding" + android:paddingEnd="?attr/dialogPreferredPadding" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:visibility="gone"/> + </LinearLayout> +</com.android.internal.widget.ResolverDrawerLayout> diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml new file mode 100644 index 000000000000..76a93083bd3e --- /dev/null +++ b/core/res/res/layout/accessibility_button_chooser_item.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 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. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="80dp" + android:gravity="center" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:background="?attr/selectableItemBackgroundBorderless"> + + <ImageView android:id="@+id/accessibility_button_target_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginLeft="3dp" + android:layout_marginRight="3dp" + android:layout_marginBottom="3dp" + android:scaleType="fitCenter"/> + + <TextView android:id="@+id/accessibility_button_target_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:textAppearance="?attr/textAppearanceSmall" + android:textColor="?attr/textColorPrimary" + android:textSize="12sp" + android:fontFamily="sans-serif-condensed" + android:gravity="top|center_horizontal" + android:minLines="2" + android:maxLines="2" + android:ellipsize="marquee"/> +</LinearLayout> + diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index b9409f23df39..ed5a42b707f7 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/public.xml b/core/res/res/values/public.xml index d6b5527bb590..876d44da416b 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 f959df9b85f9..868e256496bb 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.--> @@ -3955,6 +3958,15 @@ <string name="accessibility_shortcut_disabling_service">Accessibility Shortcut turned <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> off</string> + <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. --> + <string name="accessibility_button_prompt_text">Choose a feature to use when you tap the Accessibility button:</string> + + <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. --> + <string name="accessibility_button_instructional_text">To change features, touch & hold the Accessibility button.</string> + + <!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. --> + <string name="accessibility_magnification_chooser_text">Magnification</string> + <!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] --> <string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string> <!-- Message shown when switching to a user [CHAR LIMIT=none] --> @@ -3966,7 +3978,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 +4181,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 +4267,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 buttons.</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 +4285,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 +4476,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 f4d490aa0a52..b23c96c9b05f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2841,6 +2841,15 @@ <java-symbol type="string" name="leave_accessibility_shortcut_on" /> <java-symbol type="string" name="config_defaultAccessibilityService" /> + <!-- Accessibility Button --> + <java-symbol type="layout" name="accessibility_button_chooser" /> + <java-symbol type="layout" name="accessibility_button_chooser_item" /> + <java-symbol type="id" name="accessibility_button_chooser_grid" /> + <java-symbol type="id" name="accessibility_button_prompt" /> + <java-symbol type="id" name="accessibility_button_target_icon" /> + <java-symbol type="id" name="accessibility_button_target_label" /> + <java-symbol type="string" name="accessibility_magnification_chooser_text" /> + <!-- com.android.internal.widget.RecyclerView --> <java-symbol type="id" name="item_touch_helper_previous_elevation"/> <java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 7deff066b1c7..9911d9db6c03 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -790,6 +790,8 @@ easier. <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 3e33dd8e8409..782a50f12b2b 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -328,6 +328,7 @@ public class SettingsBackupTest { Settings.Global.USE_GOOGLE_MAIL, Settings.Global.VT_IMS_ENABLED, Settings.Global.WAIT_FOR_DEBUGGER, + Settings.Global.NETWORK_ACCESS_TIMEOUT_MS, Settings.Global.WARNING_TEMPERATURE, Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY, Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED, 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 000000000000..6b52b98e4758 --- /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 f1aeecc46265..a1b05cdbd9d8 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 @@ public class BatteryStatsDurationTimerTest extends TestCase { 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 @@ public class BatteryStatsDurationTimerTest extends TestCase { 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 @@ public class BatteryStatsDurationTimerTest extends TestCase { // 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 @@ public class BatteryStatsDurationTimerTest extends TestCase { // 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 @@ public class BatteryStatsDurationTimerTest extends TestCase { // 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 b4afddab97c9..251ceb04b973 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 @@ public class BatteryStatsSamplingTimerTest extends TestCase { 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 4ec78ff5be7f..a41a0235bef4 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 @@ package com.android.internal.os; 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 @@ public class BatteryStatsSensorTest extends TestCase { 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 @@ public class BatteryStatsSensorTest extends TestCase { 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 @@ public class BatteryStatsSensorTest extends TestCase { 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, sensorBgTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED)); + assertEquals(200_000, sensorBgTimer.getTotalTimeLocked( + clocks.realtime * 1000, BatteryStats.STATS_SINCE_CHARGED)); + } + + @SmallTest + 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); - assertEquals(1, sensorBgCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED)); + // 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 testNestedSensorReset() throws Exception { + 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 + + // UID_2 starts the sensor (background) + curr = 1000 * (clocks.realtime = clocks.uptime = 5016); + bi.noteStartSensorLocked(UID_2, SENSOR_ID); - // The sensor is started and the background counter has been created. - final BatteryStats.Uid uid = bi.getUidStats().get(UID); - assertNotNull(uid); + // UID enters background + curr = 1000 * (clocks.realtime = clocks.uptime = 6032); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); - BatteryStats.Uid.Sensor sensor = uid.getSensorStats().get(SENSOR_ID); - assertNotNull(sensor); - assertNotNull(sensor.getSensorTime()); - assertNotNull(sensor.getSensorBgCount()); + // UID enters background again (from a different background state) + curr = 1000 * (clocks.realtime = clocks.uptime = 7004); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_LAST_ACTIVITY); - // 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_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); - sensor = uid.getSensorStats().get(SENSOR_ID); - assertNotNull(sensor); - assertNotNull(sensor.getSensorTime()); - assertNull(sensor.getSensorBgCount()); + // 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); + + // 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)); + } - sensor = uid.getSensorStats().get(SENSOR_ID); - assertNull(sensor); + // 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 c7cd0ee710e1..11132683d4d1 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 @@ import org.junit.runners.Suite; 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 10541060398a..65f898c62815 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 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { MockBatteryStatsImpl(Clocks clocks) { super(clocks); this.clocks = mClocks; + mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase); } MockBatteryStatsImpl() { @@ -39,5 +40,9 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { 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 acacd7654cf1..5603508eaf09 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -74,7 +74,6 @@ const char* AssetManager::RESOURCES_FILENAME = "resources.arsc"; 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 becd307d114d..0441b9d789e2 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -66,11 +66,6 @@ public: * 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/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java index 8f7d6ac312b8..9e5049073fe7 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java @@ -25,6 +25,9 @@ import com.android.mediaframeworktest.MediaNames; import android.content.res.AssetFileDescriptor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.media.MediaCodecList; +import android.media.MediaExtractor; +import android.media.MediaFormat; import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; import android.media.MediaRecorder; @@ -805,6 +808,29 @@ public class CodecTest { mFailedToCompleteWithNoError = true; String testResult; + final MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); + final MediaExtractor extractor = new MediaExtractor(); + boolean hasSupportedVideo = false; + + try { + extractor.setDataSource(filePath); + + for (int index = 0; index < extractor.getTrackCount(); ++index) { + MediaFormat format = extractor.getTrackFormat(index); + String mime = format.getString(MediaFormat.KEY_MIME); + if (!mime.startsWith("video/")) { + continue; + } + + if (list.findDecoderForFormat(format) != null) { + hasSupportedVideo = true; + break; + } + } + } finally { + extractor.release(); + } + initializeMessageLooper(); synchronized (lock) { try { @@ -820,7 +846,12 @@ public class CodecTest { mMediaPlayer.setOnInfoListener(mInfoListener); Log.v(TAG, "playMediaSamples: sample file name " + filePath); mMediaPlayer.setDataSource(filePath); - mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder()); + if (hasSupportedVideo) { + mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder()); + } else { + Log.i(TAG, "Set no display due to no (supported) video track."); + mMediaPlayer.setDisplay(null); + } mMediaPlayer.prepare(); duration = mMediaPlayer.getDuration(); // start to play diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index ca34bb978ed8..cc2dc1bb6a7d 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/res/xml/timezones.xml b/packages/SettingsLib/res/xml/timezones.xml index 4426495b0cbe..12d31cfc6e8f 100644 --- a/packages/SettingsLib/res/xml/timezones.xml +++ b/packages/SettingsLib/res/xml/timezones.xml @@ -21,7 +21,7 @@ <timezone id="America/St_Johns"></timezone> <timezone id="America/Recife"></timezone> <timezone id="America/Sao_Paulo"></timezone> - <timezone id="America/Buenos_Aires"></timezone> + <timezone id="America/Argentina/Buenos_Aires"></timezone> <timezone id="America/Godthab"></timezone> <timezone id="America/Montevideo"></timezone> <timezone id="Atlantic/South_Georgia"></timezone> @@ -58,11 +58,11 @@ <timezone id="Asia/Karachi"></timezone> <timezone id="Asia/Oral"></timezone> <timezone id="Asia/Yekaterinburg"></timezone> - <timezone id="Asia/Calcutta"></timezone> + <timezone id="Asia/Kolkata"></timezone> <timezone id="Asia/Colombo"></timezone> - <timezone id="Asia/Katmandu"></timezone> + <timezone id="Asia/Kathmandu"></timezone> <timezone id="Asia/Almaty"></timezone> - <timezone id="Asia/Rangoon"></timezone> + <timezone id="Asia/Yangon"></timezone> <timezone id="Asia/Krasnoyarsk"></timezone> <timezone id="Asia/Bangkok"></timezone> <timezone id="Asia/Jakarta"></timezone> diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java index 9bb3c36056a9..350b64882ed5 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 @@ public class AccessibilityUtils { 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/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml index 1c49a55d4f3b..f6f2935e04ea 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 4b932a3361e6..e4d71b65ed6a 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/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java new file mode 100644 index 000000000000..8dde35782be5 --- /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 529c42154098..000000000000 --- 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 000000000000..4ce1e36bde3a --- /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 ff689aab7a74..2f5222744a86 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/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml index f5c3d4071dfc..deb494fdefbc 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/notification_menu_row.xml b/packages/SystemUI/res/layout/notification_menu_row.xml deleted file mode 100644 index 12bcf8176561..000000000000 --- 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 0fd2dc9541a0..64caefd3753c 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 d62cc184889c..7f3708788d95 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 fec3702145aa..5b20716fa41f 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> @@ -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 14f2c4aea667..8c1062bf4cf4 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -181,6 +181,7 @@ public class BatteryMeterView extends LinearLayout implements 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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index a95713fb017e..5a04108df807 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.ViewConfiguration; 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 @@ public class SwipeHelper implements Gefingerpoken { 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 @@ public class SwipeHelper implements Gefingerpoken { 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 @@ public class SwipeHelper implements Gefingerpoken { /** * 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 @@ public class SwipeHelper implements Gefingerpoken { 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 @@ public class SwipeHelper implements Gefingerpoken { 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/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 858008516d40..fbb075a5ae55 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -95,6 +95,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv */ private final Class<?>[] SERVICES_PER_USER = new Class[] { Dependency.class, + NotificationChannels.class, Recents.class }; diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 57c75bf6f54c..2b6ea1572e6c 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; 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 android.view.View; 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 @@ public class FragmentHostManager { 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/qs/tileimpl/ButtonRelativeLayout.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java new file mode 100644 index 000000000000..2c17b874dcab --- /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 d2ae6e9f5354..5c998090bada 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 @@ public class QSTileView extends QSTileBaseView { } 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 d552a2e75ad6..ed6e6ef439b5 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 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { 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 d3586c441214..f35de68a837d 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 @@ public class DndTile extends QSTileImpl<BooleanState> { 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 027064130ce3..fde2e0449c4e 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 @@ public class WifiTile extends QSTileImpl<SignalState> { 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 @@ public class WifiTile extends QSTileImpl<SignalState> { 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/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 00968ee8d0e4..9176f57c35cc 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.ViewStub; 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.MetricsLogger; 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 com.android.systemui.statusbar.stack.StackScrollState; 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { 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 class ExpandableNotificationRow extends ActivatableNotificationView { public void setAppName(String appName) { mAppName = appName; - if (mMenuRow != null) { + if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.setAppName(mAppName); } } @@ -496,7 +504,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override protected boolean handleSlideBack() { - if (mMenuRow != null && mMenuRow.isVisible()) { + if (mMenuRow != null && mMenuRow.isMenuVisible()) { animateTranslateNotification(0 /* targetLeft */); return true; } @@ -725,12 +733,65 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { super(context, attrs); mFalsingManager = FalsingManager.getInstance(context); mNotificationInflater = new NotificationInflater(this); + mMenuRow = new NotificationMenuRow(mContext); initDimens(); } @@ -1113,15 +1171,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } }); - // 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } invalidateOutline(); - if (mMenuRow != null) { - mMenuRow.resetState(true /* notify */); - } + mMenuRow.resetMenu(); } public void animateTranslateNotification(final float leftTarget) { @@ -1194,8 +1240,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } invalidateOutline(); - if (mMenuRow != null) { - mMenuRow.updateMenuAlpha(translationX, getMeasuredWidth()); + if (mMenuRow.getMenuView() != null) { + mMenuRow.onTranslationUpdate(translationX); } } @@ -1232,8 +1278,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { 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 fd1317e61436..2713f58272d0 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.internal.logging.nano.MetricsProto.MetricsEvent; 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 @@ import java.util.Set; /** * 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 @@ public class NotificationGuts extends FrameLayout 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 @@ public class NotificationGuts extends FrameLayout } } - 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 class NotificationGuts extends FrameLayout 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 54921a7108c6..0398f7be4cf1 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.internal.logging.nano.MetricsProto.MetricsEvent; 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 @@ import java.util.Set; /** * 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 @@ public class NotificationInfo extends LinearLayout implements GutsContent { 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 @@ public class NotificationInfo extends LinearLayout implements GutsContent { // Callback when checked. mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (mGutsInteractionListener != null) { - mGutsInteractionListener.onInteraction(NotificationInfo.this); + if (mGutsContainer != null) { + mGutsContainer.resetFalsingCheck(); } updateSecondaryText(); }); @@ -302,8 +300,8 @@ public class NotificationInfo extends LinearLayout implements GutsContent { } @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 534a71936b5a..5055dda0d863 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; - public NotificationMenuRow(Context context) { - this(context, null); - } + private CheckForDrag mCheckForDrag; + private Handler mHandler; - public NotificationMenuRow(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + private boolean mMenuSnappedTo; + private boolean mMenuSnappedOnLeft; + private boolean mShouldShowMenu; - public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } + private NotificationSwipeActionHelper mSwipeHelper; - public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs); - mMenuItems.addAll(getDefaultNotificationMenuItems()); + public NotificationMenuRow(Context context) { + 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); + mHandler = new Handler(); + mMenuItems = getDefaultMenuItems(context); } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Dependency.get(PluginManager.class).addPluginListener( - this, NotificationMenuRowProvider.class, false /* Allow multiple */); + public ArrayList<MenuItem> getMenuItems(Context context) { + return mMenuItems; } @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(this); + public MenuItem getLongpressMenuItem(Context context) { + return mMenuItems.get(NOTIFICATION_INFO_INDEX); } @Override - protected void onFinishInflate() { - super.onFinishInflate(); - final Resources res = getResources(); - 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 */); + public void setSwipeActionHelper(NotificationSwipeActionHelper helper) { + mSwipeHelper = helper; } - 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 void setMenuClickListener(OnMenuEventListener listener) { + mMenuListener = listener; } - 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); + @Override + public void createMenu(ViewGroup parent) { + mParent = (ExpandableNotificationRow) parent; + mMenuContainer = new FrameLayout(mContext); + for (int i = 0; i < mMenuItems.size(); i++) { + addMenuView(mMenuItems.get(i), mMenuContainer); + } + resetState(false); + } - 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 boolean isMenuVisible() { + return mAlpha > 0; } - private void updateMenu(boolean notify) { - removeAllViews(); - mMenuItems.clear(); - if (mMenuProvider != null) { - mMenuItems.addAll(mMenuProvider.getMenuItems(getContext())); - } - mMenuItems.addAll(getDefaultNotificationMenuItems()); - for (int i = 0; i < mMenuItems.size(); i++) { - final View v = createMenuView(mMenuItems.get(i)); - mMenuItems.get(i).menuView = v; - } - resetState(notify); + @Override + public View getMenuView() { + return mMenuContainer; } - 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 void resetMenu() { + resetState(true); } - public void resetState(boolean notify) { + 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; - public void setNotificationRowParent(ExpandableNotificationRow parent) { - mParent = parent; - setMenuLocation(mOnLeft ? 1 : -1); + case MotionEvent.ACTION_UP: + return handleUpEvent(ev, view, velocity); + } + return false; } - public void setAppName(String appName) { - Resources res = 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); + 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; } - public ExpandableNotificationRow getNotificationParent() { - return mParent; + private void showMenu(View animView, float targetLeft, float velocity) { + mMenuSnappedTo = true; + mMenuSnappedOnLeft = mOnLeft; + mMenuListener.onMenuShown(animView); + mSwipeHelper.snap(animView, targetLeft, velocity); } - public void setMenuAlpha(float alpha) { - mAlpha = alpha; - if (alpha == 0) { - mMenuFadedIn = false; // Can fade in again once it's gone. - setVisibility(View.INVISIBLE); - } else { - setVisibility(View.VISIBLE); - } - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - getChildAt(i).setAlpha(mAlpha); - } + private void snapBack(View animView, float velocity) { + mMenuSnappedTo = false; + mSnapping = true; + mSwipeHelper.snap(animView, 0 /* leftTarget */, velocity); } - /** - * Returns whether the menu is displayed on the left side of the view or not. - */ - public boolean isMenuOnLeft() { - return mOnLeft; + private void dismiss(View animView, float velocity) { + mMenuSnappedTo = false; + mDismissing = true; + mSwipeHelper.dismiss(animView, velocity); } - /** - * Returns the horizontal space in pixels required to display the menu. - */ - public float getSpaceForMenu() { - return mHorizSpaceForIcon * getChildCount(); + 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); } /** - * Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is - * visible. + * Returns whether the gesture is towards the menu location or not. */ - public boolean isVisible() { - return mAlpha > 0; + private boolean isTowardsMenu(float movement) { + return isMenuVisible() + && ((mOnLeft && movement <= 0) + || (!mOnLeft && movement >= 0)); + } + + @Override + public void setAppName(String appName) { + 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.getContentDescription()); + View menuView = item.getMenuView(); + if (menuView != null) { + menuView.setContentDescription(description); + } + } } - public void cancelFadeAnimator() { - if (mFadeAnimator != null) { - mFadeAnimator.cancel(); + @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 { + translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2; } + mMenuContainer.setTranslationY(translationY); } - 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 @@ public class NotificationMenuRow extends FrameLayout 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; - } - 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); - } - mOnLeft = onLeft; - mIconsPlaced = true; - } + public static ArrayList<MenuItem> getDefaultMenuItems(Context context) { + ArrayList<MenuItem> items = new ArrayList<MenuItem>(); + Resources res = context.getResources(); - public boolean isMenuLocationChange(float translation) { - boolean onLeft = translation > mIconPadding; - boolean onRight = translation < -mIconPadding; - if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) { - return true; - } - return false; - } + 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); - public void setDismissing() { - mDismissing = true; + 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; } - public void setSnapping(boolean snapping) { - mSnapping = snapping; + 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); + } } - @Override - public void onClick(View v) { - if (mListener == null) { - // Nothing to do - return; + 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; } - 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 View getMenuView() { + return mMenuView; + } - @Override - public void onPluginDisconnected(NotificationMenuRowProvider plugin) { - mMenuProvider = null; - updateMenu(false /* notify */); + @Override + public View getGutsView() { + return mGutsContent.getContentView(); + } + + @Override + public String getContentDescription() { + return mContentDescription; + } } -}
\ 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 6b1e62d9ad71..0de3e0244fa4 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 @@ package com.android.systemui.statusbar; */ 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.service.notification.StatusBarNotification; 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 @@ public class NotificationSnooze extends LinearLayout @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 @@ public class NotificationSnooze extends LinearLayout private void undoSnooze() { mSelectedOption = null; - mGutsInteractionListener.closeGuts(this); + mGutsContainer.closeControls(-1 /* x */, -1 /* y */, true /* notify */); } @Override @@ -185,18 +176,16 @@ public class NotificationSnooze extends LinearLayout 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 @@ public class NotificationSnooze extends LinearLayout // 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/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 5fb642fdcba7..1f03024fb781 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -557,9 +557,9 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } private boolean onAccessibilityLongClick(View v) { - // TODO(b/34720082): Target service selection via long click - android.widget.Toast.makeText(getContext(), "Service selection coming soon...", - android.widget.Toast.LENGTH_LONG).show(); + Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + v.getContext().startActivity(intent); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 101aee4e9130..b82b113f3f8d 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.fragments.PluginFragmentListener; 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.DejankUtils; 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 @@ import java.util.Stack; 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 class StatusBar extends SystemUI implements DemoMode, } - 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 @@ public class StatusBar extends SystemUI implements DemoMode, }, 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 @@ public class StatusBar extends SystemUI implements DemoMode, + "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 @@ public class StatusBar extends SystemUI implements DemoMode, @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 class StatusBar extends SystemUI implements DemoMode, } 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 d3b336bef629..7d2d0df3f0d4 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.PorterDuffXfermode; 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.Interpolators; 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 @@ import java.util.List; 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 class NotificationStackScrollLayout extends ViewGroup } } + public NotificationSwipeActionHelper getSwipeActionHelper() { + return mSwipeHelper; + } + @Override public void onMenuClicked(View view, int x, int y, MenuItem item) { if (mLongPressListener == null) { @@ -426,13 +429,22 @@ public class NotificationStackScrollLayout extends ViewGroup @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 @@ public class NotificationStackScrollLayout extends ViewGroup // 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup // of the panel early. handleChildDismissed(view); } - handleGearCoveredOrDismissed(); + handleMenuCoveredOrDismissed(); } @Override @@ -4294,121 +4279,21 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup // 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 */); - } - } - } - - /** - * 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); + // Touch was outside visible guts / meny notification, close what's visible + mStatusBar.dismissPopups(-1, -1, true /* resetMenu */, true /* animate */); } } } - 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 class NotificationStackScrollLayout extends ViewGroup } } - 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/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java index c2c633639e81..31b9bae846d9 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.AndroidTestingRunner; 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 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @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 f516d74f4062..8808988406a9 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 static org.mockito.Mockito.when; 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 com.android.systemui.SysuiTestCase; 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 @@ public class BluetoothControllerImplTest extends SysuiTestCase { @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 57d25819654f..783aae7121ad 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -45,16 +45,16 @@ message MetricsEvent { // The view or control was updated. TYPE_UPDATE = 6; - // Type for APP_TRANSITION event: The transition started a new activity for which it's process - // wasn't running. + // Type for APP_TRANSITION event: The transition started a new + // activity for which it's process wasn't running. TYPE_TRANSITION_COLD_LAUNCH = 7; - // Type for APP_TRANSITION event: The transition started a new activity for which it's process - // was already running. + // Type for APP_TRANSITION event: The transition started a new + // activity for which it's process was already running. TYPE_TRANSITION_WARM_LAUNCH = 8; - // Type for APP_TRANSITION event: The transition brought an already existing activity to the - // front. + // Type for APP_TRANSITION event: The transition brought an + // already existing activity to the front. TYPE_TRANSITION_HOT_LAUNCH = 9; // The action was successful @@ -64,6 +64,80 @@ message MetricsEvent { TYPE_FAILURE = 11; } + // Types of alerts, as bit field values + enum Alert { + // Vibrate the device. + ALERT_BUZZ = 1; + + // Make sound through the speaker. + ALERT_BEEP = 2; + + // Flash a notificaiton light. + ALERT_BLINK = 4; + } + + // Reasons that a notification might be dismissed. + enum DismissReason { + // from android.service.notification.NotificationListenerService + + // Notification was canceled by the status bar reporting a notification click + REASON_CLICK = 1; + + // Notification was canceled by the status bar reporting a user dismissal. + REASON_CANCEL = 2; + + // Notification was canceled by the status bar reporting a user dismiss all. + REASON_CANCEL_ALL = 3; + + // Notification was canceled by the status bar reporting an inflation error. + REASON_ERROR = 4; + + // Notification was canceled by the package manager modifying the package. + REASON_PACKAGE_CHANGED = 5; + + // Notification was canceled by the owning user context being stopped. + REASON_USER_STOPPED = 6; + + // Notification was canceled by the user banning the package. + REASON_PACKAGE_BANNED = 7; + + // Notification was canceled by the app canceling this specific notification. + REASON_APP_CANCEL = 8; + + //Notification was canceled by the app cancelling all its notifications. + REASON_APP_CANCEL_ALL = 9; + + // Notification was canceled by a listener reporting a user dismissal. + REASON_LISTENER_CANCEL = 10; + + //Notification was canceled by a listener reporting a user dismiss all. + REASON_LISTENER_CANCEL_ALL = 11; + + // Notification was canceled because it was a member of a canceled group. + REASON_GROUP_SUMMARY_CANCELED = 12; + + // Notification was canceled because it was an invisible member of a group. + REASON_GROUP_OPTIMIZATION = 13; + + // Notification was canceled by the device administrator suspending the package. + REASON_PACKAGE_SUSPENDED = 14; + + // Notification was canceled by the owning managed profile being turned off. + REASON_PROFILE_TURNED_OFF = 15; + + // Autobundled summary notification was canceled because its group was unbundled. + REASON_UNAUTOBUNDLED = 16; + + // Notification was canceled by the user banning the channel. + REASON_CHANNEL_BANNED = 17; + + // Notification was snoozed. + REASON_SNOOZED = 18; + + // Notification was canceled due to timeout. + REASON_TIMEOUT = 19; + } + // Known visual elements: views or controls. enum View { // Unknown view @@ -97,7 +171,9 @@ message MetricsEvent { // 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 +1957,9 @@ message MetricsEvent { // 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 @@ -3783,6 +3861,19 @@ message MetricsEvent { // 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; + + // OPEN: Settings -> System -> Reset options + RESET_DASHBOARD = 924; + // ---- 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 397938ac3160..05c659292e69 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -70,6 +70,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; import android.provider.Settings; +import android.provider.SettingsStringUtil; import android.provider.SettingsStringUtil.ComponentNameSet; import android.provider.SettingsStringUtil.SettingStringHelper; import android.text.TextUtils; @@ -100,7 +101,6 @@ import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; -import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.util.IntPair; import com.android.server.LocalServices; @@ -1154,17 +1154,55 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private void notifyAccessibilityButtonClickedLocked() { final UserState state = getCurrentUserStateLocked(); - if (state.mIsNavBarMagnificationEnabled) { - mMainHandler.obtainMessage( - MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget(); + + int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0; + for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { + final Service service = state.mBoundServices.get(i); + if (service.mRequestAccessibilityButton) { + potentialTargets++; + } + } + + if (potentialTargets == 0) { + return; + } + if (potentialTargets == 1) { + if (state.mIsNavBarMagnificationEnabled) { + mMainHandler.obtainMessage( + MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget(); + return; + } else { + for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { + final Service service = state.mBoundServices.get(i); + if (service.mRequestAccessibilityButton) { + service.notifyAccessibilityButtonClickedLocked(); + return; + } + } + } } else { - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - final Service service = state.mBoundServices.get(i); - // TODO(b/34720082): Only notify a single user-defined service - if (service.mRequestAccessibilityButton) { - service.notifyAccessibilityButtonClickedLocked(); + if (state.mServiceAssignedToAccessibilityButton == null + && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) { + mMainHandler.obtainMessage( + MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget(); + } else if (state.mIsNavBarMagnificationEnabled + && state.mIsNavBarMagnificationAssignedToAccessibilityButton) { + mMainHandler.obtainMessage( + MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget(); + return; + } else { + for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { + final Service service = state.mBoundServices.get(i); + if (service.mRequestAccessibilityButton && (service.mComponentName.equals( + state.mServiceAssignedToAccessibilityButton))) { + service.notifyAccessibilityButtonClickedLocked(); + return; + } } } + // The user may have turned off the assigned service or feature + mMainHandler.obtainMessage( + MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget(); } } @@ -1534,6 +1572,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private void showAccessibilityButtonTargetSelection() { + Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivity(intent); + } + private void scheduleNotifyClientsOfServicesStateChange(UserState userState) { mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS, userState.mUserId).sendToTarget(); @@ -1681,6 +1725,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { scheduleUpdateInputFilter(userState); scheduleUpdateClientsIfNeededLocked(userState); updateRelevantEventsLocked(userState); + updateAccessibilityButtonTargets(userState); } private void updateAccessibilityFocusBehaviorLocked(UserState userState) { @@ -1794,6 +1839,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { somethingChanged |= readMagnificationEnabledSettingsLocked(userState); somethingChanged |= readAutoclickEnabledSettingLocked(userState); somethingChanged |= readAccessibilityShortcutSettingLocked(userState); + somethingChanged |= readAccessibilityButtonSettingsLocked(userState); return somethingChanged; } @@ -1920,13 +1966,45 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } ComponentName componentNameToEnable = ComponentName.unflattenFromString(componentNameToEnableString); - if (componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) { + if ((componentNameToEnable != null) + && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) { return false; } userState.mServiceToEnableWithShortcut = componentNameToEnable; return true; } + private boolean readAccessibilityButtonSettingsLocked(UserState userState) { + String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId); + if (TextUtils.isEmpty(componentId)) { + if ((userState.mServiceAssignedToAccessibilityButton == null) + && !userState.mIsNavBarMagnificationAssignedToAccessibilityButton) { + return false; + } + userState.mServiceAssignedToAccessibilityButton = null; + userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false; + return true; + } + + if (componentId.equals(MagnificationController.class.getName())) { + if (userState.mIsNavBarMagnificationAssignedToAccessibilityButton) { + return false; + } + userState.mServiceAssignedToAccessibilityButton = null; + userState.mIsNavBarMagnificationAssignedToAccessibilityButton = true; + return true; + } + + ComponentName componentName = ComponentName.unflattenFromString(componentId); + if (componentName.equals(userState.mServiceAssignedToAccessibilityButton)) { + return false; + } + userState.mServiceAssignedToAccessibilityButton = componentName; + userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false; + return true; + } + /** * Check if the service that will be enabled by the shortcut is installed. If it isn't, * clear the value and the associated setting so a sideloaded service can't spoof the @@ -1948,7 +2026,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { 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); } } @@ -2135,6 +2215,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private void updateAccessibilityButtonTargets(UserState userState) { + final List<Service> services; + synchronized (mLock) { + services = userState.mBoundServices; + int numServices = services.size(); + for (int i = 0; i < numServices; i++) { + final Service service = services.get(i); + if (service.mRequestAccessibilityButton) { + boolean available = service.mComponentName.equals( + userState.mServiceAssignedToAccessibilityButton); + service.notifyAccessibilityButtonAvailabilityChangedLocked(available); + } + } + } + } + @GuardedBy("mLock") private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) { IBinder windowToken = mGlobalWindowTokens.get(windowId); @@ -2209,7 +2305,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * Disables accessibility service specified by {@param componentName} for the {@param userId}. */ private void disableAccessibilityServiceLocked(ComponentName componentName, int userId) { - final SettingStringHelper setting = + final SettingsStringUtil.SettingStringHelper setting = new SettingStringHelper( mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, @@ -2339,6 +2435,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public static final int MSG_UPDATE_FINGERPRINT = 11; public static final int MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS = 12; public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13; + public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14; public MainHandler(Looper looper) { super(looper); @@ -2432,6 +2529,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mInputFilter.notifyAccessibilityButtonClicked(); } } + } break; + + case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: { + showAccessibilityButtonTargetSelection(); } } } @@ -4810,6 +4911,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public int mSoftKeyboardShowMode = 0; public boolean mIsAccessibilityButtonAvailable; + public boolean mIsNavBarMagnificationAssignedToAccessibilityButton; + public ComponentName mServiceAssignedToAccessibilityButton; public boolean mIsTouchExplorationEnabled; public boolean mIsTextHighContrastEnabled; @@ -4885,6 +4988,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mIsEnhancedWebAccessibilityEnabled = false; mIsDisplayMagnificationEnabled = false; mIsNavBarMagnificationEnabled = false; + mServiceAssignedToAccessibilityButton = null; + mIsNavBarMagnificationAssignedToAccessibilityButton = false; mIsAutoclickEnabled = false; mSoftKeyboardShowMode = 0; @@ -4950,6 +5055,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final Uri mAccessibilityShortcutServiceIdUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); + private final Uri mAccessibilityButtonComponentIdUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT); + public AccessibilityContentObserver(Handler handler) { super(handler); } @@ -4982,6 +5090,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL); } @Override @@ -5039,6 +5149,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (readAccessibilityShortcutSettingLocked(userState)) { onUserStateChangedLocked(userState); } + } else if (mAccessibilityButtonComponentIdUri.equals(uri)) { + if (readAccessibilityButtonSettingsLocked(userState)) { + onUserStateChangedLocked(userState); + } } } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index a77e53378377..72d37ad10310 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -365,7 +365,6 @@ public final class AutofillManagerService extends SystemService { activityToken = Preconditions.checkNotNull(activityToken, "activityToken"); appCallback = Preconditions.checkNotNull(appCallback, "appCallback"); autofillId = Preconditions.checkNotNull(autofillId, "autoFillId"); - bounds = Preconditions.checkNotNull(bounds, "bounds"); packageName = Preconditions.checkNotNull(packageName, "packageName"); Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId"); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 6fb9f7c22f02..4d783503cb5f 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -71,8 +71,7 @@ import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; -import android.view.autofill.AutofillManager.AutofillCallback; - +import android.view.autofill.IAutofillWindowPresenter; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; @@ -298,17 +297,17 @@ final class AutofillManagerServiceImpl { } void startSessionLocked(@NonNull IBinder activityToken, @Nullable IBinder windowToken, - @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect bounds, - @Nullable AutofillValue value, boolean hasCallback, int flags, - @NonNull String packageName) { + @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, + @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, + int flags, @NonNull String packageName) { if (!isEnabled()) { return; } final String historyItem = "s=" + mInfo.getServiceInfo().packageName + " u=" + mUserId + " a=" + activityToken - - + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback + " f=" + flags; + + " i=" + autofillId + " b=" + virtualBounds + " hc=" + hasCallback + + " f=" + flags; mRequestsHistory.log(historyItem); // TODO(b/33197203): Handle partitioning @@ -320,7 +319,7 @@ final class AutofillManagerServiceImpl { final Session newSession = createSessionByTokenLocked(activityToken, windowToken, appCallbackToken, hasCallback, flags, packageName); - newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION); + newSession.updateLocked(autofillId, virtualBounds, value, FLAG_START_SESSION); } void finishSessionLocked(IBinder activityToken) { @@ -388,7 +387,7 @@ final class AutofillManagerServiceImpl { return newSession; } - void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect bounds, + void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect virtualBounds, AutofillValue value, int flags) { final Session session = mSessions.get(activityToken); if (session == null) { @@ -398,7 +397,7 @@ final class AutofillManagerServiceImpl { return; } - session.updateLocked(autofillId, bounds, value, flags); + session.updateLocked(autofillId, virtualBounds, value, flags); } private void handleSessionSave(IBinder activityToken) { @@ -508,8 +507,8 @@ final class AutofillManagerServiceImpl { /** * Called when the fill UI is ready to be shown for this view. */ - void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds, - AutofillId focusedId, @Nullable AutofillValue value); + void onFillReady(FillResponse fillResponse, AutofillId focusedId, + @Nullable AutofillValue value); } final AutofillId mId; @@ -522,7 +521,9 @@ final class AutofillManagerServiceImpl { Intent mAuthIntent; private AutofillValue mAutofillValue; - private Rect mBounds; + + // Bounds if a virtual view, null otherwise + private Rect mVirtualBounds; private boolean mValueUpdated; @@ -555,12 +556,12 @@ final class AutofillManagerServiceImpl { // TODO(b/33197203): need to refactor / rename / document this method to make it clear that // it can change the value and update the UI; similarly, should replace code that // directly sets mAutoFilLValue to use encapsulation. - void update(@Nullable AutofillValue autofillValue, @Nullable Rect bounds) { + void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) { if (autofillValue != null) { mAutofillValue = autofillValue; } - if (bounds != null) { - mBounds = bounds; + if (virtualBounds != null) { + mVirtualBounds = virtualBounds; } maybeCallOnFillReady(); @@ -568,19 +569,19 @@ final class AutofillManagerServiceImpl { /** * Calls {@link - * Listener#onFillReady(ViewState, FillResponse, Rect, AutofillId, AutofillValue)} if the + * Listener#onFillReady(FillResponse, AutofillId, AutofillValue)} if the * fill UI is ready to be displayed (i.e. when response and bounds are set). */ void maybeCallOnFillReady() { if (mResponse != null && (mResponse.getAuthentication() != null - || mResponse.getDatasets() != null) && mBounds != null) { - mListener.onFillReady(this, mResponse, mBounds, mId, mAutofillValue); + || mResponse.getDatasets() != null)) { + mListener.onFillReady(mResponse, mId, mAutofillValue); } } @Override public String toString() { - return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mBounds + return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds + ", updated = " + mValueUpdated + "]"; } @@ -588,7 +589,7 @@ final class AutofillManagerServiceImpl { pw.print(prefix); pw.print("id:" ); pw.println(mId); pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue); pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated); - pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds); + pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds); pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent); } } @@ -817,8 +818,24 @@ final class AutofillManagerServiceImpl { // AutoFillUiCallback @Override - public void onEvent(AutofillId id, int event) { - mHandlerCaller.getHandler().post(() -> notifyChangeToClient(id, event)); + public void requestShowFillUi(AutofillId id, int width, int height, + IAutofillWindowPresenter presenter) { + try { + mClient.requestShowFillUi(mWindowToken, id, width, height, + mCurrentViewState.mVirtualBounds, presenter); + } catch (RemoteException e) { + Slog.e(TAG, "Error requesting to show fill UI", e); + } + } + + // AutoFillUiCallback + @Override + public void requestHideFillUi(AutofillId id) { + try { + mClient.requestHideFillUi(mWindowToken, id); + } catch (RemoteException e) { + Slog.e(TAG, "Error requesting to hide fill UI", e); + } } public void setAuthenticationResultLocked(Bundle data) { @@ -835,10 +852,11 @@ final class AutofillManagerServiceImpl { processResponseLocked(mCurrentResponse); } else if (result instanceof Dataset) { Dataset dataset = (Dataset) result; - mCurrentResponse.getDatasets().remove(mAutoFilledDataset); - mCurrentResponse.getDatasets().add(dataset); - mAutoFilledDataset = dataset; - processResponseLocked(mCurrentResponse); + final int index = mCurrentResponse.getDatasets().indexOf(mAutoFilledDataset); + if (index >= 0) { + mCurrentResponse.getDatasets().set(index, dataset); + autoFill(dataset); + } } } } @@ -1009,7 +1027,7 @@ final class AutofillManagerServiceImpl { mRemoteFillService.onSaveRequest(mStructure, extras); } - void updateLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) { + void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) { if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) { // TODO(b/33197203): ignoring because we don't support partitions yet Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled"); @@ -1025,7 +1043,7 @@ final class AutofillManagerServiceImpl { if ((flags & FLAG_START_SESSION) != 0) { // View is triggering autofill. mCurrentViewState = viewState; - viewState.update(value, bounds); + viewState.update(value, virtualBounds); return; } @@ -1065,7 +1083,7 @@ final class AutofillManagerServiceImpl { } // If the ViewState is ready to be displayed, onReady() will be called. - viewState.update(value, bounds); + viewState.update(value, virtualBounds); // TODO(b/33197203): Remove when there is a response per activity. if (mCurrentResponse != null) { @@ -1087,23 +1105,14 @@ final class AutofillManagerServiceImpl { } @Override - public void onFillReady(ViewState viewState, FillResponse response, Rect bounds, - AutofillId filledId, @Nullable AutofillValue value) { + public void onFillReady(FillResponse response, AutofillId filledId, + @Nullable AutofillValue value) { String filterText = null; if (value != null && value.isText()) { filterText = value.getTextValue().toString(); } - getUiForShowing().showFillUi(filledId, response, bounds, filterText, mPackageName); - } - - private void notifyChangeToClient(AutofillId id, int event) { - if (!mHasCallback) return; - try { - mClient.onAutofillEvent(mWindowToken, id, event); - } catch (RemoteException e) { - Slog.e(TAG, "Error notifying client on change: id=" + id + ", event=" + event, e); - } + getUiForShowing().showFillUi(filledId, response, filterText, mPackageName); } private void notifyUnavailableToClient() { @@ -1112,7 +1121,13 @@ final class AutofillManagerServiceImpl { Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null"); return; } - notifyChangeToClient(mCurrentViewState.mId, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + if (!mHasCallback) return; + try { + mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId); + } catch (RemoteException e) { + Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken + + " id=" + mCurrentViewState.mId, e); + } } private void processResponseLocked(FillResponse response) { @@ -1212,7 +1227,7 @@ final class AutofillManagerServiceImpl { if (DEBUG) { Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); } - mClient.autofill(dataset.getFieldIds(), dataset.getFieldValues()); + mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues()); } catch (RemoteException e) { Slog.w(TAG, "Error autofilling activity: " + e); } @@ -1221,7 +1236,7 @@ final class AutofillManagerServiceImpl { private AutoFillUI getUiForShowing() { synchronized (mLock) { - mUi.setCallback(this, mWindowToken); + mUi.setCallback(this); return mUi; } } @@ -1261,8 +1276,7 @@ final class AutofillManagerServiceImpl { private void destroyLocked() { mRemoteFillService.destroy(); - mUi.setCallback(null, null); - + mUi.setCallback(null); mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName); } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 299b456ea022..003c8f15d5cd 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -43,7 +43,6 @@ import android.util.Slog; import com.android.internal.os.HandlerCaller; import com.android.server.FgThread; -import com.android.server.autofill.AutofillManagerServiceImpl.Session; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -89,10 +88,13 @@ final class RemoteFillService implements DeathRecipient { private PendingRequest mPendingRequest; public interface FillServiceCallbacks { - void onFillRequestSuccess(@Nullable FillResponse response, @NonNull String servicePackageName); - void onFillRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName); + void onFillRequestSuccess(@Nullable FillResponse response, + @NonNull String servicePackageName); + void onFillRequestFailure(@Nullable CharSequence message, + @NonNull String servicePackageName); void onSaveRequestSuccess(@NonNull String servicePackageName); - void onSaveRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName); + void onSaveRequestFailure(@Nullable CharSequence message, + @NonNull String servicePackageName); void onServiceDied(RemoteFillService service); void onDisableSelf(); } @@ -243,14 +245,10 @@ final class RemoteFillService implements DeathRecipient { } mBinding = false; if (isBound()) { - // TODO(b/33197203): synchronize access instead? - // Need to double check if it's null, since it could be set on onServiceDisconnected() - if (mAutoFillService != null) { - try { - mAutoFillService.onInit(null); - } catch (Exception e) { - Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e); - } + try { + mAutoFillService.onInit(null); + } catch (Exception e) { + Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e); } if (mAutoFillService != null) { mAutoFillService.asBinder().unlinkToDeath(this, 0); @@ -323,19 +321,13 @@ final class RemoteFillService implements DeathRecipient { handleBinderDied(); return; } - try { - // TODO(b/33197203): synchronize access instead? - // Need to double check if it's null, since it could be set on - // onServiceDisconnected() - if (mAutoFillService != null) { - mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() { - @Override - public void disableSelf() { - mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget(); - } - }); - } + mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() { + @Override + public void disableSelf() { + mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget(); + } + }); } catch (RemoteException e) { Slog.w(LOG_TAG, "Exception calling onConnected(): " + e); } 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 375a72607aa5..2555cee55aad 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -15,19 +15,14 @@ */ package com.android.server.autofill.ui; -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; import android.content.IntentSender; -import android.graphics.Rect; import android.metrics.LogMaker; import android.os.Handler; -import android.os.IBinder; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; import android.service.autofill.SaveInfo; @@ -35,6 +30,7 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Slog; import android.view.autofill.AutofillId; +import android.view.autofill.IAutofillWindowPresenter; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; @@ -62,7 +58,6 @@ public final class AutoFillUI { private @Nullable SaveUi mSaveUi; private @Nullable AutoFillUiCallback mCallback; - private @Nullable IBinder mWindowToken; private int mSaveTimeoutMs = (int) (5 * DateUtils.SECOND_IN_MILLIS); private final MetricsLogger mMetricsLogger = new MetricsLogger(); @@ -72,20 +67,20 @@ public final class AutoFillUI { void fill(@NonNull Dataset dataset); void save(); void cancelSave(); - void onEvent(AutofillId id, int event); + void requestShowFillUi(AutofillId id, int width, int height, + IAutofillWindowPresenter presenter); + void requestHideFillUi(AutofillId id); } public AutoFillUI(@NonNull Context context) { mContext = context; } - public void setCallback(@Nullable AutoFillUiCallback callback, - @Nullable IBinder windowToken) { + public void setCallback(@Nullable AutoFillUiCallback callback) { mHandler.post(() -> { - if (mCallback != callback || mWindowToken != windowToken) { + if (mCallback != callback) { hideAllUiThread(); mCallback = callback; - mWindowToken = windowToken; } }); } @@ -109,12 +104,7 @@ public final class AutoFillUI { * Hides the fill UI. */ public void hideFillUi(AutofillId id) { - mHandler.post(() -> { - hideFillUiUiThread(); - if (mCallback != null) { - mCallback.onEvent(id, EVENT_INPUT_HIDDEN); - } - }); + mHandler.post(this::hideFillUiUiThread); } /** @@ -135,36 +125,17 @@ public final class AutoFillUI { } /** - * Updates the position of the fill UI. - * - * @param anchoredBounds The bounds of the anchor view. - */ - public void updateFillUi(@NonNull Rect anchoredBounds) { - mHandler.post(() -> { - if (!hasCallback()) { - return; - } - hideSaveUiUiThread(); - if (mFillUi != null) { - mFillUi.update(anchoredBounds); - } - }); - } - - /** * Shows the fill UI, removing the previous fill UI if the has changed. * * @param focusedId the currently focused field * @param response the current fill response - * @param anchorBounds bounds of the focused view * @param filterText text of the view to be filled * @param packageName package name of the activity that is filled */ public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response, - @NonNull Rect anchorBounds, @Nullable String filterText, @NonNull String packageName) { + @Nullable String filterText, @NonNull String packageName) { if (DEBUG) { - Slog.d(TAG, "showFillUi(): id=" + focusedId + ", bounds=" + anchorBounds + " filter=" - + filterText); + Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + filterText); } final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI)) .setPackageName(packageName) @@ -179,7 +150,7 @@ public final class AutoFillUI { } hideAllUiThread(); mFillUi = new FillUi(mContext, response, focusedId, - mWindowToken, anchorBounds, filterText, new FillUi.Callback() { + filterText, new FillUi.Callback() { @Override public void onResponsePicked(FillResponse response) { log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL); @@ -211,8 +182,22 @@ public final class AutoFillUI { } mMetricsLogger.write(log); } + + @Override + public void requestShowFillUi(int width, int height, + IAutofillWindowPresenter windowPresenter) { + if (mCallback != null) { + mCallback.requestShowFillUi(focusedId, width, height, windowPresenter); + } + } + + @Override + public void requestHideFillUi() { + if (mCallback != null) { + mCallback.requestHideFillUi(focusedId); + } + } }); - mCallback.onEvent(focusedId, EVENT_INPUT_SHOWN); }); } 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 98a02f2aa0ca..d38fb9664124 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -18,14 +18,10 @@ package com.android.server.autofill.ui; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.graphics.PixelFormat; -import android.graphics.Point; import android.graphics.Rect; -import android.os.IBinder; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; import android.util.Slog; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -34,11 +30,13 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import android.view.autofill.IAutofillWindowPresenter; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.RemoteViews; import com.android.internal.R; +import com.android.server.UiThread; import libcore.util.Objects; import java.io.PrintWriter; @@ -54,9 +52,13 @@ final class FillUi { void onDatasetPicked(@NonNull Dataset dataset); void onCanceled(); void onDestroy(); + void requestShowFillUi(int width, int height, + IAutofillWindowPresenter windowPresenter); + void requestHideFillUi(); } - private final Rect mAnchorBounds = new Rect(); + private final @NonNull AutofillWindowPresenter mWindowPresenter = + new AutofillWindowPresenter(); private final @NonNull AnchoredWindow mWindow; @@ -75,10 +77,8 @@ final class FillUi { private boolean mDestroyed; FillUi(@NonNull Context context, @NonNull FillResponse response, - @NonNull AutofillId focusedViewId, @NonNull IBinder windowToken, - @NonNull Rect anchorBounds, @Nullable String filterText, + @NonNull AutofillId focusedViewId, @NonNull @Nullable String filterText, @NonNull Callback callback) { - mAnchorBounds.set(anchorBounds); mCallback = callback; mAccessibilityTitle = context.getString(R.string.autofill_picker_accessibility_title); @@ -104,8 +104,8 @@ final class FillUi { mContentWidth = content.getMeasuredWidth(); mContentHeight = content.getMeasuredHeight(); - mWindow = new AnchoredWindow(windowToken, content); - mWindow.show(mContentWidth, mContentHeight, mAnchorBounds); + mWindow = new AnchoredWindow(content); + mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter); } else { final int datasetCount = response.getDatasets().size(); final ArrayList<ViewItem> items = new ArrayList<>(datasetCount); @@ -153,15 +153,7 @@ final class FillUi { } applyNewFilterText(); - mWindow = new AnchoredWindow(windowToken, mListView); - } - } - - public void update(@NonNull Rect anchorBounds) { - throwIfDestroyed(); - if (!mAnchorBounds.equals(anchorBounds)) { - mAnchorBounds.set(anchorBounds); - mWindow.show(mContentWidth, mContentHeight, anchorBounds); + mWindow = new AnchoredWindow(mListView); } } @@ -171,10 +163,10 @@ final class FillUi { return; } if (count <= 0) { - mWindow.hide(); + mCallback.requestHideFillUi(); } else { if (updateContentSize()) { - mWindow.show(mContentWidth, mContentHeight, mAnchorBounds); + mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter); } if (mAdapter.getCount() > VISIBLE_OPTIONS_MAX_COUNT) { mListView.setVerticalScrollBarEnabled(true); @@ -209,7 +201,7 @@ final class FillUi { public void destroy() { throwIfDestroyed(); mCallback.onDestroy(); - mWindow.hide(); + mCallback.requestHideFillUi(); mDestroyed = true; } @@ -285,33 +277,61 @@ final class FillUi { } } - final class AnchoredWindow implements View.OnTouchListener { - private final Point mTempPoint = new Point(); + private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub { + @Override + public void show(WindowManager.LayoutParams p, Rect transitionEpicenter, + boolean fitsSystemWindows, int layoutDirection) { + UiThread.getHandler().post(() -> mWindow.show(p)); + } - private final WindowManager mWm; + @Override + public void hide(Rect transitionEpicenter) { + UiThread.getHandler().post(mWindow::hide); + } + } - private final IBinder mActivityToken; + final class AnchoredWindow implements View.OnTouchListener { + private final WindowManager mWm; private final View mContentView; + private boolean mShowing; /** * Constructor. * - * @param activityToken token to pass to window manager * @param contentView content of the window */ - AnchoredWindow(IBinder activityToken, View contentView) { + AnchoredWindow(View contentView) { mWm = contentView.getContext().getSystemService(WindowManager.class); - mActivityToken = activityToken; mContentView = contentView; } /** + * Shows the window. + */ + public void show(WindowManager.LayoutParams params) { + try { + if (!mShowing) { + params.accessibilityTitle = mAccessibilityTitle; + mWm.addView(mContentView, params); + mContentView.setOnTouchListener(this); + mShowing = true; + } else { + mWm.updateViewLayout(mContentView, params); + } + } catch (WindowManager.BadTokenException e) { + Slog.i(TAG, "Filed with with token " + params.token + " gone."); + mCallback.onDestroy(); + } + } + + /** * Hides the window. */ void hide() { - if (mContentView.isAttachedToWindow()) { + if (mShowing) { mContentView.setOnTouchListener(null); mWm.removeView(mContentView); + mShowing = false; } } @@ -324,82 +344,9 @@ final class FillUi { } return false; } - - 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"); - params.token = mActivityToken; - params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; - params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; - params.accessibilityTitle = mAccessibilityTitle; - - mWm.getDefaultDisplay().getRealSize(mTempPoint); - final int screenWidth = mTempPoint.x; - final int screenHeight = mTempPoint.y; - - // Try to place the window at the start of the anchor view if - // there is space to fit the content, otherwise fit as much of - // the window as possible moving it to the left using all available - // screen width. - params.x = Math.min(anchorBounds.left, Math.max(screenWidth - desiredWidth, 0)); - params.width = Math.min(screenWidth, desiredWidth); - - // Try to fit below using all available space with top-start gravity - // and if that fails try to fit above using all available space with - // bottom-start gravity. - final int verticalSpaceBelow = screenHeight - anchorBounds.bottom; - if (desiredHeight <= verticalSpaceBelow) { - // Fits below bounds. - params.height = desiredHeight; - params.gravity = Gravity.TOP | Gravity.START; - params.y = anchorBounds.bottom; - } else { - final int verticalSpaceAbove = anchorBounds.top; - if (desiredHeight <= verticalSpaceAbove) { - // Fits above bounds. - params.height = desiredHeight; - params.gravity = Gravity.BOTTOM | Gravity.START; - params.y = anchorBounds.top + desiredHeight; - } else { - // Pick above/below based on which has the most space. - if (verticalSpaceBelow >= verticalSpaceAbove) { - params.height = verticalSpaceBelow; - params.gravity = Gravity.TOP | Gravity.START; - params.y = anchorBounds.bottom; - } else { - params.height = verticalSpaceAbove; - params.gravity = Gravity.BOTTOM | Gravity.START; - params.y = anchorBounds.top + desiredHeight; - } - } - } - - if (!mContentView.isAttachedToWindow()) { - mWm.addView(mContentView, params); - mContentView.setOnTouchListener(this); - } else { - mWm.updateViewLayout(mContentView, params); - } - } } public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("mAnchorBounds: "); pw.println(mAnchorBounds); pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null); pw.print(prefix); pw.print("mListView: "); pw.println(mListView); pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 30d06db93330..037804e0c5e9 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -222,12 +222,27 @@ public class BackupManagerService { // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection // 3 : introduced "_meta" metadata file; no other format change per se // 4 : added support for new device-encrypted storage locations - static final int BACKUP_FILE_VERSION = 4; + // 5 : added support for key-value packages + static final int BACKUP_FILE_VERSION = 5; static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n"; static final int BACKUP_PW_FILE_VERSION = 2; static final String BACKUP_METADATA_FILENAME = "_meta"; static final int BACKUP_METADATA_VERSION = 1; static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01; + + static final int TAR_HEADER_LONG_RADIX = 8; + static final int TAR_HEADER_OFFSET_FILESIZE = 124; + static final int TAR_HEADER_LENGTH_FILESIZE = 12; + static final int TAR_HEADER_OFFSET_MODTIME = 136; + static final int TAR_HEADER_LENGTH_MODTIME = 12; + static final int TAR_HEADER_OFFSET_MODE = 100; + static final int TAR_HEADER_LENGTH_MODE = 8; + static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345; + static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155; + static final int TAR_HEADER_OFFSET_PATH = 0; + static final int TAR_HEADER_LENGTH_PATH = 100; + static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156; + static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production static final String SETTINGS_PACKAGE = "com.android.providers.settings"; @@ -553,19 +568,20 @@ public class BackupManagerService { } } - class FullParams { + // Parameters used by adbBackup() and adbRestore() + class AdbParams { public ParcelFileDescriptor fd; public final AtomicBoolean latch; public IFullBackupRestoreObserver observer; public String curPassword; // filled in by the confirmation step public String encryptPassword; - FullParams() { + AdbParams() { latch = new AtomicBoolean(false); } } - class FullBackupParams extends FullParams { + class AdbBackupParams extends AdbParams { public boolean includeApks; public boolean includeObbs; public boolean includeShared; @@ -573,11 +589,12 @@ public class BackupManagerService { public boolean allApps; public boolean includeSystem; public boolean doCompress; + public boolean includeKeyValue; public String[] packages; - FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs, + AdbBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs, boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem, - boolean compress, String[] pkgList) { + boolean compress, boolean doKeyValue, String[] pkgList) { fd = output; includeApks = saveApks; includeObbs = saveObbs; @@ -586,12 +603,13 @@ public class BackupManagerService { allApps = doAllApps; includeSystem = doSystem; doCompress = compress; + includeKeyValue = doKeyValue; packages = pkgList; } } - class FullRestoreParams extends FullParams { - FullRestoreParams(ParcelFileDescriptor input) { + class AdbRestoreParams extends AdbParams { + AdbRestoreParams(ParcelFileDescriptor input) { fd = input; } } @@ -627,10 +645,10 @@ public class BackupManagerService { static final int OP_TIMEOUT = -1; // Waiting for backup agent to respond during backup operation. - private static final int OP_TYPE_BACKUP_WAIT = 0; + static final int OP_TYPE_BACKUP_WAIT = 0; // Waiting for backup agent to respond during restore operation. - private static final int OP_TYPE_RESTORE_WAIT = 1; + static final int OP_TYPE_RESTORE_WAIT = 1; // An entire backup operation spanning multiple packages. private static final int OP_TYPE_BACKUP = 2; @@ -672,7 +690,7 @@ public class BackupManagerService { final Object mCurrentOpLock = new Object(); final Random mTokenGenerator = new Random(); - final SparseArray<FullParams> mFullConfirmations = new SparseArray<FullParams>(); + final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<AdbParams>(); // Where we keep our journal files and other bookkeeping File mBaseStateDir; @@ -791,15 +809,9 @@ public class BackupManagerService { } /* adb backup: is this app only capable of doing key/value? We say otherwise if - * the app has a backup agent and does not say fullBackupOnly, *unless* it - * is a package that we know _a priori_ explicitly supports both key/value and - * full-data backup. + * the app has a backup agent and does not say fullBackupOnly, */ private static boolean appIsKeyValueOnly(PackageInfo pkg) { - if ("com.android.providers.settings".equals(pkg.packageName)) { - return false; - } - return !appGetsFullBackup(pkg); } @@ -912,13 +924,12 @@ public class BackupManagerService { { // TODO: refactor full backup to be a looper-based state machine // similar to normal backup/restore. - FullBackupParams params = (FullBackupParams)msg.obj; + AdbBackupParams params = (AdbBackupParams)msg.obj; PerformAdbBackupTask task = new PerformAdbBackupTask(params.fd, params.observer, params.includeApks, params.includeObbs, - params.includeShared, params.doWidgets, - params.curPassword, params.encryptPassword, - params.allApps, params.includeSystem, params.doCompress, - params.packages, params.latch); + params.includeShared, params.doWidgets, params.curPassword, + params.encryptPassword, params.allApps, params.includeSystem, + params.doCompress, params.includeKeyValue, params.packages, params.latch); (new Thread(task, "adb-backup")).start(); break; } @@ -963,7 +974,7 @@ public class BackupManagerService { { // TODO: refactor full restore to be a looper-based state machine // similar to normal backup/restore. - FullRestoreParams params = (FullRestoreParams)msg.obj; + AdbRestoreParams params = (AdbRestoreParams)msg.obj; PerformAdbRestoreTask task = new PerformAdbRestoreTask(params.fd, params.curPassword, params.encryptPassword, params.observer, params.latch); @@ -1071,16 +1082,16 @@ public class BackupManagerService { case MSG_FULL_CONFIRMATION_TIMEOUT: { - synchronized (mFullConfirmations) { - FullParams params = mFullConfirmations.get(msg.arg1); + synchronized (mAdbBackupRestoreConfirmations) { + AdbParams params = mAdbBackupRestoreConfirmations.get(msg.arg1); if (params != null) { Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation"); // Release the waiter; timeout == completion - signalFullBackupRestoreCompletion(params); + signalAdbBackupRestoreCompletion(params); // Remove the token from the set - mFullConfirmations.delete(msg.arg1); + mAdbBackupRestoreConfirmations.delete(msg.arg1); // Report a timeout to the observer, if any if (params.observer != null) { @@ -3719,7 +3730,7 @@ public class BackupManagerService { } - private void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out) + static void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out) throws IOException { // We do not take close() responsibility for the pipe FD FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor()); @@ -3822,7 +3833,7 @@ public class BackupManagerService { if (mWriteManifest) { final boolean writeWidgetData = mWidgetData != null; if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName); - writeAppManifest(mPackage, mManifestFile, mSendApk, writeWidgetData); + writeAppManifest(mPackage, mPackageManager, mManifestFile, mSendApk, writeWidgetData); FullBackup.backupToTar(mPackage.packageName, null, null, mFilesDir.getAbsolutePath(), mManifestFile.getAbsolutePath(), @@ -4006,52 +4017,6 @@ public class BackupManagerService { } } - private void writeAppManifest(PackageInfo pkg, File manifestFile, - boolean withApk, boolean withWidgets) throws IOException { - // Manifest format. All data are strings ending in LF: - // BACKUP_MANIFEST_VERSION, currently 1 - // - // Version 1: - // package name - // package's versionCode - // platform versionCode - // getInstallerPackageName() for this package (maybe empty) - // boolean: "1" if archive includes .apk; any other string means not - // number of signatures == N - // N*: signature byte array in ascii format per Signature.toCharsString() - StringBuilder builder = new StringBuilder(4096); - StringBuilderPrinter printer = new StringBuilderPrinter(builder); - - printer.println(Integer.toString(BACKUP_MANIFEST_VERSION)); - printer.println(pkg.packageName); - printer.println(Integer.toString(pkg.versionCode)); - printer.println(Integer.toString(Build.VERSION.SDK_INT)); - - String installerName = mPackageManager.getInstallerPackageName(pkg.packageName); - printer.println((installerName != null) ? installerName : ""); - - printer.println(withApk ? "1" : "0"); - if (pkg.signatures == null) { - printer.println("0"); - } else { - printer.println(Integer.toString(pkg.signatures.length)); - for (Signature sig : pkg.signatures) { - printer.println(sig.toCharsString()); - } - } - - FileOutputStream outstream = new FileOutputStream(manifestFile); - outstream.write(builder.toString().getBytes()); - outstream.close(); - - // We want the manifest block in the archive stream to be idempotent: - // each time we generate a backup stream for the app, we want the manifest - // block to be identical. The underlying tar mechanism sees it as a file, - // though, and will propagate its mtime, causing the tar header to vary. - // Avoid this problem by pinning the mtime to zero. - manifestFile.setLastModified(0); - } - // Widget metadata format. All header entries are strings ending in LF: // // Version 1 header: @@ -4100,6 +4065,52 @@ public class BackupManagerService { } } + static void writeAppManifest(PackageInfo pkg, PackageManager packageManager, File manifestFile, + boolean withApk, boolean withWidgets) throws IOException { + // Manifest format. All data are strings ending in LF: + // BACKUP_MANIFEST_VERSION, currently 1 + // + // Version 1: + // package name + // package's versionCode + // platform versionCode + // getInstallerPackageName() for this package (maybe empty) + // boolean: "1" if archive includes .apk; any other string means not + // number of signatures == N + // N*: signature byte array in ascii format per Signature.toCharsString() + StringBuilder builder = new StringBuilder(4096); + StringBuilderPrinter printer = new StringBuilderPrinter(builder); + + printer.println(Integer.toString(BACKUP_MANIFEST_VERSION)); + printer.println(pkg.packageName); + printer.println(Integer.toString(pkg.versionCode)); + printer.println(Integer.toString(Build.VERSION.SDK_INT)); + + String installerName = packageManager.getInstallerPackageName(pkg.packageName); + printer.println((installerName != null) ? installerName : ""); + + printer.println(withApk ? "1" : "0"); + if (pkg.signatures == null) { + printer.println("0"); + } else { + printer.println(Integer.toString(pkg.signatures.length)); + for (Signature sig : pkg.signatures) { + printer.println(sig.toCharsString()); + } + } + + FileOutputStream outstream = new FileOutputStream(manifestFile); + outstream.write(builder.toString().getBytes()); + outstream.close(); + + // We want the manifest block in the archive stream to be idempotent: + // each time we generate a backup stream for the app, we want the manifest + // block to be identical. The underlying tar mechanism sees it as a file, + // though, and will propagate its mtime, causing the tar header to vary. + // Avoid this problem by pinning the mtime to zero. + manifestFile.setLastModified(0); + } + // Generic driver skeleton for full backup operations abstract class FullBackupTask implements Runnable { IFullBackupRestoreObserver mObserver; @@ -4172,6 +4183,7 @@ public class BackupManagerService { boolean mAllApps; boolean mIncludeSystem; boolean mCompress; + boolean mKeyValue; ArrayList<String> mPackages; PackageInfo mCurrentTarget; String mCurrentPassword; @@ -4179,9 +4191,9 @@ public class BackupManagerService { private final int mCurrentOpToken; PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, - boolean includeApks, boolean includeObbs, boolean includeShared, - boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps, - boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) { + boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, + String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem, + boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) { super(observer); mCurrentOpToken = generateToken(); mLatch = latch; @@ -4210,6 +4222,7 @@ public class BackupManagerService { Slog.w(TAG, "Encrypting backup with passphrase=" + mEncryptPassword); } mCompress = doCompress; + mKeyValue = doKeyValue; } void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) { @@ -4309,7 +4322,8 @@ public class BackupManagerService { @Override public void run() { - Slog.i(TAG, "--- Performing full-dataset adb backup ---"); + String includeKeyValue = mKeyValue ? ", including key-value backups" : ""; + Slog.i(TAG, "--- Performing adb backup" + includeKeyValue + " ---"); TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>(); FullBackupObbConnection obbConnection = new FullBackupObbConnection(); @@ -4361,14 +4375,26 @@ public class BackupManagerService { // Now we cull any inapplicable / inappropriate packages from the set. This // includes the special shared-storage agent package; we handle that one - // explicitly at the end of the backup pass. + // explicitly at the end of the backup pass. Packages supporting key-value backup are + // added to their own queue, and handled after packages supporting fullbackup. + ArrayList<PackageInfo> keyValueBackupQueue = new ArrayList<>(); Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator(); while (iter.hasNext()) { PackageInfo pkg = iter.next().getValue(); if (!appIsEligibleForBackup(pkg.applicationInfo) - || appIsStopped(pkg.applicationInfo) - || appIsKeyValueOnly(pkg)) { + || appIsStopped(pkg.applicationInfo)) { + iter.remove(); + if (DEBUG) { + Slog.i(TAG, "Package " + pkg.packageName + + " is not eligible for backup, removing."); + } + } else if (appIsKeyValueOnly(pkg)) { iter.remove(); + if (DEBUG) { + Slog.i(TAG, "Package " + pkg.packageName + + " is key-value."); + } + keyValueBackupQueue.add(pkg); } } @@ -4402,7 +4428,7 @@ public class BackupManagerService { // final '\n'. // // line 1: "ANDROID BACKUP" - // line 2: backup file format version, currently "2" + // line 2: backup file format version, currently "5" // line 3: compressed? "0" if not compressed, "1" if compressed. // line 4: name of encryption algorithm [currently only "none" or "AES-256"] // @@ -4462,10 +4488,14 @@ public class BackupManagerService { } } - // Now actually run the constructed backup sequence + // Now actually run the constructed backup sequence for full backup int N = backupQueue.size(); for (int i = 0; i < N; i++) { pkg = backupQueue.get(i); + if (DEBUG) { + Slog.i(TAG,"--- Performing full backup for package " + pkg.packageName + + " ---"); + } final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); @@ -4485,6 +4515,21 @@ public class BackupManagerService { } } } + // And for key-value backup if enabled + if (mKeyValue) { + for (PackageInfo keyValuePackage : keyValueBackupQueue) { + if (DEBUG) { + Slog.i(TAG, "--- Performing key-value backup for package " + + keyValuePackage.packageName + " ---"); + } + KeyValueAdbBackupEngine kvBackupEngine = + new KeyValueAdbBackupEngine(out, keyValuePackage, + BackupManagerService.this, + mPackageManager, mBaseStateDir, mDataDir); + sendOnBackupPackage(keyValuePackage.packageName); + kvBackupEngine.backupOnePackage(); + } + } // Done! finalizeBackup(out); @@ -6693,19 +6738,24 @@ public class BackupManagerService { try { // okay, presume we're okay, and extract the various metadata info = new FileMetadata(); - info.size = extractRadix(block, 124, 12, 8); - info.mtime = extractRadix(block, 136, 12, 8); - info.mode = extractRadix(block, 100, 8, 8); - - info.path = extractString(block, 345, 155); // prefix - String path = extractString(block, 0, 100); + info.size = extractRadix(block, TAR_HEADER_OFFSET_FILESIZE, + TAR_HEADER_LENGTH_FILESIZE, TAR_HEADER_LONG_RADIX); + info.mtime = extractRadix(block, TAR_HEADER_OFFSET_MODTIME, + TAR_HEADER_LENGTH_MODTIME, TAR_HEADER_LONG_RADIX); + info.mode = extractRadix(block, TAR_HEADER_OFFSET_MODE, + TAR_HEADER_LENGTH_MODE, TAR_HEADER_LONG_RADIX); + + info.path = extractString(block, TAR_HEADER_OFFSET_PATH_PREFIX, + TAR_HEADER_LENGTH_PATH_PREFIX); + String path = extractString(block, TAR_HEADER_OFFSET_PATH, + TAR_HEADER_LENGTH_PATH); if (path.length() > 0) { if (info.path.length() > 0) info.path += '/'; info.path += path; } // tar link indicator field: 1 byte at offset 156 in the header. - int typeChar = block[156]; + int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR]; if (typeChar == 'x') { // pax extended header, so we need to read that gotHeader = readPaxExtendedHeader(instream, info); @@ -6716,7 +6766,7 @@ public class BackupManagerService { } if (!gotHeader) throw new IOException("Bad or missing pax header"); - typeChar = block[156]; + typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR]; } switch (typeChar) { @@ -7037,6 +7087,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF IFullBackupRestoreObserver mObserver; AtomicBoolean mLatchObject; IBackupAgent mAgent; + PackageManagerBackupAgent mPackageManagerBackupAgent; String mAgentPackage; ApplicationInfo mTargetApp; FullBackupObbConnection mObbConnection = null; @@ -7088,6 +7139,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF mObserver = observer; mLatchObject = latch; mAgent = null; + mPackageManagerBackupAgent = new PackageManagerBackupAgent(mPackageManager); mAgentPackage = null; mTargetApp = null; mObbConnection = new FullBackupObbConnection(); @@ -7505,14 +7557,21 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF long toCopy = info.size; final int token = generateToken(); try { - prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null, + prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, null, OP_TYPE_RESTORE_WAIT); - if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) { + if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) { if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg + " : " + info.path); mObbConnection.restoreObbFile(pkg, mPipes[0], info.size, info.type, info.path, info.mode, info.mtime, token, mBackupManagerBinder); + } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) { + if (DEBUG) Slog.d(TAG, "Restoring key-value file for " + pkg + + " : " + info.path); + KeyValueAdbRestoreEngine restoreEngine = + new KeyValueAdbRestoreEngine(BackupManagerService.this, + mDataDir, info, mPipes[0], mAgent, token); + new Thread(restoreEngine, "restore-key-value-runner").start(); } else { if (DEBUG) Slog.d(TAG, "Invoking agent to restore file " + info.path); @@ -8100,6 +8159,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF Slog.i(TAG, b.toString()); } } + // Consume a tar file header block [sequence] and accumulate the relevant metadata FileMetadata readTarHeaders(InputStream instream) throws IOException { byte[] block = new byte[512]; @@ -9920,16 +9980,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0); } - // Run a *full* backup pass for the given packages, writing the resulting data stream + // Run a backup pass for the given packages, writing the resulting data stream // to the supplied file descriptor. This method is synchronous and does not return // to the caller until the backup has been completed. // // This is the variant used by 'adb backup'; it requires on-screen confirmation // by the user because it can be used to offload data over untrusted USB. - public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, - boolean includeObbs, boolean includeShared, boolean doWidgets, - boolean doAllApps, boolean includeSystem, boolean compress, String[] pkgList) { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup"); + public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, + boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem, + boolean compress, boolean doKeyValue, String[] pkgList) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup"); final int callingUserHandle = UserHandle.getCallingUserId(); // TODO: http://b/22388012 @@ -9954,27 +10014,28 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF try { // Doesn't make sense to do a full backup prior to setup if (!deviceIsProvisioned()) { - Slog.i(TAG, "Full backup not supported before setup"); + Slog.i(TAG, "Backup not supported before setup"); return; } - if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks - + " obb=" + includeObbs + " shared=" + includeShared + " all=" + doAllApps - + " system=" + includeSystem + " pkgs=" + pkgList); - Slog.i(TAG, "Beginning full backup..."); + if (DEBUG) Slog.v(TAG, "Requesting backup: apks=" + includeApks + " obb=" + includeObbs + + " shared=" + includeShared + " all=" + doAllApps + " system=" + + includeSystem + " includekeyvalue=" + doKeyValue + " pkgs=" + pkgList); + Slog.i(TAG, "Beginning adb backup..."); - FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs, - includeShared, doWidgets, doAllApps, includeSystem, compress, pkgList); + AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs, + includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue, + pkgList); final int token = generateToken(); - synchronized (mFullConfirmations) { - mFullConfirmations.put(token, params); + synchronized (mAdbBackupRestoreConfirmations) { + mAdbBackupRestoreConfirmations.put(token, params); } // start up the confirmation UI if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token); if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) { - Slog.e(TAG, "Unable to launch full backup confirmation"); - mFullConfirmations.delete(token); + Slog.e(TAG, "Unable to launch backup confirmation UI"); + mAdbBackupRestoreConfirmations.delete(token); return; } @@ -9987,7 +10048,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF startConfirmationTimeout(token, params); // wait for the backup to be performed - if (DEBUG) Slog.d(TAG, "Waiting for full backup completion..."); + if (DEBUG) Slog.d(TAG, "Waiting for backup completion..."); waitForCompletion(params); } finally { try { @@ -9996,7 +10057,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // just eat it } Binder.restoreCallingIdentity(oldId); - Slog.d(TAG, "Full backup processing complete."); + Slog.d(TAG, "Adb backup processing complete."); } } @@ -10049,8 +10110,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } - public void fullRestore(ParcelFileDescriptor fd) { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore"); + public void adbRestore(ParcelFileDescriptor fd) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore"); final int callingUserHandle = UserHandle.getCallingUserId(); // TODO: http://b/22388012 @@ -10068,19 +10129,19 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF return; } - Slog.i(TAG, "Beginning full restore..."); + Slog.i(TAG, "Beginning restore..."); - FullRestoreParams params = new FullRestoreParams(fd); + AdbRestoreParams params = new AdbRestoreParams(fd); final int token = generateToken(); - synchronized (mFullConfirmations) { - mFullConfirmations.put(token, params); + synchronized (mAdbBackupRestoreConfirmations) { + mAdbBackupRestoreConfirmations.put(token, params); } // start up the confirmation UI if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token); if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) { - Slog.e(TAG, "Unable to launch full restore confirmation"); - mFullConfirmations.delete(token); + Slog.e(TAG, "Unable to launch restore confirmation"); + mAdbBackupRestoreConfirmations.delete(token); return; } @@ -10093,16 +10154,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF startConfirmationTimeout(token, params); // wait for the restore to be performed - if (DEBUG) Slog.d(TAG, "Waiting for full restore completion..."); + if (DEBUG) Slog.d(TAG, "Waiting for restore completion..."); waitForCompletion(params); } finally { try { fd.close(); } catch (IOException e) { - Slog.w(TAG, "Error trying to close fd after full restore: " + e); + Slog.w(TAG, "Error trying to close fd after adb restore: " + e); } Binder.restoreCallingIdentity(oldId); - Slog.i(TAG, "Full restore processing complete."); + Slog.i(TAG, "adb restore processing complete."); } } @@ -10120,7 +10181,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF return true; } - void startConfirmationTimeout(int token, FullParams params) { + void startConfirmationTimeout(int token, AdbParams params) { if (MORE_DEBUG) Slog.d(TAG, "Posting conf timeout msg after " + TIMEOUT_FULL_CONFIRMATION + " millis"); Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT, @@ -10128,7 +10189,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION); } - void waitForCompletion(FullParams params) { + void waitForCompletion(AdbParams params) { synchronized (params.latch) { while (params.latch.get() == false) { try { @@ -10138,7 +10199,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } - void signalFullBackupRestoreCompletion(FullParams params) { + void signalAdbBackupRestoreCompletion(AdbParams params) { synchronized (params.latch) { params.latch.set(true); params.latch.notifyAll(); @@ -10147,27 +10208,27 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // Confirm that the previously-requested full backup/restore operation can proceed. This // is used to require a user-facing disclosure about the operation. - public void acknowledgeFullBackupOrRestore(int token, boolean allow, + public void acknowledgeAdbBackupOrRestore(int token, boolean allow, String curPassword, String encPpassword, IFullBackupRestoreObserver observer) { - if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token + if (DEBUG) Slog.d(TAG, "acknowledgeAdbBackupOrRestore : token=" + token + " allow=" + allow); // TODO: possibly require not just this signature-only permission, but even // require that the specific designated confirmation-UI app uid is the caller? - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeFullBackupOrRestore"); + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeAdbBackupOrRestore"); long oldId = Binder.clearCallingIdentity(); try { - FullParams params; - synchronized (mFullConfirmations) { - params = mFullConfirmations.get(token); + AdbParams params; + synchronized (mAdbBackupRestoreConfirmations) { + params = mAdbBackupRestoreConfirmations.get(token); if (params != null) { mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params); - mFullConfirmations.delete(token); + mAdbBackupRestoreConfirmations.delete(token); if (allow) { - final int verb = params instanceof FullBackupParams + final int verb = params instanceof AdbBackupParams ? MSG_RUN_ADB_BACKUP : MSG_RUN_ADB_RESTORE; @@ -10183,7 +10244,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } else { Slog.w(TAG, "User rejected full backup/restore operation"); // indicate completion without having actually transferred any data - signalFullBackupRestoreCompletion(params); + signalAdbBackupRestoreCompletion(params); } } else { Slog.w(TAG, "Attempted to ack full backup/restore with invalid token"); diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java new file mode 100644 index 000000000000..cd137603b350 --- /dev/null +++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java @@ -0,0 +1,281 @@ +package com.android.server.backup; + +import static android.os.ParcelFileDescriptor.MODE_CREATE; +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; +import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; +import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT; +import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL; + +import android.app.ApplicationThreadConstants; +import android.app.IBackupAgent; +import android.app.backup.FullBackup; +import android.app.backup.FullBackupDataOutput; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.SELinux; +import android.util.Slog; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Used by BackupManagerService to perform adb backup for key-value packages. At the moment this + * class resembles what is done in the standard key-value code paths in BackupManagerService, and + * should be unified later. + * + * TODO: We should create unified backup/restore engines that can be used for both transport and + * adb backup/restore, and for fullbackup and key-value backup. + */ +class KeyValueAdbBackupEngine { + private static final String TAG = "KeyValueAdbBackupEngine"; + private static final boolean DEBUG = false; + + private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir"; + private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state"; + private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data"; + private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new"; + + private BackupManagerService mBackupManagerService; + private final PackageManager mPackageManager; + private final OutputStream mOutput; + private final PackageInfo mCurrentPackage; + private final File mDataDir; + private final File mStateDir; + private final File mBlankStateName; + private final File mBackupDataName; + private final File mNewStateName; + private final File mManifestFile; + private ParcelFileDescriptor mSavedState; + private ParcelFileDescriptor mBackupData; + private ParcelFileDescriptor mNewState; + + KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo, + BackupManagerService backupManagerService, PackageManager packageManager, + File baseStateDir, File dataDir) { + mOutput = output; + mCurrentPackage = packageInfo; + mBackupManagerService = backupManagerService; + mPackageManager = packageManager; + + mDataDir = dataDir; + mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME); + mStateDir.mkdirs(); + + String pkg = mCurrentPackage.packageName; + + mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME); + mBackupDataName = new File(mDataDir, + pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX); + mNewStateName = new File(mStateDir, + pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX); + + mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME); + } + + void backupOnePackage() throws IOException { + ApplicationInfo targetApp = mCurrentPackage.applicationInfo; + + try { + prepareBackupFiles(mCurrentPackage.packageName); + + IBackupAgent agent = bindToAgent(targetApp); + + if (agent == null) { + // We failed binding to the agent, so ignore this package + Slog.e(TAG, "Failed binding to BackupAgent for package " + + mCurrentPackage.packageName); + return; + } + + // We are bound to agent, initiate backup. + if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) { + // Backup failed, skip package. + Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName); + return; + } + + // Backup finished successfully. Copy the backup data to the output stream. + writeBackupData(); + } catch (FileNotFoundException e) { + Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName + + " will ignore package. " + e); + } finally { + // We are either done, failed or have timed out, so do cleanup and kill the agent. + cleanup(); + } + } + + private void prepareBackupFiles(String packageName) throws FileNotFoundException { + + // We pass a blank state to make sure we are getting the complete backup, not just an + // increment + mSavedState = ParcelFileDescriptor.open(mBlankStateName, + MODE_READ_ONLY | MODE_CREATE); // Make an empty file if necessary + + mBackupData = ParcelFileDescriptor.open(mBackupDataName, + MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); + + if (!SELinux.restorecon(mBackupDataName)) { + Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName); + } + + mNewState = ParcelFileDescriptor.open(mNewStateName, + MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); + } + + private IBackupAgent bindToAgent(ApplicationInfo targetApp) { + try { + return mBackupManagerService.bindToAgentSynchronous(targetApp, + ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL); + } catch (SecurityException e) { + Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName + + ". " + e); + return null; + } + } + + // Return true on backup success, false otherwise + private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) { + int token = mBackupManagerService.generateToken(); + try { + mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null, + OP_TYPE_BACKUP_WAIT); + + // Start backup and wait for BackupManagerService to get callback for success or timeout + agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token, + mBackupManagerService.mBackupManagerBinder); + if (!mBackupManagerService.waitUntilOperationComplete(token)) { + Slog.e(TAG, "Key-value backup failed on package " + packageName); + return false; + } + if (DEBUG) { + Slog.i(TAG, "Key-value backup success for package " + packageName); + } + return true; + } catch (RemoteException e) { + Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e); + return false; + } + } + + class KeyValueAdbBackupDataCopier implements Runnable { + private final PackageInfo mPackage; + private final ParcelFileDescriptor mPipe; + private final int mToken; + + KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe, + int token) + throws IOException { + mPackage = pack; + mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor()); + mToken = token; + } + + @Override + public void run() { + try { + FullBackupDataOutput output = new FullBackupDataOutput(mPipe); + + if (DEBUG) { + Slog.d(TAG, "Writing manifest for " + mPackage.packageName); + } + BackupManagerService.writeAppManifest( + mPackage, mPackageManager, mManifestFile, false, false); + FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null, + mDataDir.getAbsolutePath(), + mManifestFile.getAbsolutePath(), + output); + mManifestFile.delete(); + + if (DEBUG) { + Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName); + } + FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null, + mDataDir.getAbsolutePath(), + mBackupDataName.getAbsolutePath(), + output); + + // Write EOD marker + try { + FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor()); + byte[] buf = new byte[4]; + out.write(buf); + } catch (IOException e) { + Slog.e(TAG, "Unable to finalize backup stream!"); + } + + try { + mBackupManagerService.mBackupManagerBinder.opComplete(mToken, 0); + } catch (RemoteException e) { + // we'll time out anyway, so we're safe + } + + } catch (IOException e) { + Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e); + } finally { + IoUtils.closeQuietly(mPipe); + } + } + } + + private void writeBackupData() throws IOException { + + int token = mBackupManagerService.generateToken(); + + ParcelFileDescriptor[] pipes = null; + try { + pipes = ParcelFileDescriptor.createPipe(); + + mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null, + OP_TYPE_BACKUP_WAIT); + + // We will have to create a runnable that will read the manifest and backup data we + // created, such that we can pipe the data into mOutput. The reason we do this is that + // internally FullBackup.backupToTar is used, which will create the necessary file + // header, but will also chunk the data. The method routeSocketDataToOutput in + // BackupManagerService will dechunk the data, and append it to the TAR outputstream. + KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1], + token); + pipes[1].close(); // the runner has dup'd it + pipes[1] = null; + Thread t = new Thread(runner, "key-value-app-data-runner"); + t.start(); + + // Now pull data from the app and stuff it into the output + BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput); + + if (!mBackupManagerService.waitUntilOperationComplete(token)) { + Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName); + } else { + if (DEBUG) { + Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName); + } + } + } catch (IOException e) { + Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e); + } finally { + // flush after every package + mOutput.flush(); + if (pipes != null) { + IoUtils.closeQuietly(pipes[0]); + IoUtils.closeQuietly(pipes[1]); + } + } + } + + private void cleanup() { + mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo); + mBlankStateName.delete(); + mNewStateName.delete(); + mBackupDataName.delete(); + } +} diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java new file mode 100644 index 000000000000..6fb935569928 --- /dev/null +++ b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java @@ -0,0 +1,148 @@ +package com.android.server.backup; + +import static android.os.ParcelFileDescriptor.MODE_CREATE; +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; +import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; + +import android.app.IBackupAgent; +import android.app.backup.BackupDataInput; +import android.app.backup.BackupDataOutput; +import android.app.backup.FullBackup; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.backup.BackupManagerService.FileMetadata; +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Used by BackupManagerService to perform adb restore for key-value packages. At the moment this + * class resembles what is done in the standard key-value code paths in BackupManagerService, and + * should be unified later. + * + * TODO: We should create unified backup/restore engines that can be used for both transport and + * adb backup/restore, and for fullbackup and key-value backup. + */ +class KeyValueAdbRestoreEngine implements Runnable { + private static final String TAG = "KeyValueAdbRestoreEngine"; + private static final boolean DEBUG = false; + + private final BackupManagerService mBackupManagerService; + private final File mDataDir; + + FileMetadata mInfo; + BackupManagerService.PerformAdbRestoreTask mRestoreTask; + ParcelFileDescriptor mInFD; + IBackupAgent mAgent; + int mToken; + + KeyValueAdbRestoreEngine(BackupManagerService backupManagerService, File dataDir, + FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent, int token) { + mBackupManagerService = backupManagerService; + mDataDir = dataDir; + mInfo = info; + mInFD = inFD; + mAgent = agent; + mToken = token; + } + + @Override + public void run() { + try { + File restoreData = prepareRestoreData(mInfo, mInFD); + + // TODO: version ? + invokeAgentForAdbRestore(mAgent, mInfo, restoreData, 0); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private File prepareRestoreData(FileMetadata info, ParcelFileDescriptor inFD) throws IOException { + String pkg = info.packageName; + File restoreDataName = new File(mDataDir, pkg + ".restore"); + File sortedDataName = new File(mDataDir, pkg + ".sorted"); + + FullBackup.restoreFile(inFD, info.size, info.type, info.mode, info.mtime, restoreDataName); + + // Sort the keys, as the BackupAgent expect them to come in lexicographical order + sortKeyValueData(restoreDataName, sortedDataName); + return sortedDataName; + } + + private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData, + int versionCode) throws IOException { + String pkg = info.packageName; + File newStateName = new File(mDataDir, pkg + ".new"); + try { + ParcelFileDescriptor backupData = + ParcelFileDescriptor.open(restoreData, MODE_READ_ONLY); + ParcelFileDescriptor newState = ParcelFileDescriptor.open(newStateName, + MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); + + if (DEBUG) { + Slog.i(TAG, "Starting restore of package " + pkg + " for version code " + + versionCode); + } + agent.doRestore(backupData, versionCode, newState, mToken, + mBackupManagerService.mBackupManagerBinder); + } catch (IOException e) { + Slog.e(TAG, "Exception opening file. " + e); + } catch (RemoteException e) { + Slog.e(TAG, "Exception calling doRestore on agent: " + e); + } + } + + private void sortKeyValueData (File restoreData, File sortedData) throws IOException { + FileInputStream inputStream = null; + FileOutputStream outputStream = null; + try { + inputStream = new FileInputStream(restoreData); + outputStream = new FileOutputStream(sortedData); + BackupDataInput reader = new BackupDataInput(inputStream.getFD()); + BackupDataOutput writer = new BackupDataOutput(outputStream.getFD()); + copyKeysInLexicalOrder(reader, writer); + } finally { + if (inputStream != null) { + IoUtils.closeQuietly(inputStream); + } + if (outputStream != null) { + IoUtils.closeQuietly(outputStream); + } + } + } + + private void copyKeysInLexicalOrder(BackupDataInput in, BackupDataOutput out) + throws IOException { + Map<String, byte[]> data = new HashMap<>(); + while (in.readNextHeader()) { + String key = in.getKey(); + int size = in.getDataSize(); + if (size < 0) { + in.skipEntityData(); + continue; + } + byte[] value = new byte[size]; + in.readEntityData(value, 0, size); + data.put(key, value); + } + List<String> keys = new ArrayList<>(data.keySet()); + Collections.sort(keys); + for (String key : keys) { + byte[] value = data.get(key); + out.writeEntityHeader(key, value.length); + out.writeEntityData(value, value.length); + } + } +} diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 8855661b954f..c40f2ca0b5ac 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -227,14 +227,14 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, + public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, boolean allApps, - boolean allIncludesSystem, boolean doCompress, String[] packageNames) + boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.fullBackup(fd, includeApks, includeObbs, includeShared, doWidgets, - allApps, allIncludesSystem, doCompress, packageNames); + svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets, + allApps, allIncludesSystem, doCompress, doKeyValue, packageNames); } } @@ -247,10 +247,10 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void fullRestore(ParcelFileDescriptor fd) throws RemoteException { + public void adbRestore(ParcelFileDescriptor fd) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.fullRestore(fd); + svc.adbRestore(fd); } } @@ -260,7 +260,7 @@ public class Trampoline extends IBackupManager.Stub { throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.acknowledgeFullBackupOrRestore(token, allow, + svc.acknowledgeAdbBackupOrRestore(token, allow, curPassword, encryptionPassword, observer); } } diff --git a/services/core/Android.mk b/services/core/Android.mk index d312902382f9..e35a1711f72e 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -13,6 +13,7 @@ LOCAL_SRC_FILES += \ ../../../../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/NetworkManagementInternal.java b/services/core/java/com/android/server/NetworkManagementInternal.java new file mode 100644 index 000000000000..f53c454cb917 --- /dev/null +++ b/services/core/java/com/android/server/NetworkManagementInternal.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +/** + * NetworkManagement local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class NetworkManagementInternal { + /** + * Checks if network is restricted for {@param uid} as per the app idle state, device idle mode, + * battery and data saver modes. + */ + public abstract boolean isNetworkRestrictedForUid(int uid); +} diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index adc5e3343a32..74328c0b55d9 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -27,7 +27,9 @@ import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_TYPE_BLACKLIST; import static android.net.NetworkPolicyManager.FIREWALL_TYPE_WHITELIST; import static android.net.NetworkStats.SET_DEFAULT; @@ -90,6 +92,7 @@ import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.util.HexDump; @@ -222,7 +225,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); + /** + * If both locks need to be held, then they should be obtained in the order: + * first {@link #mQuotaLock} and then {@link #mRulesLock}. + */ private Object mQuotaLock = new Object(); + private Object mRulesLock = new Object(); /** Set of interfaces with active quotas. */ @GuardedBy("mQuotaLock") @@ -231,41 +239,41 @@ public class NetworkManagementService extends INetworkManagementService.Stub @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveAlerts = Maps.newHashMap(); /** Set of UIDs blacklisted on metered networks. */ - @GuardedBy("mQuotaLock") + @GuardedBy("mRulesLock") private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray(); /** Set of UIDs whitelisted on metered networks. */ - @GuardedBy("mQuotaLock") + @GuardedBy("mRulesLock") private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray(); /** Set of UIDs with cleartext penalties. */ @GuardedBy("mQuotaLock") private SparseIntArray mUidCleartextPolicy = new SparseIntArray(); /** Set of UIDs that are to be blocked/allowed by firewall controller. */ - @GuardedBy("mQuotaLock") + @GuardedBy("mRulesLock") private SparseIntArray mUidFirewallRules = new SparseIntArray(); /** * Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches * to application idles. */ - @GuardedBy("mQuotaLock") + @GuardedBy("mRulesLock") private SparseIntArray mUidFirewallStandbyRules = new SparseIntArray(); /** * Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches * to device idles. */ - @GuardedBy("mQuotaLock") + @GuardedBy("mRulesLock") private SparseIntArray mUidFirewallDozableRules = new SparseIntArray(); /** * Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches * to device on power-save mode. */ - @GuardedBy("mQuotaLock") + @GuardedBy("mRulesLock") private SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray(); /** Set of states for the child firewall chains. True if the chain is active. */ - @GuardedBy("mQuotaLock") + @GuardedBy("mRulesLock") final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray(); @GuardedBy("mQuotaLock") - private boolean mDataSaverMode; + private volatile boolean mDataSaverMode; private Object mIdleTimerLock = new Object(); /** Set of interfaces with active idle timers. */ @@ -321,6 +329,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); + + LocalServices.addService(NetworkManagementInternal.class, new LocalService()); + } + + @VisibleForTesting + NetworkManagementService() { + mConnector = null; + mContext = null; + mDaemonHandler = null; + mFgHandler = null; + mThread = null; } static NetworkManagementService create(Context context, String socket) @@ -502,21 +521,24 @@ public class NetworkManagementService extends INetworkManagementService.Stub } // Sync the state of the given chain with the native daemon. - private void syncFirewallChainLocked(int chain, SparseIntArray uidFirewallRules, String name) { - int size = uidFirewallRules.size(); - if (size > 0) { + private void syncFirewallChainLocked(int chain, String name) { + SparseIntArray rules; + synchronized (mRulesLock) { + final SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain); // Make a copy of the current rules, and then clear them. This is because - // setFirewallUidRuleInternal only pushes down rules to the native daemon if they are - // different from the current rules stored in the mUidFirewall*Rules array for the - // specified chain. If we don't clear the rules, setFirewallUidRuleInternal will do - // nothing. - final SparseIntArray rules = uidFirewallRules.clone(); + // setFirewallUidRuleInternal only pushes down rules to the native daemon if they + // are different from the current rules stored in the mUidFirewall*Rules array for + // the specified chain. If we don't clear the rules, setFirewallUidRuleInternal + // will do nothing. + rules = uidFirewallRules.clone(); uidFirewallRules.clear(); - + } + if (rules.size() > 0) { // Now push the rules. setFirewallUidRuleInternal will push each of these down to the // native daemon, and also add them to the mUidFirewall*Rules array for the specified // chain. - if (DBG) Slog.d(TAG, "Pushing " + size + " active firewall " + name + "UID rules"); + if (DBG) Slog.d(TAG, "Pushing " + rules.size() + " active firewall " + + name + "UID rules"); for (int i = 0; i < rules.size(); i++) { setFirewallUidRuleLocked(chain, rules.keyAt(i), rules.valueAt(i)); } @@ -597,22 +619,30 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } - size = mUidRejectOnMetered.size(); - if (size > 0) { - if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules"); - final SparseBooleanArray uidRejectOnQuota = mUidRejectOnMetered; - mUidRejectOnMetered = new SparseBooleanArray(); + SparseBooleanArray uidRejectOnQuota = null; + SparseBooleanArray uidAcceptOnQuota = null; + synchronized (mRulesLock) { + size = mUidRejectOnMetered.size(); + if (size > 0) { + if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules"); + uidRejectOnQuota = mUidRejectOnMetered; + mUidRejectOnMetered = new SparseBooleanArray(); + } + + size = mUidAllowOnMetered.size(); + if (size > 0) { + if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules"); + uidAcceptOnQuota = mUidAllowOnMetered; + mUidAllowOnMetered = new SparseBooleanArray(); + } + } + if (uidRejectOnQuota != null) { for (int i = 0; i < uidRejectOnQuota.size(); i++) { setUidMeteredNetworkBlacklist(uidRejectOnQuota.keyAt(i), uidRejectOnQuota.valueAt(i)); } } - - size = mUidAllowOnMetered.size(); - if (size > 0) { - if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules"); - final SparseBooleanArray uidAcceptOnQuota = mUidAllowOnMetered; - mUidAllowOnMetered = new SparseBooleanArray(); + if (uidAcceptOnQuota != null) { for (int i = 0; i < uidAcceptOnQuota.size(); i++) { setUidMeteredNetworkWhitelist(uidAcceptOnQuota.keyAt(i), uidAcceptOnQuota.valueAt(i)); @@ -631,20 +661,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled()); - syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, ""); - syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby "); - syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, mUidFirewallDozableRules, "dozable "); - syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, mUidFirewallPowerSaveRules, - "powersave "); + syncFirewallChainLocked(FIREWALL_CHAIN_NONE, ""); + syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby "); + syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable "); + syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave "); - if (mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY)) { - setFirewallChainEnabled(FIREWALL_CHAIN_STANDBY, true); - } - if (mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE)) { - setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true); - } - if (mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE)) { - setFirewallChainEnabled(FIREWALL_CHAIN_POWERSAVE, true); + final int[] chains = + {FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE}; + for (int chain : chains) { + if (getFirewallChainState(chain)) { + setFirewallChainEnabled(chain, true); + } } } } @@ -1602,8 +1629,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } - private void setUidOnMeteredNetworkList(SparseBooleanArray quotaList, int uid, - boolean blacklist, boolean enable) { + private void setUidOnMeteredNetworkList(int uid, boolean blacklist, boolean enable) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled @@ -1614,7 +1640,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub final String suffix = enable ? "add" : "remove"; synchronized (mQuotaLock) { - final boolean oldEnable = quotaList.get(uid, false); + boolean oldEnable; + SparseBooleanArray quotaList; + synchronized (mRulesLock) { + quotaList = blacklist ? mUidRejectOnMetered : mUidAllowOnMetered; + oldEnable = quotaList.get(uid, false); + } if (oldEnable == enable) { // TODO: eventually consider throwing return; @@ -1623,10 +1654,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth"); try { mConnector.execute("bandwidth", suffix + chain, uid); - if (enable) { - quotaList.put(uid, true); - } else { - quotaList.delete(uid); + synchronized (mRulesLock) { + if (enable) { + quotaList.put(uid, true); + } else { + quotaList.delete(uid); + } } } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); @@ -1638,12 +1671,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub @Override public void setUidMeteredNetworkBlacklist(int uid, boolean enable) { - setUidOnMeteredNetworkList(mUidRejectOnMetered, uid, true, enable); + setUidOnMeteredNetworkList(uid, true, enable); } @Override public void setUidMeteredNetworkWhitelist(int uid, boolean enable) { - setUidOnMeteredNetworkList(mUidAllowOnMetered, uid, false, enable); + setUidOnMeteredNetworkList(uid, false, enable); } @Override @@ -1934,7 +1967,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub // UID ranges whose sockets we won't touch. int[] exemptUids; - final SparseIntArray rules = getUidFirewallRules(chain); int numUids = 0; if (getFirewallType(chain) == FIREWALL_TYPE_WHITELIST) { @@ -1945,11 +1977,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub new UidRange(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE), }; // ... except for the UIDs that have allow rules. - exemptUids = new int[rules.size()]; - for (int i = 0; i < exemptUids.length; i++) { - if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) { - exemptUids[numUids] = rules.keyAt(i); - numUids++; + synchronized (mRulesLock) { + final SparseIntArray rules = getUidFirewallRulesLR(chain); + exemptUids = new int[rules.size()]; + for (int i = 0; i < exemptUids.length; i++) { + if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) { + exemptUids[numUids] = rules.keyAt(i); + numUids++; + } } } // Normally, whitelist chains only contain deny rules, so numUids == exemptUids.length. @@ -1964,12 +1999,15 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } else { // Close sockets for every UID that has a deny rule... - ranges = new UidRange[rules.size()]; - for (int i = 0; i < ranges.length; i++) { - if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) { - int uid = rules.keyAt(i); - ranges[numUids] = new UidRange(uid, uid); - numUids++; + synchronized (mRulesLock) { + final SparseIntArray rules = getUidFirewallRulesLR(chain); + ranges = new UidRange[rules.size()]; + for (int i = 0; i < ranges.length; i++) { + if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) { + int uid = rules.keyAt(i); + ranges[numUids] = new UidRange(uid, uid); + numUids++; + } } } // As above; usually numUids == ranges.length, but not always. @@ -1991,12 +2029,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void setFirewallChainEnabled(int chain, boolean enable) { enforceSystemUid(); synchronized (mQuotaLock) { - if (mFirewallChainStates.get(chain) == enable) { - // All is the same, nothing to do. This relies on the fact that netd has child - // chains default detached. - return; + synchronized (mRulesLock) { + if (getFirewallChainState(chain) == enable) { + // All is the same, nothing to do. This relies on the fact that netd has child + // chains default detached. + return; + } + setFirewallChainState(chain, enable); } - mFirewallChainStates.put(chain, enable); final String operation = enable ? "enable_chain" : "disable_chain"; final String chainName; @@ -2048,27 +2088,29 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void setFirewallUidRules(int chain, int[] uids, int[] rules) { enforceSystemUid(); synchronized (mQuotaLock) { - SparseIntArray uidFirewallRules = getUidFirewallRules(chain); - SparseIntArray newRules = new SparseIntArray(); - // apply new set of rules - for (int index = uids.length - 1; index >= 0; --index) { - int uid = uids[index]; - int rule = rules[index]; - updateFirewallUidRuleLocked(chain, uid, rule); - newRules.put(uid, rule); - } - // collect the rules to remove. - SparseIntArray rulesToRemove = new SparseIntArray(); - for (int index = uidFirewallRules.size() - 1; index >= 0; --index) { - int uid = uidFirewallRules.keyAt(index); - if (newRules.indexOfKey(uid) < 0) { - rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT); + synchronized (mRulesLock) { + SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain); + SparseIntArray newRules = new SparseIntArray(); + // apply new set of rules + for (int index = uids.length - 1; index >= 0; --index) { + int uid = uids[index]; + int rule = rules[index]; + updateFirewallUidRuleLocked(chain, uid, rule); + newRules.put(uid, rule); + } + // collect the rules to remove. + SparseIntArray rulesToRemove = new SparseIntArray(); + for (int index = uidFirewallRules.size() - 1; index >= 0; --index) { + int uid = uidFirewallRules.keyAt(index); + if (newRules.indexOfKey(uid) < 0) { + rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT); + } + } + // remove dead rules + for (int index = rulesToRemove.size() - 1; index >= 0; --index) { + int uid = rulesToRemove.keyAt(index); + updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT); } - } - // remove dead rules - for (int index = rulesToRemove.size() - 1; index >= 0; --index) { - int uid = rulesToRemove.keyAt(index); - updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT); } try { switch (chain) { @@ -2112,28 +2154,30 @@ public class NetworkManagementService extends INetworkManagementService.Stub // TODO: now that netd supports batching, NMS should not keep these data structures anymore... private boolean updateFirewallUidRuleLocked(int chain, int uid, int rule) { - SparseIntArray uidFirewallRules = getUidFirewallRules(chain); + synchronized (mRulesLock) { + SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain); - final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT); - if (DBG) { - Slog.d(TAG, "oldRule = " + oldUidFirewallRule - + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain); - } - if (oldUidFirewallRule == rule) { - if (DBG) Slog.d(TAG, "!!!!! Skipping change"); - // TODO: eventually consider throwing - return false; - } + final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT); + if (DBG) { + Slog.d(TAG, "oldRule = " + oldUidFirewallRule + + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain); + } + if (oldUidFirewallRule == rule) { + if (DBG) Slog.d(TAG, "!!!!! Skipping change"); + // TODO: eventually consider throwing + return false; + } - String ruleName = getFirewallRuleName(chain, rule); - String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule); + String ruleName = getFirewallRuleName(chain, rule); + String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule); - if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) { - uidFirewallRules.delete(uid); - } else { - uidFirewallRules.put(uid, rule); + if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) { + uidFirewallRules.delete(uid); + } else { + uidFirewallRules.put(uid, rule); + } + return !ruleName.equals(oldRuleName); } - return !ruleName.equals(oldRuleName); } private @NonNull String getFirewallRuleName(int chain, int rule) { @@ -2154,7 +2198,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub return ruleName; } - private @NonNull SparseIntArray getUidFirewallRules(int chain) { + private @NonNull SparseIntArray getUidFirewallRulesLR(int chain) { switch (chain) { case FIREWALL_CHAIN_STANDBY: return mUidFirewallStandbyRules; @@ -2284,29 +2328,25 @@ public class NetworkManagementService extends INetworkManagementService.Stub pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString()); pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString()); pw.print("Data saver mode: "); pw.println(mDataSaverMode); - dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered); - dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered); + synchronized (mRulesLock) { + dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered); + dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered); + } } - synchronized (mUidFirewallRules) { + synchronized (mRulesLock) { dumpUidFirewallRule(pw, "", mUidFirewallRules); - } - pw.print("UID firewall standby chain enabled: "); pw.println( - mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY)); - synchronized (mUidFirewallStandbyRules) { + pw.print("UID firewall standby chain enabled: "); pw.println( + getFirewallChainState(FIREWALL_CHAIN_STANDBY)); dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_STANDBY, mUidFirewallStandbyRules); - } - pw.print("UID firewall dozable chain enabled: "); pw.println( - mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE)); - synchronized (mUidFirewallDozableRules) { + pw.print("UID firewall dozable chain enabled: "); pw.println( + getFirewallChainState(FIREWALL_CHAIN_DOZABLE)); dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_DOZABLE, mUidFirewallDozableRules); - } - pw.println("UID firewall powersave chain enabled: " + - mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE)); - synchronized (mUidFirewallPowerSaveRules) { + pw.println("UID firewall powersave chain enabled: " + + getFirewallChainState(FIREWALL_CHAIN_POWERSAVE)); dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_POWERSAVE, mUidFirewallPowerSaveRules); } @@ -2576,4 +2616,99 @@ public class NetworkManagementService extends INetworkManagementService.Stub return failures; } + + private void setFirewallChainState(int chain, boolean state) { + synchronized (mRulesLock) { + mFirewallChainStates.put(chain, state); + } + } + + private boolean getFirewallChainState(int chain) { + synchronized (mRulesLock) { + return mFirewallChainStates.get(chain); + } + } + + @VisibleForTesting + class LocalService extends NetworkManagementInternal { + @Override + public boolean isNetworkRestrictedForUid(int uid) { + synchronized (mRulesLock) { + if (getFirewallChainState(FIREWALL_CHAIN_STANDBY) + && mUidFirewallStandbyRules.get(uid) == FIREWALL_RULE_DENY) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of app standby mode"); + return true; + } + if (getFirewallChainState(FIREWALL_CHAIN_DOZABLE) + && mUidFirewallDozableRules.get(uid) != FIREWALL_RULE_ALLOW) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of device idle mode"); + return true; + } + if (getFirewallChainState(FIREWALL_CHAIN_POWERSAVE) + && mUidFirewallPowerSaveRules.get(uid) != FIREWALL_RULE_ALLOW) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of power saver mode"); + return true; + } + if (mUidRejectOnMetered.get(uid)) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data" + + " in the background"); + return true; + } + if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); + return true; + } + return false; + } + } + } + + @VisibleForTesting + Injector getInjector() { + return new Injector(); + } + + @VisibleForTesting + class Injector { + void setDataSaverMode(boolean dataSaverMode) { + mDataSaverMode = dataSaverMode; + } + + void setFirewallChainState(int chain, boolean state) { + NetworkManagementService.this.setFirewallChainState(chain, state); + } + + void setFirewallRule(int chain, int uid, int rule) { + synchronized (mRulesLock) { + getUidFirewallRulesLR(chain).put(uid, rule); + } + } + + void setUidOnMeteredNetworkList(boolean blacklist, int uid, boolean enable) { + synchronized (mRulesLock) { + if (blacklist) { + mUidRejectOnMetered.put(uid, enable); + } else { + mUidAllowOnMetered.put(uid, enable); + } + } + } + + void reset() { + synchronized (mRulesLock) { + setDataSaverMode(false); + final int[] chains = { + FIREWALL_CHAIN_DOZABLE, + FIREWALL_CHAIN_STANDBY, + FIREWALL_CHAIN_POWERSAVE + }; + for (int chain : chains) { + setFirewallChainState(chain, false); + getUidFirewallRulesLR(chain).clear(); + } + mUidAllowOnMetered.clear(); + mUidRejectOnMetered.clear(); + } + } + } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 891a13bc6878..81219dab0932 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -715,7 +715,8 @@ class StorageManagerService extends IStorageManager.Stub final Intent intent = new Intent(action, Uri.fromFile(userVol.getPathFile())); intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); mContext.sendBroadcastAsUser(intent, userVol.getOwner()); } break; diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 5115fdecd1bf..e4f468732686 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -323,52 +323,6 @@ final class UiModeManagerService extends SystemService { } @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 34375125d7aa..3eb236e26a74 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -54,6 +54,7 @@ import static android.provider.Settings.Global.DEBUG_APP; import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL; +import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS; import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER; import static android.provider.Settings.System.FONT_SCALE; import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION; @@ -357,6 +358,7 @@ import com.android.server.DeviceIdleController; import com.android.server.IntentResolver; import com.android.server.LocalServices; import com.android.server.LockGuard; +import com.android.server.NetworkManagementInternal; import com.android.server.RescueParty; import com.android.server.ServiceThread; import com.android.server.SystemConfig; @@ -572,9 +574,9 @@ public class ActivityManagerService extends IActivityManager.Stub static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true; /** - * Indicates the maximum time spent waiting for the network rules to get updated. + * Default value for {@link Settings.Global#NETWORK_ACCESS_TIMEOUT_MS}. */ - private static final long WAIT_FOR_NETWORK_TIMEOUT_MS = 2000; // 2 sec + private static final long NETWORK_ACCESS_TIMEOUT_DEFAULT_MS = 0; // 0 sec /** * State indicating that there is no need for any blocking for network. @@ -753,6 +755,12 @@ public class ActivityManagerService extends IActivityManager.Stub final AppErrors mAppErrors; + /** + * Indicates the maximum time spent waiting for the network rules to get updated. + */ + @VisibleForTesting + long mWaitForNetworkTimeoutMs; + public boolean canShowErrorDialogs() { return mShowDialogs && !mSleeping && !mShuttingDown && !mKeyguardController.isKeyguardShowing(); @@ -13808,6 +13816,8 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0; final boolean forceResizable = Settings.Global.getInt( resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0; + final long waitForNetworkTimeoutMs = Settings.Global.getLong(resolver, + NETWORK_ACCESS_TIMEOUT_MS, NETWORK_ACCESS_TIMEOUT_DEFAULT_MS); final boolean supportsLeanbackOnly = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK_ONLY); @@ -13863,6 +13873,7 @@ public class ActivityManagerService extends IActivityManager.Stub mFullscreenThumbnailScale = res.getFraction( com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1); } + mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs; } } @@ -19032,26 +19043,13 @@ public class ActivityManagerService extends IActivityManager.Stub 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; @@ -22529,6 +22527,9 @@ public class ActivityManagerService extends IActivityManager.Stub @VisibleForTesting @GuardedBy("this") void incrementProcStateSeqAndNotifyAppsLocked() { + if (mWaitForNetworkTimeoutMs <= 0) { + return; + } // Used for identifying which uids need to block for network. ArrayList<Integer> blockingUids = null; for (int i = mActiveUids.size() - 1; i >= 0; --i) { @@ -23571,10 +23572,14 @@ public class ActivityManagerService extends IActivityManager.Stub } final long startTime = SystemClock.uptimeMillis(); record.waitingForNetwork = true; - record.lock.wait(WAIT_FOR_NETWORK_TIMEOUT_MS); + record.lock.wait(mWaitForNetworkTimeoutMs); record.waitingForNetwork = false; final long totalTime = SystemClock.uptimeMillis() - startTime; - if (DEBUG_NETWORK || totalTime > WAIT_FOR_NETWORK_TIMEOUT_MS / 2) { + if (totalTime >= mWaitForNetworkTimeoutMs) { + Slog.wtf(TAG_NETWORK, "Total time waited for network rules to get updated: " + + totalTime + ". Uid: " + callingUid + " procStateSeq: " + + procStateSeq); + } else if (DEBUG_NETWORK || totalTime >= mWaitForNetworkTimeoutMs / 2) { Slog.d(TAG_NETWORK, "Total time waited for network rules to get updated: " + totalTime + ". Uid: " + callingUid + " procStateSeq: " + procStateSeq); @@ -23869,6 +23874,8 @@ public class ActivityManagerService extends IActivityManager.Stub @VisibleForTesting public static class Injector { + private NetworkManagementInternal mNmi; + public AppOpsService getAppOpsService(File file, Handler handler) { return new AppOpsService(file, handler); } @@ -23878,8 +23885,17 @@ public class ActivityManagerService extends IActivityManager.Stub } public boolean isNetworkRestrictedForUid(int uid) { - // TODO: add implementation + if (ensureHasNetworkManagementInternal()) { + return mNmi.isNetworkRestrictedForUid(uid); + } return false; } + + private boolean ensureHasNetworkManagementInternal() { + if (mNmi == null) { + mNmi = LocalServices.getService(NetworkManagementInternal.class); + } + return mNmi != null; + } } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 6cea4838901b..9a1cd8c3884b 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_FORCE_RESIZEABLE; 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.pm.ApplicationInfo; 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 @@ import java.util.Objects; /** * 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 @@ final class ActivityRecord implements AppWindowContainerListener { // 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 @@ final class ActivityRecord implements AppWindowContainerListener { 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 @@ final class ActivityRecord implements AppWindowContainerListener { } 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 @@ final class ActivityRecord implements AppWindowContainerListener { 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 @@ final class ActivityRecord implements AppWindowContainerListener { 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 @@ final class ActivityRecord implements AppWindowContainerListener { } /** 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 @@ final class ActivityRecord implements AppWindowContainerListener { 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 @@ final class ActivityRecord implements AppWindowContainerListener { // 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 @@ final class ActivityRecord implements AppWindowContainerListener { // 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 @@ final class ActivityRecord implements AppWindowContainerListener { + 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 @@ final class ActivityRecord implements AppWindowContainerListener { } // 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 @@ final class ActivityRecord implements AppWindowContainerListener { 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 @@ final class ActivityRecord implements AppWindowContainerListener { 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 97d0aa3ec964..27b8e91981c0 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D 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 a3e95b85eb21..3d60681cfd71 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 @@ import android.content.res.Configuration; * 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 a668fea9711f..fd65c101919d 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 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta @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 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta } // Close up recents linked list. - void closeRecentsChain() { + private void closeRecentsChain() { if (mPrevAffiliate != null) { mPrevAffiliate.setNextAffiliate(mNextAffiliate); } @@ -1188,7 +1188,10 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta 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 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta 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 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta } /** 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 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta 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 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta } - /** - * 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 f953a36771f6..67b80f61775e 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 final class UidRecord { public UidRecord(int _uid) { uid = _uid; + idle = true; reset(); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f5ea74d6606b..8cc937502592 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 @@ public class PackageManagerService extends IPackageManager.Stub { /** 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { } } - 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 cd55f50d9b4b..7d53310b4e86 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.database.ContentObserver; 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 @@ public class AccessibilityShortcutController { 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 @@ public class AccessibilityShortcutController { 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 @@ public class AccessibilityShortcutController { } } - 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 31e22b9819a6..52f6955568cb 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void interceptAccessibilityShortcutChord() { - if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable() + if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(isKeyguardLocked()) && mScreenshotChordVolumeDownKeyTriggered && mA11yShortcutChordVolumeUpKeyTriggered && !mScreenshotChordPowerKeyTriggered) { final long now = SystemClock.uptimeMillis(); @@ -1771,7 +1771,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { // 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenshotChordVolumeDownKeyConsumed = false; cancelPendingPowerKeyAction(); interceptScreenshotChord(); - if (!isKeyguardLocked()) { - interceptAccessibilityShortcutChord(); - } + interceptAccessibilityShortcutChord(); } } else { mScreenshotChordVolumeDownKeyTriggered = false; @@ -5841,9 +5839,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mA11yShortcutChordVolumeUpKeyConsumed = false; cancelPendingPowerKeyAction(); cancelPendingScreenshotChordAction(); - if (!isKeyguardLocked()) { - interceptAccessibilityShortcutChord(); - } + interceptAccessibilityShortcutChord(); } } else { mA11yShortcutChordVolumeUpKeyTriggered = false; @@ -7945,6 +7941,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java index 8f95cc74c914..a8d622353636 100644 --- a/services/core/java/com/android/server/vr/CompatibilityDisplay.java +++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java @@ -1,4 +1,3 @@ - package com.android.server.vr; import static android.view.Display.INVALID_DISPLAY; @@ -8,14 +7,18 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.ImageFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.media.ImageReader; import android.os.Build; import android.os.Handler; import android.os.IBinder; +import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; -import android.service.vr.IVrStateCallbacks; +import android.os.SystemProperties; +import android.service.vr.IPersistentVrStateCallbacks; import android.service.vr.IVrManager; import android.util.Log; import android.view.Surface; @@ -34,11 +37,12 @@ class CompatibilityDisplay { private final static int HEIGHT = 1800; private final static int WIDTH = 1400; private final static int DPI = 320; + private final static int STOP_VIRTUAL_DISPLAY_DELAY_MILLIS = 2000; private final static String DEBUG_ACTION_SET_MODE = "com.android.server.vr.CompatibilityDisplay.SET_MODE"; private final static String DEBUG_EXTRA_MODE_ON = - "com.android.servier.vr.CompatibilityDisplay.EXTRA_MODE_ON"; + "com.android.server.vr.CompatibilityDisplay.EXTRA_MODE_ON"; private final static String DEBUG_ACTION_SET_SURFACE = "com.android.server.vr.CompatibilityDisplay.SET_SURFACE"; private final static String DEBUG_EXTRA_SURFACE = @@ -46,14 +50,16 @@ class CompatibilityDisplay { private final DisplayManager mDisplayManager; private final IVrManager mVrManager; + private final Object mVdLock = new Object(); + private final Handler mHandler = new Handler(); - // TODO: Lock initially created when VrStateCallback was connected through Binder. This may not - // be necessary with the direct access to VrManager. - private final Object vdLock = new Object(); - - private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { + /** + * Callback implementation to receive changes to VrMode. + **/ + private final IPersistentVrStateCallbacks mVrStateCallbacks = + new IPersistentVrStateCallbacks.Stub() { @Override - public void onVrStateChanged(boolean enabled) { + public void onPersistentVrStateChanged(boolean enabled) { if (enabled != mIsVrModeEnabled) { mIsVrModeEnabled = enabled; updateVirtualDisplay(); @@ -63,7 +69,9 @@ class CompatibilityDisplay { private VirtualDisplay mVirtualDisplay; private Surface mSurface; - private boolean mIsDebugOverrideEnabled; + private ImageReader mImageReader; + private Runnable mStopVDRunnable; + private boolean mIsVrModeOverrideEnabled; private boolean mIsVrModeEnabled; public CompatibilityDisplay(DisplayManager displayManager, IVrManager vrManager) { @@ -79,13 +87,23 @@ class CompatibilityDisplay { startDebugOnlyBroadcastReceiver(context); } + /** + * Creates and Destroys the virtual display depending on the current state of VrMode. + */ private void updateVirtualDisplay() { - if (mIsVrModeEnabled || (DEBUG && mIsDebugOverrideEnabled)) { + boolean createVirtualDisplay = "true".equals(SystemProperties.get("vr_virtualdisplay")); + if (DEBUG) { + Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", createVD: " + createVirtualDisplay + + ", override: " + mIsVrModeOverrideEnabled); + } + + if (mIsVrModeEnabled || (createVirtualDisplay && mIsVrModeOverrideEnabled)) { // TODO: Consider not creating the display until ActivityManager needs one on // which to display a 2D application. - // TODO: STOPSHIP Remove DEBUG conditional before launching. - if (DEBUG) { + // TODO: STOPSHIP Remove createVirtualDisplay conditional before launching. + if (createVirtualDisplay) { startVirtualDisplay(); + startImageReader(); } } else { // Stop virtual display to test exit condition @@ -93,8 +111,17 @@ class CompatibilityDisplay { } } + /** + * Creates a DEBUG-only BroadcastReceiver through which a test app can simulate VrMode and + * set a custom Surface for the virtual display. This allows testing of the virtual display + * without going into full 3D. + * + * @param context The context. + */ private void startDebugOnlyBroadcastReceiver(Context context) { - if (DEBUG) { + // STOPSHIP: remove vr_debug_vd_receiver test. + boolean debugBroadcast = "true".equals(SystemProperties.get("vr_debug_vd_receiver")); + if (DEBUG || debugBroadcast) { IntentFilter intentFilter = new IntentFilter(DEBUG_ACTION_SET_MODE); intentFilter.addAction(DEBUG_ACTION_SET_SURFACE); @@ -103,21 +130,13 @@ class CompatibilityDisplay { public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (DEBUG_ACTION_SET_MODE.equals(action)) { - mIsDebugOverrideEnabled = + mIsVrModeOverrideEnabled = intent.getBooleanExtra(DEBUG_EXTRA_MODE_ON, false); updateVirtualDisplay(); } else if (DEBUG_ACTION_SET_SURFACE.equals(action)) { if (mVirtualDisplay != null) { - final Surface newSurface = - intent.getParcelableExtra(DEBUG_EXTRA_SURFACE); - - Log.i(TAG, "Setting the new surface from " + mSurface + " to " + newSurface); - if (newSurface != mSurface) { - mVirtualDisplay.setSurface(newSurface); - if (mSurface != null) { - mSurface.release(); - } - mSurface = newSurface; + if (intent.hasExtra(DEBUG_EXTRA_SURFACE)) { + setSurfaceLocked(intent.getParcelableExtra(DEBUG_EXTRA_SURFACE)); } } else { Log.w(TAG, "Cannot set the surface because the VD is null."); @@ -128,18 +147,27 @@ class CompatibilityDisplay { } } + /** + * Starts listening to VrMode changes. + */ private void startVrModeListener() { if (mVrManager != null) { try { - mVrManager.registerListener(mVrStateCallbacks); + mVrManager.registerPersistentVrStateListener(mVrStateCallbacks); } catch (RemoteException e) { Log.e(TAG, "Could not register VR State listener.", e); } } } + /** + * Returns the virtual display ID if one currently exists, otherwise returns + * {@link INVALID_DISPLAY_ID}. + * + * @return The virtual display ID. + */ public int getVirtualDisplayId() { - synchronized(vdLock) { + synchronized(mVdLock) { if (mVirtualDisplay != null) { int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId(); if (DEBUG) { @@ -151,6 +179,9 @@ class CompatibilityDisplay { return INVALID_DISPLAY; } + /** + * Starts the virtual display if one does not already exist. + */ private void startVirtualDisplay() { if (DEBUG) { Log.d(TAG, "Request to start VD, DM:" + mDisplayManager); @@ -161,7 +192,7 @@ class CompatibilityDisplay { return; } - synchronized (vdLock) { + synchronized (mVdLock) { if (mVirtualDisplay != null) { Log.i(TAG, "VD already exists, ignoring request"); return; @@ -169,10 +200,6 @@ class CompatibilityDisplay { mVirtualDisplay = mDisplayManager.createVirtualDisplay("VR 2D Display", WIDTH, HEIGHT, DPI, null /* Surface */, 0 /* flags */); - if (mVirtualDisplay != null && mSurface != null && mSurface.isValid()) { - // TODO: Need to protect all setSurface calls with a lock. - mVirtualDisplay.setSurface(mSurface); - } } if (DEBUG) { @@ -180,16 +207,69 @@ class CompatibilityDisplay { } } + /** + * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout. + * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out + * of being enabled. This can happen sometimes with our 2D test app. + */ private void stopVirtualDisplay() { - if (DEBUG) { - Log.i(TAG, "Santos, stopping VD"); + if (mStopVDRunnable == null) { + mStopVDRunnable = new Runnable() { + @Override + public void run() { + if (mIsVrModeEnabled) { + Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on."); + } else { + Log.i(TAG, "Stopping Virtual Display"); + synchronized (mVdLock) { + setSurfaceLocked(null); // clean up and release the surface first. + if (mVirtualDisplay != null) { + mVirtualDisplay.release(); + mVirtualDisplay = null; + } + } + } + } + }; } - synchronized (vdLock) { + mHandler.removeCallbacks(mStopVDRunnable); + mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS); + } + + /** + * Set the surface to use with the virtual display. + * + * Code should be locked by {@link #mVdLock} before invoked. + * + * @param surface The Surface to set. + */ + private void setSurfaceLocked(Surface surface) { + // Change the surface to either a valid surface or a null value. + if (mSurface != surface && (surface == null || surface.isValid())) { + Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface); if (mVirtualDisplay != null) { - mVirtualDisplay.release(); - mVirtualDisplay = null; + mVirtualDisplay.setSurface(surface); + } + if (mSurface != null) { + mSurface.release(); } + mSurface = surface; + } + } + + /** + * Starts an ImageReader as a do-nothing Surface. The virtual display will not get fully + * initialized within surface flinger unless it has a valid Surface associated with it. We use + * the ImageReader as the default valid Surface. + */ + private void startImageReader() { + if (mImageReader == null) { + mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, ImageFormat.RAW_PRIVATE, + 2 /* maxImages */); + } + synchronized (mVdLock) { + setSurfaceLocked(mImageReader.getSurface()); } } } diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index e1df0ba287e1..731f53f37af2 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.Looper; 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.IPersistentVrStateCallbacks; 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 @@ public class VrManagerService extends SystemService implements EnabledComponentC } @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 @@ public class VrManagerService extends SystemService implements EnabledComponentC 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 ef3d87c9ae97..e60295de4876 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.app.ActivityManager.TaskSnapshot; 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 @@ public class AppWindowContainerController 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 @@ public class AppWindowContainerController 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 @@ public class AppWindowContainerController 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 @@ public class AppWindowContainerController 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 @@ public class AppWindowContainerController } } + // 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 d1f1305d032b..72ae90d71a9e 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 e5b00f3df79a..8f391a718269 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 6973c3c95d49..2a02359cc529 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 java.util.function.Predicate; 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 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon 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 d4c8b1f850ad..6fd95a4d4a4f 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** * @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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ecbd312327ac..ab86966334c3 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 59e698c39975..7b4fa8798885 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -16,8 +16,6 @@ package android.net.ip; -import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; - import com.android.internal.util.MessageUtils; import com.android.internal.util.WakeupMessage; @@ -44,7 +42,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.SystemClock; -import android.system.OsConstants; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -1031,36 +1028,15 @@ public class IpManager extends StateMachine { return true; } - private void enableInterfaceIPv6PrivacyExtensions() { + private boolean startIPv6() { + // Set privacy extensions. final String PREFER_TEMPADDRS = "2"; - NetdService.run((INetd netd) -> { - netd.setProcSysNet( - INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr", PREFER_TEMPADDRS); - }); - } - - private void setInterfaceIPv6RaRtInfoMaxPlen(int plen) { - // Setting RIO max plen is best effort. Catch and ignore most exceptions. try { NetdService.run((INetd netd) -> { - netd.setProcSysNet( - INetd.IPV6, INetd.CONF, mInterfaceName, "accept_ra_rt_info_max_plen", - Integer.toString(plen)); - }); - } catch (ServiceSpecificException e) { - // Old kernel versions without support for RIOs do not export accept_ra_rt_info_max_plen - // in the /proc filesystem. If the kernel supports RIOs we should never see any other - // type of error. - if (e.errorCode != OsConstants.ENOENT) { - logError("unexpected error setting accept_ra_rt_info_max_plen %s", e); - } - } - } - - private boolean startIPv6() { - try { - enableInterfaceIPv6PrivacyExtensions(); - setInterfaceIPv6RaRtInfoMaxPlen(RFC7421_PREFIX_LENGTH); + netd.setProcSysNet( + INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr", + PREFER_TEMPADDRS); + }); mNwService.enableIpv6(mInterfaceName); } catch (IllegalStateException|RemoteException|ServiceSpecificException e) { logError("Unable to change interface settings: %s", e); diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java new file mode 100644 index 000000000000..c9180a99c98d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; +import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; +import static android.util.DebugUtils.valueToString; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.net.NetworkPolicyManager; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArrayMap; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.BiFunction; + +/** + * Test class for {@link NetworkManagementInternal}. + * + * To run the tests, use + * + * runtest -c com.android.server.NetworkManagementInternalTest frameworks-services + * + * or the following steps: + * + * Build: m FrameworksServicesTests + * Install: adb install -r \ + * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk + * Run: adb shell am instrument -e class com.android.server.NetworkManagementInternalTest -w \ + * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NetworkManagementInternalTest { + private static final int TEST_UID = 111; + + private NetworkManagementService.Injector mInjector; + private NetworkManagementInternal mNmi; + + @Before + public void setUp() { + final NetworkManagementService service = new NetworkManagementService(); + mInjector = service.getInjector(); + mNmi = service.new LocalService(); + } + + @Test + public void testIsNetworkRestrictedForUid() { + // No firewall chains enabled + assertFalse(mNmi.isNetworkRestrictedForUid(TEST_UID)); + + // Restrict usage of mobile data in background + mInjector.setUidOnMeteredNetworkList(true, TEST_UID, true); + assertTrue("Should be true since mobile data usage is restricted", + mNmi.isNetworkRestrictedForUid(TEST_UID)); + mInjector.reset(); + + // Data saver is on and uid is not whitelisted + mInjector.setDataSaverMode(true); + mInjector.setUidOnMeteredNetworkList(false, TEST_UID, false); + assertTrue("Should be true since data saver is on and the uid is not whitelisted", + mNmi.isNetworkRestrictedForUid(TEST_UID)); + mInjector.reset(); + + // Data saver is on and uid is whitelisted + mInjector.setDataSaverMode(true); + mInjector.setUidOnMeteredNetworkList(false, TEST_UID, true); + assertFalse("Should be false since data saver is on and the uid is whitelisted", + mNmi.isNetworkRestrictedForUid(TEST_UID)); + mInjector.reset(); + + final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>(); + // Dozable chain + final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>(); + isRestrictedForDozable.put(FIREWALL_RULE_DEFAULT, true); + isRestrictedForDozable.put(FIREWALL_RULE_ALLOW, false); + isRestrictedForDozable.put(FIREWALL_RULE_DENY, true); + expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable); + // Powersaver chain + final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>(); + isRestrictedForPowerSave.put(FIREWALL_RULE_DEFAULT, true); + isRestrictedForPowerSave.put(FIREWALL_RULE_ALLOW, false); + isRestrictedForPowerSave.put(FIREWALL_RULE_DENY, true); + expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave); + // Standby chain + final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>(); + isRestrictedForStandby.put(FIREWALL_RULE_DEFAULT, false); + isRestrictedForStandby.put(FIREWALL_RULE_ALLOW, false); + isRestrictedForStandby.put(FIREWALL_RULE_DENY, true); + expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby); + + final int[] chains = { + FIREWALL_CHAIN_STANDBY, + FIREWALL_CHAIN_POWERSAVE, + FIREWALL_CHAIN_DOZABLE + }; + final int[] states = { + FIREWALL_RULE_ALLOW, + FIREWALL_RULE_DENY, + FIREWALL_RULE_DEFAULT + }; + BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> { + return String.format("Unexpected value for chain: %s and state: %s", + valueToString(NetworkPolicyManager.class, "FIREWALL_CHAIN_", chain), + valueToString(NetworkPolicyManager.class, "FIREWALL_RULE_", state)); + }; + for (int chain : chains) { + final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain); + mInjector.setFirewallChainState(chain, true); + for (int state : states) { + mInjector.setFirewallRule(chain, TEST_UID, state); + assertEquals(errorMsg.apply(chain, state), + expectedValues.get(state), mNmi.isNetworkRestrictedForUid(TEST_UID)); + } + mInjector.reset(); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index cc5764bdae53..b12da34f4375 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -132,6 +132,7 @@ public class ActivityManagerServiceTest { mHandler = new TestHandler(mHandlerThread.getLooper()); mInjector = new TestInjector(); mAms = new ActivityManagerService(mInjector); + mAms.mWaitForNetworkTimeoutMs = 100; } @After @@ -217,6 +218,17 @@ public class ActivityManagerServiceTest { 44, // exptectedCurProcStateSeq -1, // expectedBlockState, -1 to verify there are no interactions with main thread. false); // expectNotify + + // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented. + mAms.mWaitForNetworkTimeoutMs = 0; + mInjector.setNetworkRestrictedForUid(true); + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_TOP, // prevState + PROCESS_STATE_IMPORTANT_BACKGROUND, // curState + 44, // expectedGlobalCounter + 44, // exptectedCurProcStateSeq + -1, // expectedBlockState, -1 to verify there are no interactions with main thread. + false); // expectNotify } private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState, 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 4d5f783bc238..a4e3988e686f 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.lang.reflect.Field; 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 @@ public class AccessibilityShortcutControllerTest { (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 @@ public class AccessibilityShortcutControllerTest { } @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(); + configureValidShortcutService(); + accessibilityShortcutController.onSettingsChanged(); + 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(); + configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); accessibilityShortcutController.onSettingsChanged(); - assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable()); + 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 @@ public class AccessibilityShortcutControllerTest { @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 @@ public class AccessibilityShortcutControllerTest { @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 @@ public class AccessibilityShortcutControllerTest { @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 @@ public class AccessibilityShortcutControllerTest { @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 @@ public class AccessibilityShortcutControllerTest { @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 @@ public class AccessibilityShortcutControllerTest { @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 @@ public class AccessibilityShortcutControllerTest { 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/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java index 74557e256d51..80b2e7d9bc3c 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 @@ public class WindowContainerTests extends WindowTestsBase { } @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 @@ public class WindowContainerTests extends WindowTestsBase { 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 @@ public class WindowContainerTests extends WindowTestsBase { } @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 28b6e45dc8da..1b1984dba8f4 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 org.junit.runner.RunWith; 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.IWindow; 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 @@ import static org.junit.Assert.assertTrue; @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 @@ public class WindowFrameTests { // 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 6f7824562b65..48799d23c06e 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 @@ class WindowTestsBase { 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 @@ class WindowTestsBase { 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 @@ class WindowTestsBase { 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 4da2853aba41..f1cf4414de9e 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 @@ public class UsbDeviceManager { */ 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 @@ public class UsbDeviceManager { 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 @@ public class UsbDeviceManager { 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 @@ public class UsbDeviceManager { 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 @@ public class UsbDeviceManager { } // 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 @@ public class UsbDeviceManager { } 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 @@ public class UsbDeviceManager { } 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 class UsbDeviceManager { 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 @@ public class UsbDeviceManager { 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); + } + + // 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])); + } } - overrideList.add(new Pair<String, String>(items[1], items[2])); } } } } 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"); - - 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; + Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode); + + 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/tests/testables/src/android/testing/AndroidTestingRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java index a425f70e836c..816ed033a3e2 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.RunAfters; 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.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; -import java.util.ArrayList; import java.util.List; /** @@ -50,21 +49,28 @@ public class AndroidTestingRunner extends BlockJUnit4ClassRunner { @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 @@ public class AndroidTestingRunner extends BlockJUnit4ClassRunner { 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 62490bc214a4..8a33cf918646 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 @@ import java.util.Map; */ 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); - } - private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception { - mQueueWrapper = wrapper; - setupQueue(l); + public TestableLooper() throws Exception { + this(true); } - 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 @@ public class TestableLooper { * 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 @@ public class TestableLooper { 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 @@ public class TestableLooper { * 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 @@ public class TestableLooper { return sLoopers.get(test); } - public static class LooperFrameworkMethod extends FrameworkMethod { - private HandlerThread mHandlerThread; - - private final TestableLooper mTestableLooper; - private final Looper mLooper; - private final Handler mHandler; + public static class LooperStatement extends Statement { + private final boolean mSetAsMain; + private final Statement mBase; + private final TestableLooper mLooper; - 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); - } - boolean set = mTestableLooper.mQueueWrapper == null; - if (set) { - mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation() - .acquireLooperManager(mLooper); + public void evaluate() throws Throwable { + mLooper.setForCurrentThread(); + if (mSetAsMain) { + mLooper.setAsMainLooper(); } + 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 12f1d0a5f414..18e5fffef992 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.times; 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 @@ public class TestableLooperTest { 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 @@ public class TestableLooperTest { @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()); - - try { - testableLooper.setMessageHandler(m -> { - if (m.getCallback() == r) return true; - return false; - }); - new Handler(Looper.getMainLooper()).post(r); - testableLooper.processAllMessages(); - - verify(r).run(); - verify(r2, never()).run(); - } finally { - testableLooper.destroy(); - } + new Handler().post(r); + new Handler(looper.getLooper()).post(r2); + + looper.processAllMessages(); + verify(r2).run(); + verify(r, never()).run(); + + mTestableLooper.processAllMessages(); + verify(r).run(); } @Test diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 57036aad611b..ef3797c4a277 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -87,6 +87,7 @@ cc_library_host_static { "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 1d04b357131a..b855f8f80c58 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 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context, 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 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context, 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 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption 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 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption 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 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption 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 456f68635705..5e9b81a29af9 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -25,7 +25,7 @@ namespace aapt { 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 8027f4289169..1fe30f0b1478 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 @@ static bool LoadInputFilesFromDir( 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 @@ static bool LoadInputFilesFromDir( 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 @@ static bool LoadInputFilesFromDir( 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 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, 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 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // 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 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // 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 @@ static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, // 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 @@ static bool FlattenXmlToOutStream(IAaptContext* context, 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 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, 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 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, 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 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, 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 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, } 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 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, 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 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, // 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 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, } // 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 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, // 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 5e15c88410ee..6d6147d8c97a 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 a82005115850..e4255e714744 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 @@ class Png { const PngOptions& options); private: - IDiagnostics* mDiag; - DISALLOW_COPY_AND_ASSIGN(Png); + + IDiagnostics* mDiag; }; /** @@ -57,26 +60,26 @@ class Png { 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 edec12317fdd..f9043b5a109a 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -71,16 +71,16 @@ static bool IsPngChunkWhitelisted(uint32_t type) { 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 @@ bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) { 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 @@ bool PngChunkFilter::Next(const void** buffer, int* len) { // 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 @@ bool PngChunkFilter::Next(const void** buffer, int* len) { 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 3b46d8b4c782..ae98afcd3cc3 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 @@ class PngReadStructDeleter { 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 @@ static void LogError(png_structp png_ptr, png_const_charp error_msg) { 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 @@ static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, 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 @@ static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, 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 @@ static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, 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 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { // 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 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { } // 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 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { // 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 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { // 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 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { 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 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { 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 @@ static int PickColorType(int32_t width, int32_t height, bool grayscale, 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 @@ static void WritePalette(png_structp write_ptr, png_infop write_info_ptr, } // 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 @@ static void WritePalette(png_structp write_ptr, png_infop write_info_ptr, // 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 @@ static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr, 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 @@ bool WritePng(IAaptContext* context, const Image* image, 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 @@ bool WritePng(IAaptContext* context, const Image* image, } // 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 @@ bool WritePng(IAaptContext* context, const Image* image, 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 @@ bool WritePng(IAaptContext* context, const Image* image, } 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 @@ bool WritePng(IAaptContext* context, const Image* image, } } 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 5c96a4dbd4e2..826f91b4a2fd 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 @@ class DirectoryWriter : public IArchiveWriter { 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 @@ class DirectoryWriter : public IArchiveWriter { 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 @@ class DirectoryWriter : public IArchiveWriter { 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 @@ class ZipFileWriter : public IArchiveWriter { 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 @@ class ZipFileWriter : public IArchiveWriter { 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 f0681bdd1167..4ee4ce71a5c5 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 @@ struct ArchiveEntry { 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 000000000000..92612c744aae --- /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 000000000000..95113bc2132c --- /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 000000000000..eb99033e1cbe --- /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 fdc044d86e5a..09dc7ea5b1c4 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 IData { 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 @@ class DataSegment : public IData { 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 1ef9743f65cf..7ef6d88c1e3b 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 @@ class IFile { 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 @@ class IFileCollectionIterator { 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 cab4b65f2f5a..f5c5737cb149 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 @@ namespace io { 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 33cdc7bbe498..2a34d4dd442d 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 1b4d5bb4f6e1..7f715895e282 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 @@ class LinkContext : public IAaptContext { 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 @@ static bool FlattenXml(xml::XmlResource* xml_res, const StringPiece& path, 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 @@ static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, 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 @@ class LinkCommand { 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 @@ class LinkCommand { 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 @@ class LinkCommand { 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 1c9a75d0547d..9899f803b339 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 3b882909698d..2e149748636b 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.annotation.NonNull; 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 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { // dispose of the default typeface. Typeface_Delegate.resetDefaults(); + Typeface.sDynamicTypefaceCache.evictAll(); return true; } diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png Binary files differindex b2baa98be0a0..957831d5c7ec 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png 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 67b42a7cf86d..00dddeed3e8c 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 @@ public class RenderTestBase { 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 b0aa3c2989a5..cb0bc6d3c431 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 @@ public final class CreateInfo implements ICreateInfo { 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" }; /** diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index af48d0a2b6ca..10ffd8ae1f7d 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -139,7 +139,7 @@ interface IWifiManager void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable); - String getWpsNfcConfigurationToken(int netId); + String getCurrentNetworkWpsNfcConfigurationToken(); void enableVerboseLogging(int verbose); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 824c436ac9fd..ae6a6793dc9d 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1437,15 +1437,15 @@ public class WifiManager { } /** - * Creates a configuration token describing the network referenced by {@code netId} - * of MIME type application/vnd.wfa.wsc. Can be used to configure WiFi networks via NFC. + * Creates a configuration token describing the current network of MIME type + * application/vnd.wfa.wsc. Can be used to configure WiFi networks via NFC. * - * @return hex-string encoded configuration token + * @return hex-string encoded configuration token or null if there is no current network * @hide */ - public String getWpsNfcConfigurationToken(int netId) { + public String getCurrentNetworkWpsNfcConfigurationToken() { try { - return mService.getWpsNfcConfigurationToken(netId); + return mService.getCurrentNetworkWpsNfcConfigurationToken(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } |