Merge "Increase UI and RenderThread priority when not using FIFO." into nyc-mr1-dev
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 1e2cc26..a8d332b 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -17,6 +17,7 @@
package android.app;
import android.graphics.Rect;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -852,7 +853,9 @@
* Ensure that fragments that are entering are at least at the CREATED state
* so that they may load Transitions using TransitionInflater.
*/
- if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED) {
+ if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED &&
+ mManager.mHost.getContext().getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.N) {
mManager.makeActive(fragment);
mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
}
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index f256a95..f987468 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -42,7 +42,7 @@
// If 'true', then apply an acceptable-size heuristic at restore time, dropping back
// to the factory default wallpaper if the restored one differs "too much" from the
// device's preferred wallpaper image dimensions.
- private static final boolean REJECT_OUTSIZED_RESTORE = true;
+ private static final boolean REJECT_OUTSIZED_RESTORE = false;
// When outsized restore rejection is enabled, this is the maximum ratio between the
// source and target image heights that will be permitted. The ratio is checked both
@@ -60,6 +60,9 @@
public static final String WALLPAPER_IMAGE =
new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
"wallpaper").getAbsolutePath();
+ public static final String WALLPAPER_ORIG_IMAGE =
+ new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
+ "wallpaper_orig").getAbsolutePath();
public static final String WALLPAPER_INFO =
new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
"wallpaper_info.xml").getAbsolutePath();
@@ -199,7 +202,7 @@
// since it does not exist anywhere other than the private wallpaper
// file.
Slog.d(TAG, "Applying restored wallpaper image.");
- f.renameTo(new File(WALLPAPER_IMAGE));
+ f.renameTo(new File(WALLPAPER_ORIG_IMAGE));
}
}
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 2eb767e..b9b609b 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -492,6 +492,8 @@
* If the calling launcher application contains pinned shortcuts, they will still work,
* even though the caller no longer has the shortcut host permission.
*
+ * <p>Returns {@code false} when the user is locked.
+ *
* @see ShortcutManager
*/
public boolean hasShortcutHostPermission() {
@@ -508,6 +510,9 @@
* <p>Callers must be allowed to access the shortcut information, as defined in {@link
* #hasShortcutHostPermission()}.
*
+ * <p>Returns am empty list when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
* @param query result includes shortcuts matching this query.
* @param user The UserHandle of the profile.
*
@@ -551,6 +556,9 @@
* <p>The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
+ * <p>Call will be ignored when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
* @param packageName The target package name.
* @param shortcutIds The IDs of the shortcut to be pinned.
* @param user The UserHandle of the profile.
@@ -622,6 +630,9 @@
* <p>The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
+ * <p>Returns {@code null} when the user is locked, or when the user owning the shortcut
+ * is locked or not running.
+ *
* @param density The preferred density of the icon, zero for default density. Use
* density DPI values from {@link DisplayMetrics}.
*
@@ -670,6 +681,9 @@
* <p>The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
+ * <p>Returns {@code 0} when the user is locked, or when the user owning the shortcut
+ * is locked or not running.
+ *
* @param density Optional density for the icon, or 0 to use the default density. Use
* @return A badged icon for the shortcut.
*
@@ -690,6 +704,10 @@
* <p>The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
+ * <p>Throws {@link android.content.ActivityNotFoundException}
+ * when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
* @param packageName The target shortcut package name.
* @param shortcutId The target shortcut ID.
* @param sourceBounds The Rect containing the source bounds of the clicked icon.
@@ -712,6 +730,10 @@
* <p>The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
+ * <p>Throws {@link android.content.ActivityNotFoundException}
+ * when the user is locked, or when the user owning the shortcut
+ * is locked or not running.
+ *
* @param shortcut The target shortcut.
* @param sourceBounds The Rect containing the source bounds of the clicked icon.
* @param startActivityOptions Options to pass to startActivity.
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 99d578b..cd248ea 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -434,6 +434,12 @@
* <h3>Launcher API</h3>
*
* The {@link LauncherApps} class provides APIs for launcher applications to access shortcuts.
+ *
+ *
+ * <h3>Direct Boot and Shortcuts</h3>
+ *
+ * All shortcut information is stored in credential encrypted storage, so no shortcuts can be
+ * accessed when the user is locked.
*/
public class ShortcutManager {
private static final String TAG = "ShortcutManager";
@@ -469,6 +475,8 @@
*
* @throws IllegalArgumentException if {@link #getMaxShortcutCountPerActivity()} is exceeded,
* or when trying to update immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
try {
@@ -481,6 +489,8 @@
/**
* Return all dynamic shortcuts from the caller application.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
@NonNull
public List<ShortcutInfo> getDynamicShortcuts() {
@@ -494,6 +504,8 @@
/**
* Return all manifest shortcuts from the caller application.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
@NonNull
public List<ShortcutInfo> getManifestShortcuts() {
@@ -515,6 +527,8 @@
*
* @throws IllegalArgumentException if {@link #getMaxShortcutCountPerActivity()} is exceeded,
* or when trying to update immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
try {
@@ -527,6 +541,8 @@
/**
* Delete dynamic shortcuts by ID.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) {
try {
@@ -539,6 +555,8 @@
/**
* Delete all dynamic shortcuts from the caller application.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public void removeAllDynamicShortcuts() {
try {
@@ -550,6 +568,8 @@
/**
* Return all pinned shortcuts from the caller application.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
@NonNull
public List<ShortcutInfo> getPinnedShortcuts() {
@@ -570,6 +590,8 @@
* @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
*
* @throws IllegalArgumentException If trying to update immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) {
try {
@@ -585,6 +607,8 @@
* class.
*
* @throws IllegalArgumentException If trying to disable immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public void disableShortcuts(@NonNull List<String> shortcutIds) {
try {
@@ -622,6 +646,8 @@
* For more details, see the Javadoc for the {@link ShortcutManager} class.
*
* @throws IllegalArgumentException If trying to disable immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public void disableShortcuts(@NonNull List<String> shortcutIds, CharSequence disabledMessage) {
try {
@@ -638,6 +664,8 @@
* already enabled, this method does nothing.
*
* @throws IllegalArgumentException If trying to enable immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public void enableShortcuts(@NonNull List<String> shortcutIds) {
try {
@@ -704,6 +732,8 @@
* Return {@code true} when rate-limiting is active for the caller application.
*
* <p>See the class level javadoc for details.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public boolean isRateLimitingActive() {
try {
@@ -747,6 +777,8 @@
* <p>The information is accessible via {@link UsageStatsManager#queryEvents}
* Typically, launcher applications use this information to build a prediction model
* so that they can promote the shortcuts that are likely to be used at the moment.
+ *
+ * @throws IllegalStateException when the user is locked.
*/
public void reportShortcutUsed(String shortcutId) {
try {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index da09922..5e4cd33 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6230,6 +6230,20 @@
public static final int VR_DISPLAY_MODE_OFF = 1;
/**
+ * Whether CarrierAppUtils#disableCarrierAppsUntilPrivileged has been executed at least
+ * once.
+ *
+ * <p>This is used to ensure that we only take one pass which will disable apps that are not
+ * privileged (if any). From then on, we only want to enable apps (when a matching SIM is
+ * inserted), to avoid disabling an app that the user might actively be using.
+ *
+ * <p>Will be set to 1 once executed.
+ *
+ * @hide
+ */
+ public static final String CARRIER_APPS_HANDLED = "carrier_apps_handled";
+
+ /**
* Whether parent user can access remote contact in managed profile.
*
* @hide
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 899ae49..85a4bf9 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1227,6 +1227,33 @@
return mMinLuminance;
}
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof HdrCapabilities)) {
+ return false;
+ }
+ HdrCapabilities that = (HdrCapabilities) other;
+
+ return Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes)
+ && mMaxLuminance == that.mMaxLuminance
+ && mMaxAverageLuminance == that.mMaxAverageLuminance
+ && mMinLuminance == that.mMinLuminance;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 23;
+ hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
+ hash = hash * 17 + Float.floatToIntBits(mMaxLuminance);
+ hash = hash * 17 + Float.floatToIntBits(mMaxAverageLuminance);
+ hash = hash * 17 + Float.floatToIntBits(mMinLuminance);
+ return hash;
+ }
+
public static final Creator<HdrCapabilities> CREATOR = new Creator<HdrCapabilities>() {
@Override
public HdrCapabilities createFromParcel(Parcel source) {
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 49c3c46..bc40849 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -289,7 +289,7 @@
&& modeId == other.modeId
&& defaultModeId == other.defaultModeId
&& colorMode == other.colorMode
- && Objects.equal(supportedColorModes, other.supportedColorModes)
+ && Arrays.equals(supportedColorModes, other.supportedColorModes)
&& Objects.equal(hdrCapabilities, other.hdrCapabilities)
&& logicalDensityDpi == other.logicalDensityDpi
&& physicalXDpi == other.physicalXDpi
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d93f8af..dab494b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14778,26 +14778,14 @@
}
private void getRoundVerticalScrollBarBounds(Rect bounds) {
- final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
- int verticalScrollbarPosition = mVerticalScrollbarPosition;
- if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) {
- verticalScrollbarPosition = isLayoutRtl() ?
- SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT;
- }
final int width = mRight - mLeft;
final int height = mBottom - mTop;
- switch (verticalScrollbarPosition) {
- default:
- case SCROLLBAR_POSITION_RIGHT:
- bounds.left = mScrollX - (mUserPaddingRight & inside);
- break;
- case SCROLLBAR_POSITION_LEFT:
- bounds.left = mScrollX + (mUserPaddingLeft & inside);
- break;
- }
- bounds.top = mScrollY + (mPaddingTop & inside);
+ // Do not take padding into account as we always want the scrollbars
+ // to hug the screen for round wearable devices.
+ bounds.left = mScrollX;
+ bounds.top = mScrollY;
bounds.right = bounds.left + width;
- bounds.bottom = mScrollY + height - (mUserPaddingBottom & inside);
+ bounds.bottom = mScrollY + height;
}
private void getStraightVerticalScrollBarBounds(Rect bounds) {
@@ -22960,7 +22948,7 @@
/**
* Last global system UI visibility reported by the window manager.
*/
- int mGlobalSystemUiVisibility;
+ int mGlobalSystemUiVisibility = -1;
/**
* True if a view in this hierarchy has an OnSystemUiVisibilityChangeListener
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
index e59d7ba..fc68b84 100644
--- a/core/java/com/android/internal/widget/DecorCaptionView.java
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -244,6 +244,11 @@
return mTouchDispatchList;
}
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
private boolean passedSlop(int x, int y) {
return Math.abs(x - mTouchDownX) > mDragSlop || Math.abs(y - mTouchDownY) > mDragSlop;
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 886265a..429131b 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -42,6 +42,8 @@
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
/**
* Loads global system configuration info.
@@ -122,6 +124,11 @@
// These are the permitted backup transport service components
final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
+ // These are the packages of carrier-associated apps which should be disabled until used until
+ // a SIM is inserted which grants carrier privileges to that carrier app.
+ final ArrayMap<String, List<String>> mDisabledUntilUsedPreinstalledCarrierAssociatedApps =
+ new ArrayMap<>();
+
public static SystemConfig getInstance() {
synchronized (SystemConfig.class) {
if (sInstance == null) {
@@ -183,6 +190,10 @@
return mBackupTransportWhitelist;
}
+ public ArrayMap<String, List<String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps() {
+ return mDisabledUntilUsedPreinstalledCarrierAssociatedApps;
+ }
+
SystemConfig() {
// Read configuration from system
readPermissions(Environment.buildPath(
@@ -476,6 +487,26 @@
}
}
XmlUtils.skipCurrentTag(parser);
+ } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name)
+ && allowAppConfigs) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ String carrierPkgname = parser.getAttributeValue(null, "carrierAppPackage");
+ if (pkgname == null || carrierPkgname == null) {
+ Slog.w(TAG, "<disabled-until-used-preinstalled-carrier-associated-app"
+ + " without package or carrierAppPackage in " + permFile + " at "
+ + parser.getPositionDescription());
+ } else {
+ List<String> associatedPkgs =
+ mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get(
+ carrierPkgname);
+ if (associatedPkgs == null) {
+ associatedPkgs = new ArrayList<>();
+ mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put(
+ carrierPkgname, associatedPkgs);
+ }
+ associatedPkgs.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
} else {
XmlUtils.skipCurrentTag(parser);
continue;
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 037ca73..3669da8 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -195,10 +195,13 @@
// the animation list, 2) post a delayed message to end them at end time so their
// listeners can receive the corresponding callbacks.
anim->getVectorDrawable()->setPropertyChangeWillBeConsumed(false);
+ // Mark the VD dirty so it will damage itself during prepareTree.
+ anim->getVectorDrawable()->markDirty();
}
if (info.mode == TreeInfo::MODE_FULL) {
for (auto &anim : mPausedVDAnimators) {
anim->getVectorDrawable()->setPropertyChangeWillBeConsumed(false);
+ anim->getVectorDrawable()->markDirty();
}
}
// TODO: This is hacky
@@ -210,34 +213,6 @@
info.windowInsetLeft = 0;
info.windowInsetTop = 0;
info.errorHandler = nullptr;
-
- for (auto it = mRunningVDAnimators.begin(); it != mRunningVDAnimators.end();) {
- if (!(*it)->getVectorDrawable()->getPropertyChangeWillBeConsumed()) {
- // Vector Drawable is not in the display list, we should remove this animator from
- // the list, put it in the paused list, and post a delayed message to end the
- // animator.
- detachVectorDrawableAnimator(it->get());
- mPausedVDAnimators.insert(*it);
- it = mRunningVDAnimators.erase(it);
- } else {
- it++;
- }
- }
-
- if (info.mode == TreeInfo::MODE_FULL) {
- // Check whether any paused animator's target is back in Display List. If so, put the
- // animator back in the running list.
- for (auto it = mPausedVDAnimators.begin(); it != mPausedVDAnimators.end();) {
- if ((*it)->getVectorDrawable()->getPropertyChangeWillBeConsumed()) {
- mRunningVDAnimators.insert(*it);
- it = mPausedVDAnimators.erase(it);
- } else {
- it++;
- }
- }
- }
- info.out.hasAnimations |= !mRunningVDAnimators.empty();
-
}
void sendMessage(const sp<MessageHandler>& handler) {
@@ -275,7 +250,14 @@
mPendingAnimatingRenderNodes.clear();
}
- void runVectorDrawableAnimators(AnimationContext* context, TreeInfo::TraversalMode mode) {
+ // Run VectorDrawable animators after prepareTree.
+ void runVectorDrawableAnimators(AnimationContext* context, TreeInfo& info) {
+ // Push staging.
+ if (info.mode == TreeInfo::MODE_FULL) {
+ pushStagingVectorDrawableAnimators(context);
+ }
+
+ // Run the animators in the running list.
for (auto it = mRunningVDAnimators.begin(); it != mRunningVDAnimators.end();) {
if ((*it)->animate(*context)) {
it = mRunningVDAnimators.erase(it);
@@ -284,7 +266,8 @@
}
}
- if (mode == TreeInfo::MODE_FULL) {
+ // Run the animators in paused list during full sync.
+ if (info.mode == TreeInfo::MODE_FULL) {
// During full sync we also need to pulse paused animators, in case their targets
// have been added back to the display list. All the animators that passed the
// scheduled finish time will be removed from the paused list.
@@ -297,6 +280,42 @@
}
}
}
+
+ // Move the animators with a target not in DisplayList to paused list.
+ for (auto it = mRunningVDAnimators.begin(); it != mRunningVDAnimators.end();) {
+ if (!(*it)->getVectorDrawable()->getPropertyChangeWillBeConsumed()) {
+ // Vector Drawable is not in the display list, we should remove this animator from
+ // the list, put it in the paused list, and post a delayed message to end the
+ // animator.
+ detachVectorDrawableAnimator(it->get());
+ mPausedVDAnimators.insert(*it);
+ it = mRunningVDAnimators.erase(it);
+ } else {
+ it++;
+ }
+ }
+
+ // Move the animators with a target in DisplayList from paused list to running list, and
+ // trim paused list.
+ if (info.mode == TreeInfo::MODE_FULL) {
+ // Check whether any paused animator's target is back in Display List. If so, put the
+ // animator back in the running list.
+ for (auto it = mPausedVDAnimators.begin(); it != mPausedVDAnimators.end();) {
+ if ((*it)->getVectorDrawable()->getPropertyChangeWillBeConsumed()) {
+ mRunningVDAnimators.insert(*it);
+ it = mPausedVDAnimators.erase(it);
+ } else {
+ it++;
+ }
+ }
+ // Trim paused VD animators at full sync, so that when Java loses reference to an
+ // animator, we know we won't be requested to animate it any more, then we remove such
+ // animators from the paused list so they can be properly freed. We also remove the
+ // animators from paused list when the time elapsed since start has exceeded duration.
+ trimPausedVDAnimators(context);
+ }
+
+ info.out.hasAnimations |= !mRunningVDAnimators.empty();
}
void trimPausedVDAnimators(AnimationContext* context) {
@@ -387,29 +406,13 @@
mRootNode->attachPendingVectorDrawableAnimators();
}
AnimationContext::startFrame(mode);
- // Run VectorDrawable animators in the beginning of the frame instead of during prepareTree,
- // because one VD can be in multiple render nodes' display list. So it's more simple to
- // run them all at once before prepareTree than running them or checking whether they have
- // already ran in each RenderNode. Note that these animators don't damage the RenderNodes.
- // The damaging is done in prepareTree as needed after checking whether a VD has been
- // modified.
- if (mode == TreeInfo::MODE_FULL) {
- mRootNode->pushStagingVectorDrawableAnimators(this);
- }
- mRootNode->runVectorDrawableAnimators(this, mode);
}
// Runs any animations still left in mCurrentFrameAnimations
virtual void runRemainingAnimations(TreeInfo& info) {
AnimationContext::runRemainingAnimations(info);
+ mRootNode->runVectorDrawableAnimators(this, info);
postOnFinishedEvents();
- if (info.mode == TreeInfo::MODE_FULL) {
- // Trim paused VD animators at full sync, so that when Java loses reference to an
- // animator, we know we won't be requested to animate it any more, then we remove such
- // animators from the paused list so they can be properly freed. We also remove the
- // animators from paused list when the time elapsed since start has exceeded duration.
- mRootNode->trimPausedVDAnimators(this);
- }
}
virtual void detachAnimators() override {
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index 2b94718..1e6f216 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -1202,9 +1202,19 @@
- from: /r/studio-ui/menu-start.html
to: /training/index.html?utm_medium=android-studio
-# N Preview redirects
-
+- from: /preview/features/icu4j-framework.html
+ to: /guide/topics/resources/icu4j-framework.html
- from: /preview/features/key-attestation.html
to: /training/articles/security-key-attestation.html
- from: /preview/features/security-config.html
- to: /training/articles/security-config.html
\ No newline at end of file
+ to: /training/articles/security-config.html
+- from: /preview/features/picture-in-picture.html
+ to: /training/tv/playback/picture-in-picture.html
+- from: /preview/features/tv-recording-api.html
+ to: /training/tv/tif/content-recording.html
+- from: /preview/features/direct-boot.html
+ to: /training/articles/direct-boot.html
+- from: /preview/features/scoped-folder-access.html
+ to: /training/articles/scoped-directory-access.html
+- from: /preview/features/notification-updates.html
+ to: /guide/topics/ui/notifiers/notifications.html
diff --git a/docs/html/guide/_book.yaml b/docs/html/guide/_book.yaml
index 6d6686b..b788c38 100644
--- a/docs/html/guide/_book.yaml
+++ b/docs/html/guide/_book.yaml
@@ -69,6 +69,9 @@
path: /guide/topics/resources/runtime-changes.html
- title: Localization
path: /guide/topics/resources/localization.html
+ section:
+ - title: ICU4J Android Framework APIs
+ path: /guide/topics/resources/icu4j-framework.html
- title: Resource Types
path: /guide/topics/resources/available-resources.html
section:
diff --git a/docs/html/guide/topics/data/data-storage.jd b/docs/html/guide/topics/data/data-storage.jd
index a745d00..770340b 100644
--- a/docs/html/guide/topics/data/data-storage.jd
+++ b/docs/html/guide/topics/data/data-storage.jd
@@ -252,6 +252,17 @@
save to the external storage. All applications can read and write files placed on the external
storage and the user can remove them.</p>
+<h3 id="ScopedDirAccess">Using Scoped Directory Access</h3>
+
+On Android 7.0 or later, if you need access to a specific directory on
+external storage, use scoped directory access. Scoped directory access
+simplifies how your application accesses standard external storage directories,
+such as the <code>Pictures</code> directory, and provides a simple
+permissions UI that clearly details what directory the application is
+requesting access to. For more details on scoped directory access, see
+<a href="{@docRoot}training/articles/scoped-directory-access.html">Using
+Scoped Directory Access</a>.
+
<h3 id="ExternalPermissions">Getting access to external storage</h3>
<p>In order to read or write files on the external storage, your app must acquire the
diff --git a/docs/html/preview/features/icu4j-framework.jd b/docs/html/guide/topics/resources/icu4j-framework.jd
similarity index 92%
rename from docs/html/preview/features/icu4j-framework.jd
rename to docs/html/guide/topics/resources/icu4j-framework.jd
index 839c077..e7cbe72 100644
--- a/docs/html/preview/features/icu4j-framework.jd
+++ b/docs/html/guide/topics/resources/icu4j-framework.jd
@@ -30,14 +30,16 @@
<p>
ICU4J is an open-source, widely used set of Java libraries providing Unicode
- and globalization support for software applications. Android N
- exposes a subset of the ICU4J APIs in the Android framework for app developers
+ and globalization support for software applications. Starting in Android 7.0
+ (API level 24), Android
+ exposes a subset of the ICU4J APIs for app developers
to use under the {@code android.icu} package. These APIs use
localization data present on the device. As a result, you can reduce your APK
footprint by not compiling the ICU4J libraries into your APK; instead, you can
simply call out to them in the framework. (In this case, you may want to provide
<a href="{@docRoot}google/play/publishing/multiple-apks.html">multiple versions
- of your APK</a>, so users running versions of Android lower than Android N
+ of your APK</a>, so users running versions of Android lower than
+ Android 7.0 (API level 24)
can download a version of the app that contains the ICU4J libraries.)
</p>
@@ -51,17 +53,17 @@
<h2 id="relation">Relationship to ICU4J</h2>
<p>
- Android N exposes a subset of the ICU4J APIs via the
+ Android exposes a subset of the ICU4J APIs via the
<code>android.icu</code> package, rather than <code>com.ibm.icu</code>. The
Android framework may choose not to
- expose ICU4J APIs for various reasons; for example, Android N does not expose
+ expose ICU4J APIs for various reasons; for example, Android does not expose
some deprecated APIs or those that the ICU team have not yet declared as
stable. As the ICU team deprecates APIs in the future, Android will also mark
them as deprecated but will continue to include them.
</p>
<p class="table-caption"><strong>Table 1.</strong> ICU and CLDR versions used
- in Android N.</p>
+ in Android].</p>
<table>
<tr>
<th>Android API level</th>
@@ -69,7 +71,7 @@
<th>CLDR version</th>
</tr>
<tr>
-<td>Android N</td>
+<td>Android 7.0 (API level 24)</td>
<td>56</td>
<td>28</td>
</tr>
diff --git a/docs/html/guide/topics/ui/notifiers/notifications.jd b/docs/html/guide/topics/ui/notifiers/notifications.jd
index 6cd63f3..205da73 100644
--- a/docs/html/guide/topics/ui/notifiers/notifications.jd
+++ b/docs/html/guide/topics/ui/notifiers/notifications.jd
@@ -21,6 +21,8 @@
<ol>
<li><a href="#Updating">Updating notifications</a></li>
<li><a href="#Removing">Removing notifications</a></li>
+ <li><a href="#direct">Replying to notifications</a></li>
+ <li><a href="#bundle">Bundling notifications</a></li>
</ol>
</li>
<li><a href="#NotificationResponse">Preserving Navigation when Starting an Activity</a>
@@ -37,12 +39,18 @@
</li>
<li><a href="#metadata">Notification Metadata</a></li>
<li><a href="#Heads-up">Heads-up Notifications</a></li>
- <li><a href="#lockscreenNotification">Lock Screen Notifications</a></li>
+ <li><a href="#lockscreenNotification">Lock Screen Notifications</a>
<ol>
<li><a href="#visibility">Setting Visibility</a></li>
<li><a href="#controllingMedia">Controlling Media Playback on the Lock Screen</a></li>
</ol>
- <li><a href="#CustomNotification">Custom Notification Layouts</a></li>
+ </li>
+ <li><a href="#CustomNotification">Customizing Notifications</a>
+ <ol>
+ <li><a href="#custom">Custom Views</a></li>
+ <li><a href="#style">Messaging Style</a></li>
+ </ol>
+ </li>
</ol>
<h2>Key classes</h2>
@@ -394,6 +402,305 @@
all of the notifications you previously issued.
</li>
</ul>
+
+<!--------------------------------------------------------------- -->
+<h2 id="direct">Replying to notifications</h2>
+
+<p>
+ Starting in Android 7.0 (API level 24),
+ users can respond directly to text messages or update task lists
+ from within the notification
+ dialog. On a handheld, the inline reply action appears as an additional
+ button
+ displayed in the notification. When a user replies via keyboard, the system
+ attaches the text response to the intent
+ you had specified for the notification action and sends the intent to your
+ handheld app.
+</p>
+
+<img id="fig-reply-button" src="{@docRoot}images/android-7.0/inline-reply.png"
+ srcset="{@docRoot}images/android-7.0/inline-reply.png 1x,
+ {@docRoot}images/android-7.0/inline-reply_2x.png 2x"
+ width="400">
+<p class="img-caption">
+ <strong>Figure 1.</strong> The <strong>Reply</strong> action button.
+</p>
+
+<h3>Adding inline reply actions</h3>
+
+<p>To create a notification action that supports direct reply:
+</p>
+
+<ol>
+<li>Create an instance of {@link android.support.v4.app.RemoteInput.Builder}
+ that you can add to your notification
+action. This class's constructor accepts a string that the system uses as the key
+ for the text input. Later, your handheld app uses that key to retrieve the text
+ of the input.
+
+<pre>
+// Key for the string that's delivered in the action's intent.
+private static final String KEY_TEXT_REPLY = "key_text_reply";
+String replyLabel = getResources().getString(R.string.reply_label);
+RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
+ .setLabel(replyLabel)
+ .build();
+</pre>
+</li>
+<li>Attach the {@link android.support.v4.app.RemoteInput}
+ object to an action using <code>addRemoteInput()</code>.
+
+<pre>
+// Create the reply action and add the remote input.
+Notification.Action action =
+ new Notification.Action.Builder(R.drawable.ic_reply_icon,
+ getString(R.string.label), replyPendingIntent)
+ .addRemoteInput(remoteInput)
+ .build();
+</pre>
+</li>
+
+<li>Apply the action to a notification and issue the notification.
+
+<pre>
+// Build the notification and add the action.
+Notification newMessageNotification =
+ new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_message)
+ .setContentTitle(getString(R.string.title))
+ .setContentText(getString(R.string.content))
+ .addAction(action))
+ .build();
+
+// Issue the notification.
+NotificationManager notificationManager =
+ NotificationManager.from(mContext);
+notificationManager.notify(notificationId, newMessageNotification);
+
+</pre>
+</li>
+
+</ol>
+
+
+<p> The system prompts the user to input a response when they trigger the
+notification action. </p>
+
+<img id="fig-user-input" src="{@docRoot}images/android-7.0/inline-type-reply.png"
+ srcset="{@docRoot}images/android-7.0/inline-type-reply.png 1x,
+ {@docRoot}images/android-7.0/inline-type-reply_2x.png 2x"
+ width="300">
+<p class="img-caption">
+ <strong>Figure 2.</strong> The user inputs text from the notification shade.
+</p>
+
+<h3>
+ Retrieving user input from the inline reply
+</h3>
+
+<p>
+ To receive user input from the notification interface to the activity you
+ declared in the reply action's intent:
+</p>
+
+<ol>
+ <li>Call {@link android.support.v4.app.RemoteInput#getResultsFromIntent
+ getResultsFromIntent()} by passing the notification action’s intent as the
+ input parameter. This method returns a {@link android.os.Bundle} that
+ contains the text response.
+
+ <pre>
+Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+</pre>
+ </li>
+
+ <li>Query the bundle using the result key (provided to the {@link
+ android.support.v4.app.RemoteInput.Builder} constructor). You can complete
+ this process and retrieve the input text by creating a method, as in the
+ following code snippet:
+
+ <pre>
+// Obtain the intent that started this activity by calling
+// Activity.getIntent() and pass it into this method to
+// get the associated string.
+
+private CharSequence getMessageText(Intent intent) {
+ Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ return remoteInput.getCharSequence(KEY_TEXT_REPLY);
+ }
+ return null;
+ }
+</pre>
+ </li>
+
+ <li>Build and issue another notification, using the same notification ID that
+ you provided for the previous notification. The progress indicator
+ disappears from the notification interface to inform users of a successful
+ reply. When working with this new notification, use the context that gets
+ passed to the receiver's {@code onReceive()} method.
+
+ <pre>
+// Build a new notification, which informs the user that the system
+// handled their interaction with the previous notification.
+Notification repliedNotification =
+ new Notification.Builder(context)
+ .setSmallIcon(R.drawable.ic_message)
+ .setContentText(getString(R.string.replied))
+ .build();
+
+// Issue the new notification.
+NotificationManager notificationManager =
+ NotificationManager.from(context);
+notificationManager.notify(notificationId, repliedNotification);
+</pre>
+ </li>
+</ol>
+
+<p>
+ For interactive apps, such as chats, you can include additional
+ context when handling retrieved text. For example, these apps could show
+ multiple lines of chat history. When the user responds via {@link
+ android.support.v4.app.RemoteInput}, you can update the reply history
+ using the {@code setRemoteInputHistory()} method.
+</p>
+
+<p>
+ The notification must be either updated or cancelled after the app has
+ received remote input. When the user replies to a remote update
+ using Direct Reply,
+ do not cancel the notification. Instead, update the notification
+ to display the user's reply.
+ For notifications using {@code MessagingStyle}, you should add
+ the reply as the latest message. When using other templates, you can
+ append the user's reply to the remote-input history.
+</p>
+
+<!-------------------------------------------------------------------------- -->
+
+<h2 id="bundle">Bundling notifications</h2>
+
+<p>
+ Starting in Android 7.0 (API level 24),
+ Android provides developers with a new way to represent
+ a queue of notifications: <i>bundled notifications</i>. This is similar to the
+ <a href="{@docRoot}training/wearables/notifications/stacks.html">Notification
+ Stacks</a> feature in Android Wear. For example, if your app
+ creates notifications
+ for received messages, when more than one message is received, bundle the
+ notifications together as a single group. You can
+ use the {@link android.support.v4.app.NotificationCompat.Builder#setGroup
+ Builder.setGroup()} method to bundle similar notifications.</p>
+
+<p>
+ A notification group imposes a hierarchy on the notifications comprising it.
+ At the top of that hierarchy is a parent notification that displays summary
+ information for the group. The user can progressively
+ expand the notification group, and the system shows more information as the
+ user drills deeper. When the user expands the bundle, the system reveals more
+ information for all its child notifications; when the user
+ expands one of those notifications, the system reveals its entire content.
+</p>
+
+<img id="fig-bundles" src="{@docRoot}images/android-7.0/bundles.png"
+ srcset="{@docRoot}images/android-7.0/bundles.png 1x,
+ {@docRoot}images/android-7.0/bundles_2x.png 2x"
+ width="300">
+<p class="img-caption">
+ <strong>Figure 3.</strong> The user can progressively expand the notification
+ group.
+</p>
+
+<p class="note">
+ <strong>Note:</strong> If the same app sends four or more notifications
+ and does not specify a grouping, the
+ system automatically groups them together.
+</p>
+
+<p>To learn how to add notifications to a group, see
+<a href="{@docRoot}training/wearables/notifications/stacks.html#AddGroup">Add
+Each Notification to a Group</a>.</p>
+
+
+<h3 id="best-practices">Best practices for bundled notifications</h3>
+<p>This section provides guidelines about when to use notification groups instead
+of the {@link android.app.Notification.InboxStyle InboxStyle}
+notifications available in Android 6.0 and previous versions.</p>
+
+<h3>When to use bundled notifications</h3>
+
+<p>You should use notification groups only if all of the following conditions are
+true for your use case:</p>
+
+<ul>
+ <li>The child notifications are complete notifications and can be displayed
+ individually without the need for a group summary.</li>
+ <li>There is a benefit to surfacing the child notifications individually. For
+ example:
+ </li>
+ <ul>
+ <li>They are actionable, with actions specific to each child.</li>
+ <li>There is more information to the child that the user wants to read.</li>
+ </ul>
+</ul>
+
+<p>Examples of good use cases for notification groups include: a messaging app
+displaying a list of incoming messages, or an email app displaying a list of
+received emails.</p>
+
+<p>
+Examples of cases where a single notification is preferable
+ include individual messages from a single person, or a list representation of
+ single-line text items. You can use
+({@link android.app.Notification.InboxStyle InboxStyle} or
+{@link android.app.Notification.BigTextStyle BigTextStyle}) to accomplish
+this.
+</p>
+
+<h3 id ="post">Displaying bundled notifications</h3>
+
+<p>
+ The app should always post a group summary, even if the group contains just a
+ single child. The system will suppress the summary and directly display the
+ child notification if it only contains a single notification. This ensures
+ that the system can provide a consistent experience when the user swipes away
+ children of a group.
+</p>
+
+<h3>Peeking notifications</h3>
+
+<p>While the system usually displays child notifications as a group, you can set
+ them to temporarily appear as
+ <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#Heads-up">
+ heads-up notifications</a>. This feature is especially useful because it allows
+ immediate access to the most recent child and the actions associated with it.
+</p>
+
+
+<h3>Backwards compatibility</h3>
+
+<p>
+ Both notification groups and remote input have been a part of the {@link
+ android.app.Notification} API since Android 5.0 (API level 21) to support
+ Android Wear devices. If you've already built notifications with these APIs,
+ the only action you must take is to verify that the app behavior corresponds
+ to the guidelines described above, and to consider implementing {@code
+ setRemoteInputHistory()}.
+</p>
+
+<p>
+ In order to support backward compatibility, the same APIs are available with
+ the support library's {@link android.support.v4.app.NotificationCompat}
+ class, allowing you to build notifications that works on versions of Android
+ less than 5.0 (API level 21).
+ On handhelds and tablets, users only see the summary notification,
+ so an app should still have an inbox style or an equivalent notification
+ representative for the whole information content of the group. As Android
+ Wear devices allow users to see all child notifications even on older
+ platform levels, you should build child notifications regardless of API
+ level.
+</p>
+
<!-- ------------------------------------------------------------------------------------------ -->
<!-- ------------------------------------------------------------------------------------------ -->
<h2 id="NotificationResponse">Preserving Navigation when Starting an Activity</h2>
@@ -886,35 +1193,38 @@
<h3 id="controllingMedia">Controlling Media Playback on the Lock Screen</h3>
-<p>In Android 5.0 (API level 21) the lock screen no longer displays media controls
-based on the {@link android.media.RemoteControlClient}, which is now deprecated. Instead, use the
-{@link android.app.Notification.MediaStyle} template with the
-{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()}
-method, which converts actions into clickable icons.</p>
+<p>
+ In Android 5.0 (API level 21) the lock screen no longer displays media
+ controls based on the {@link android.media.RemoteControlClient}, which is
+ now deprecated. Instead, use the {@link
+ android.support.v7.app.NotificationCompat.MediaStyle} template with the
+ {@link
+ android.support.v4.app.NotificationCompat.Builder#addAction(android.support.v4.app.NotificationCompat.Action)
+ addAction()} method, which converts actions into clickable icons.
+</p>
-<p class="note"><strong>Note:</strong> The template and the
-{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()}
-method are not included in the support library, so these features run in Android 5.0 and higher
-only.</p>
-
-<p>To display media playback controls on the lock screen in Android 5.0, set the visibility
-to {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, as described above. Then add
-the actions and set the {@link android.app.Notification.MediaStyle} template, as described in the
-following sample code:</p>
+<p>
+ To display media playback controls on the lock screen in Android 5.0, set
+ the visibility to {@link
+ android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, as described
+ above. Then add the actions and set the {@link
+ android.support.v7.app.NotificationCompat.MediaStyle} template, as described
+ in the following sample code:
+</p>
<pre>
-Notification notification = new Notification.Builder(context)
+Notification notification = new NotificationCompat.Builder(context)
// Show controls on lock screen even when user hides sensitive content.
- .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_stat_player)
// Add media control buttons that invoke intents in your media service
.addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
.addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
.addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
// Apply the media style template
- .setStyle(new Notification.MediaStyle()
- .setShowActionsInCompactView(1 /* #1: pause button */)
- .setMediaSession(mMediaSession.getSessionToken())
+ .setStyle(new NotificationCompat.MediaStyle()
+ .setShowActionsInCompactView(1 /* #1: pause button */)
+ .setMediaSession(mMediaSession.getSessionToken()))
.setContentTitle("Wonderful music")
.setContentText("My Awesome Band")
.setLargeIcon(albumArtBitmap)
@@ -984,3 +1294,69 @@
standard notification layout text. If you use the same style in applications that target Android
2.3 or higher, you'll ensure that your text is visible against the display background.
</p>
+
+<!-- ------------------------------------------------------------------------ -->
+<h3 id="custom"> Custom Views</h3>
+<p>
+ Starting from Android 7.0 (API level 24),
+ you can customize notification views and
+ still obtain system decorations like notification headers, actions, and
+ expandable layouts.
+</p>
+
+<p>To enable this capability, Android provides the following APIs to style your
+ custom view:</p>
+
+<dl>
+<dt>
+{@code DecoratedCustomViewStyle()}</dt>
+<dd> Styles notifications other than media
+notifications.</dd>
+<dt>
+{@code DecoratedMediaCustomViewStyle()}</dt>
+<dd> Styles media notifications.</dd>
+</dl>
+
+<p>To use this API, call the {@code setStyle()} method, passing to it
+the desired custom view style.</p>
+
+<p>This snippet shows how to construct a custom notification object with the
+{@code DecoratedCustomViewStyle()} method.</p>
+
+<pre>
+Notification notification = new Notification.Builder()
+ .setSmallIcon(R.drawable.ic_stat_player)
+ .setLargeIcon(albumArtBitmap))
+ .setCustomContentView(contentView);
+ .setStyle(new Notification.DecoratedCustomViewStyle())
+ .build();
+
+</pre>
+
+<!-- ----------------------------------------------------------------------- -->
+
+<h3 id="style">Messaging Style</h3>
+<p>
+ Starting in Android 7.0 (API level 24),
+ Android provides an API for customizing the style of a notification.
+ Using the <code>MessagingStyle</code> class, you can change several of the
+ labels displayed on the notification, including the conversation title,
+ additional messages, and the content view for the notification.
+</p>
+
+<p>
+ The following code snippet demonstrates how to customize a notification's
+ style using the <code>MessagingStyle</code> class.
+</p>
+
+<pre>
+ Notification notification = new Notification.Builder()
+ .setStyle(new Notification.MessagingStyle("Me")
+ .setConversationTitle("Team lunch")
+ .addMessage("Hi", timestamp1, null) // Pass in null for user.
+ .addMessage("What's up?", timestamp2, "Coworker")
+ .addMessage("Not much", timestamp3, null)
+ .addMessage("How about lunch?", timestamp4, "Coworker"))
+ .build();
+</pre>
+
diff --git a/docs/html/preview/images/bundles.png b/docs/html/images/android-7.0/bundles.png
similarity index 100%
rename from docs/html/preview/images/bundles.png
rename to docs/html/images/android-7.0/bundles.png
Binary files differ
diff --git a/docs/html/preview/images/bundles_2x.png b/docs/html/images/android-7.0/bundles_2x.png
similarity index 100%
rename from docs/html/preview/images/bundles_2x.png
rename to docs/html/images/android-7.0/bundles_2x.png
Binary files differ
diff --git a/docs/html/preview/images/inline-reply.png b/docs/html/images/android-7.0/inline-reply.png
similarity index 100%
rename from docs/html/preview/images/inline-reply.png
rename to docs/html/images/android-7.0/inline-reply.png
Binary files differ
diff --git a/docs/html/preview/images/inline-reply_2x.png b/docs/html/images/android-7.0/inline-reply_2x.png
similarity index 100%
rename from docs/html/preview/images/inline-reply_2x.png
rename to docs/html/images/android-7.0/inline-reply_2x.png
Binary files differ
diff --git a/docs/html/preview/images/inline-type-reply.png b/docs/html/images/android-7.0/inline-type-reply.png
similarity index 100%
rename from docs/html/preview/images/inline-type-reply.png
rename to docs/html/images/android-7.0/inline-type-reply.png
Binary files differ
diff --git a/docs/html/preview/images/inline-type-reply_2x.png b/docs/html/images/android-7.0/inline-type-reply_2x.png
similarity index 100%
rename from docs/html/preview/images/inline-type-reply_2x.png
rename to docs/html/images/android-7.0/inline-type-reply_2x.png
Binary files differ
diff --git a/docs/html/preview/images/notifications-1.png b/docs/html/images/android-7.0/notifications-1.png
similarity index 100%
rename from docs/html/preview/images/notifications-1.png
rename to docs/html/images/android-7.0/notifications-1.png
Binary files differ
diff --git a/docs/html/preview/images/notifications-2.png b/docs/html/images/android-7.0/notifications-2.png
similarity index 100%
rename from docs/html/preview/images/notifications-2.png
rename to docs/html/images/android-7.0/notifications-2.png
Binary files differ
diff --git a/docs/html/preview/images/notifications-3.png b/docs/html/images/android-7.0/notifications-3.png
similarity index 100%
rename from docs/html/preview/images/notifications-3.png
rename to docs/html/images/android-7.0/notifications-3.png
Binary files differ
diff --git a/docs/html/preview/images/notifications-card.png b/docs/html/images/android-7.0/notifications-card.png
similarity index 100%
rename from docs/html/preview/images/notifications-card.png
rename to docs/html/images/android-7.0/notifications-card.png
Binary files differ
diff --git a/docs/html/preview/images/pip-active.png b/docs/html/images/android-7.0/pip-active.png
similarity index 100%
rename from docs/html/preview/images/pip-active.png
rename to docs/html/images/android-7.0/pip-active.png
Binary files differ
diff --git a/docs/html/preview/images/pip-button.png b/docs/html/images/android-7.0/pip-button.png
similarity index 100%
rename from docs/html/preview/images/pip-button.png
rename to docs/html/images/android-7.0/pip-button.png
Binary files differ
diff --git a/docs/html/images/android-7.0/scoped-directory-access-dont-ask.png b/docs/html/images/android-7.0/scoped-directory-access-dont-ask.png
new file mode 100644
index 0000000..6682948
--- /dev/null
+++ b/docs/html/images/android-7.0/scoped-directory-access-dont-ask.png
Binary files differ
diff --git a/docs/html/images/android-7.0/scoped-directory-access-dont-ask_2x.png b/docs/html/images/android-7.0/scoped-directory-access-dont-ask_2x.png
new file mode 100644
index 0000000..be717f9
--- /dev/null
+++ b/docs/html/images/android-7.0/scoped-directory-access-dont-ask_2x.png
Binary files differ
diff --git a/docs/html/images/android-7.0/scoped-directory-access-framed.png b/docs/html/images/android-7.0/scoped-directory-access-framed.png
new file mode 100644
index 0000000..e4d619a
--- /dev/null
+++ b/docs/html/images/android-7.0/scoped-directory-access-framed.png
Binary files differ
diff --git a/docs/html/images/android-7.0/scoped-directory-access-framed_2x.png b/docs/html/images/android-7.0/scoped-directory-access-framed_2x.png
new file mode 100644
index 0000000..fe17bce
--- /dev/null
+++ b/docs/html/images/android-7.0/scoped-directory-access-framed_2x.png
Binary files differ
diff --git a/docs/html/preview/features/notification-updates.jd b/docs/html/preview/features/notification-updates.jd
deleted file mode 100644
index fd65e12..0000000
--- a/docs/html/preview/features/notification-updates.jd
+++ /dev/null
@@ -1,400 +0,0 @@
-page.title=Notifications
-page.tags=notifications
-helpoutsWidget=true
-page.image=/preview/images/notifications-card.png
-
-trainingnavtop=true
-
-@jd:body
-
-<div id="qv-wrapper">
-<div id="qv">
-
-<!-- table of contents -->
-<h2>This document includes</h2>
-<ol>
- <li><a href="#direct">Direct Reply</a></li>
- <li><a href="#bundle">Bundled Notifications</a></li>
- <li><a href="#custom">Custom Views</a></li>
- <li><a href="#style">Messaging Style</a></li>
-</ol>
-
-</div>
-</div>
-
-<p>Android N introduces several new APIs that allow apps to post
-notifications that are highly visible and interactive.</p>
-
-<p>Android N extends the existing {@link android.support.v4.app.RemoteInput}
-notification API to support inline replies on handsets. This feature allows users
- to quickly respond from the notification shade without visiting your app.</p>
-
-<p>
- Android N also allows you to bundle similar notifications to
- appear as a single notification. To make this possible, Android N uses the existing {@link
- android.support.v4.app.NotificationCompat.Builder#setGroup
- NotificationCompat.Builder.setGroup()} method. Users can expand each of the
- notifications, and perform actions such as reply and dismiss on each of the
- notifications, individually from the notification shade.
-</p>
-
-<p>Last, Android N also adds new APIs that allow you to leverage system
-decorations in your app’s customized notification views. These APIs help
-ensure that the notification views share a consistent presentation with
-standard templates.</p>
-
-<p>This document highlights some of the key changes that you should take into
- account when using the new notification features in your apps.</p>
-
-<h2 id="direct">Direct Reply</h2>
-
-<p>With the Direct Reply feature in Android N, users can quickly
-respond to text messages or update task lists directly within the notification
-interface. On a handheld, the inline reply action appears as an additional button
- attached to the notification. When a user replies via keyboard, the system attaches
- the text response to the intent
- you had specified for the notification action and sends the intent to your
- handheld app.
-
-
-<img id="fig-reply-button" src="{@docRoot}preview/images/inline-reply.png"
- srcset="{@docRoot}preview/images/inline-reply.png 1x,
- {@docRoot}preview/images/inline-reply_2x.png 2x"
- width="400">
-<p class="img-caption">
- <strong>Figure 1.</strong> Android N adds the <strong>Reply</strong>
- action button.
-</p>
-
-<h3>Adding inline reply actions</h3>
-
-<p>To create a notification action that supports direct reply:
-</p>
-
-<ol>
-<li>Create an instance of {@link android.support.v4.app.RemoteInput.Builder}
- that you can add to your notification
-action. This class's constructor accepts a string that the system uses as the key
- for the text input. Later, your handheld app uses that key to retrieve the text
- of the input.
-
-<pre>
-// Key for the string that's delivered in the action's intent.
-private static final String KEY_TEXT_REPLY = "key_text_reply";
-String replyLabel = getResources().getString(R.string.reply_label);
-RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
- .setLabel(replyLabel)
- .build();
-</pre>
-</li>
-<li>Attach the {@link android.support.v4.app.RemoteInput}
- object to an action using <code>addRemoteInput()</code>.
-
-<pre>
-// Create the reply action and add the remote input.
-Notification.Action action =
- new Notification.Action.Builder(R.drawable.ic_reply_icon,
- getString(R.string.label), replyPendingIntent)
- .addRemoteInput(remoteInput)
- .build();
-</pre>
-</li>
-
-<li>Apply the action to a notification and issue the notification.
-
-<pre>
-// Build the notification and add the action.
-Notification newMessageNotification =
- new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_message)
- .setContentTitle(getString(R.string.title))
- .setContentText(getString(R.string.content))
- .addAction(action))
- .build();
-
-// Issue the notification.
-NotificationManager notificationManager =
- NotificationManager.from(mContext);
-notificationManager.notify(notificationId, newMessageNotification);
-
-</pre>
-</li>
-
-</ol>
-
-
-<p> The system prompts the user to input a response when they trigger the
-notification action. </p>
-
-<img id="fig-user-input" src="{@docRoot}preview/images/inline-type-reply.png"
- srcset="{@docRoot}preview/images/inline-type-reply.png 1x,
- {@docRoot}preview/images/inline-type-reply_2x.png 2x"
- width="300">
-<p class="img-caption">
- <strong>Figure 2.</strong> The user inputs text from the notification shade.
-</p>
-
-<h3>
- Retrieving user input from the inline reply
-</h3>
-
-<p>
- To receive user input from the notification interface to the activity you
- declared in the reply action's intent:
-</p>
-
-<ol>
- <li>Call {@link android.support.v4.app.RemoteInput#getResultsFromIntent
- getResultsFromIntent()} by passing the notification action’s intent as the
- input parameter. This method returns a {@link android.os.Bundle} that
- contains the text response.
-
- <pre>
-Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
-</pre>
- </li>
-
- <li>Query the bundle using the result key (provided to the {@link
- android.support.v4.app.RemoteInput.Builder} constructor). You can complete
- this process and retrieve the input text by creating a method, as in the
- following code snippet:
-
- <pre>
-// Obtain the intent that started this activity by calling
-// Activity.getIntent() and pass it into this method to
-// get the associated string.
-
-private CharSequence getMessageText(Intent intent) {
- Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
- if (remoteInput != null) {
- return remoteInput.getCharSequence(KEY_TEXT_REPLY);
- }
- return null;
- }
-</pre>
- </li>
-
- <li>Build and issue another notification, using the same notification ID that
- you provided for the previous notification. The progress indicator
- disappears from the notification interface to inform users of a successful
- reply. When working with this new notification, use the context that gets
- passed to the receiver's {@code onReceive()} method.
-
- <pre>
-// Build a new notification, which informs the user that the system
-// handled their interaction with the previous notification.
-Notification repliedNotification =
- new Notification.Builder(context)
- .setSmallIcon(R.drawable.ic_message)
- .setContentText(getString(R.string.replied))
- .build();
-
-// Issue the new notification.
-NotificationManager notificationManager =
- NotificationManager.from(context);
-notificationManager.notify(notificationId, repliedNotification);
-</pre>
- </li>
-</ol>
-
-<p>
- For interactive apps, such as chats, it could be useful to include additional
- context when handling retrieved text. For example, these apps could show
- multiple lines of chat history. When the user responds via {@link
- android.support.v4.app.RemoteInput}, you can update the reply history
- using the {@code setRemoteInputHistory()} method.
-</p>
-
-<p>
- The notification must be either updated or cancelled after the app has
- received remote input. When the user replies to a remote update
- using Direct Reply,
- do not cancel the notification. Instead, update the notification to display the user's reply.
-For notifications using {@code MessagingStyle}, you should add
-the reply as the latest message. When using other templates, you can
-append the user's reply to the remote-input history.
-</p>
-
-<h2 id="bundle">Bundled Notifications</h2>
-
-<p>Android N provides developers with a new way to represent
- a queue of notifications: <i>bundled notifications</i>. This is similar to the
- <a href="{@docRoot}training/wearables/notifications/stacks.html">Notification
- Stacks</a> feature in Android Wear. For example, if your app creates notifications
- for received messages, when more than one message is received, bundle the
- notifications together as a single group. You can
- use the existing {@link android.support.v4.app.NotificationCompat.Builder#setGroup
-Builder.setGroup()} method to bundle similar notifications.</p>
-
-<p>
- A notification group imposes a hierarchy on the notifications comprising it.
- At the top of that hierarchy is a parent notification that displays summary
- information for the group. The user can progressively
- expand the notification group, and the system shows more information as the
- user drills deeper. When the user expands the bundle, the system reveals more
- information for all its child notifications; when the user
- expands one of those notifications, the system reveals its entire content.
-</p>
-
-<img id="fig-bundles" src="{@docRoot}preview/images/bundles.png"
- srcset="{@docRoot}preview/images/bundles.png 1x,
- {@docRoot}preview/images/bundles_2x.png 2x"
- width="300">
-<p class="img-caption">
- <strong>Figure 3.</strong> The user can progressively expand the notification
- group.
-</p>
-
-<p class="note">
- <strong>Note:</strong> If the same app sends four or more notifications
- and does not specify a grouping, the
- system automatically groups them together.
-</p>
-
-<p>To learn how to add notifications to a group, see
-<a href="{@docRoot}training/wearables/notifications/stacks.html#AddGroup">Add
-Each Notification to a Group</a>.</p>
-
-
-<h3 id="best-practices">Best practices for bundled notifications</h3>
-<p>This section provides guidelines about when to use notification groups instead
-of the {@link android.app.Notification.InboxStyle InboxStyle}
-notifications that have been available in earlier versions of the
-Android platform.</p>
-
-<h3>When to use bundled notifications</h3>
-
-<p>You should use notification groups only if all of the following conditions are
-true for your use case:</p>
-
-<ul>
- <li>The child notifications are complete notifications and can be displayed
- individually without the need for a group summary.</li>
- <li>There is a benefit to surfacing the child notifications individually. For
- example:
- </li>
- <ul>
- <li>They are actionable, with actions specific to each child.</li>
- <li>There is more information to the child that the user wants to read.</li>
- </ul>
-</ul>
-
-<p>Examples of good use cases for notification groups include: a messaging app
-displaying a list of incoming messages, or an email app displaying a list of
-received emails.</p>
-
-<p>
-Examples of cases where a single notification is preferable
- include individual messages from a single person, or a list representation of
- single-line text items. You can use
-({@link android.app.Notification.InboxStyle InboxStyle} or
-{@link android.app.Notification.BigTextStyle BigTextStyle}) to accomplish
-this.
-</p>
-
-<h3 id ="post">Displaying bundled notifications</h3>
-
-<p>
- The app should always post a group summary, even if the group contains just a
- single child. The system will suppress the summary and directly display the
- child notification if it only contains a single notification. This ensures
- that the system can provide a consistent experience when the user swipes away
- children of a group.
-</p>
-
-<p class="note">
- <strong>Note:</strong> This version of Android N does not yet
- suppress the summary for notification groups containing a single child. This
- functionality will be added in a later version of Android N.
-</p>
-
-<h3>Peeking notifications</h3>
-
-<p>While the system usually displays child notifications as a group, you can set
- them to temporarily appear as
- <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#Heads-up">
- heads-up notifications</a>. This feature is especially useful because it allows
- immediate access to the most recent child and the actions associated with it.
-</p>
-
-
-<h3>Backwards compatibility</h3>
-
-<p>
- Both notification groups and remote input have been a part of the {@link
- android.app.Notification} API since Android 5.0 (API level 21) to support
- Android Wear devices. If you've already built notifications with these APIs,
- the only action you must take is to verify that the app behavior corresponds
- to the guidelines described above, and to consider implementing {@code
- setRemoteInputHistory()}.
-</p>
-
-<p>
- In order to support backward compatibility, the same APIs are available with
- the support library's {@link android.support.v4.app.NotificationCompat}
- class, allowing you to build notifications that works on earlier Android
- versions. On handhelds and tablets, users only see the summary notification,
- so an app should still have an inbox style or an equivalent notification
- representative for the whole information content of the group. As Android
- Wear devices allow users to see all child notifications even on older
- platform levels, you should build child notifications regardless of API
- level.
-</p>
-
-<h2 id="custom"> Custom Views</h2>
-<p>Starting from Android N, you can customize notification views and
-still obtain system decorations like notification headers, actions, and
-expandable layouts.</p>
-
-<p>To enable this capability, Android N adds the following APIs to style your
- custom view:</p>
-
-<dl>
-<dt>
-{@code DecoratedCustomViewStyle()}</dt>
-<dd> Styles notifications other than media
-notifications.</dd>
-<dt>
-{@code DecoratedMediaCustomViewStyle()}</dt>
-<dd> Styles media notifications.</dd>
-</dl>
-
-<p>To use this new API, call the {@code setStyle()} method, passing to it
-the desired custom view style.</p>
-
-<p>This snippet shows how to construct a custom notification object with the
-{@code DecoratedCustomViewStyle()} method.</p>
-
-<pre>
-Notification notification = new Notification.Builder()
- .setSmallIcon(R.drawable.ic_stat_player)
- .setLargeIcon(albumArtBitmap))
- .setCustomContentView(contentView);
- .setStyle(new Notification.DecoratedCustomViewStyle())
- .build();
-
-</pre>
-
-<h2 id="style">Messaging Style</h2>
-<p>
- Android N introduces a new API for customizing the style of a notification.
- Using the <code>MessagingStyle</code> class, you can change several of the
- labels displayed on the notification, including the conversation title,
- additional messages, and the content view for the notification.
-</p>
-
-<p>
- The following code snippet demonstrates how to customize a notification's
- style using the <code>MessagingStyle</code> class.
-</p>
-
-<pre>
- Notification notification = new Notification.Builder()
- .setStyle(new Notification.MessagingStyle("Me")
- .setConversationTitle("Team lunch")
- .addMessage("Hi", timestamp1, null) // Pass in null for user.
- .addMessage("What's up?", timestamp2, "Coworker")
- .addMessage("Not much", timestamp3, null)
- .addMessage("How about lunch?", timestamp4, "Coworker"))
- .build();
-</pre>
diff --git a/docs/html/preview/features/tv-recording-api.jd b/docs/html/preview/features/tv-recording-api.jd
deleted file mode 100644
index 6619821..0000000
--- a/docs/html/preview/features/tv-recording-api.jd
+++ /dev/null
@@ -1,142 +0,0 @@
-page.title=TV Recording
-page.keywords=preview,sdk,tv,recording
-page.tags=androidn
-page.image=images/cards/card-nyc_2x.jpg
-
-@jd:body
-
-<div id="qv-wrapper">
-<div id="qv">
- <h2>In this document</h2>
- <ol>
- <li><a href="#supporting">Indicating Support for Recording</a></li>
- <li><a href="#recording">Recording a Session</a></li>
- <li><a href="#errors">Handling Recording Errors</a></li>
- <li><a href="#sessions">Managing Recorded Sessions</a></li>
- <li><a href="#best">Best Practices</a></li>
- </ol>
-</div>
-</div>
-
-<p>TV input services let the user pause and resume channel playback via
-time-shifting APIs. Android N expands on time-shifting
-by letting the user save multiple recorded sessions.</p>
-
-<p>Users can schedule recordings in advance, or start a recording as they watch
-a program. Once the system has saved a recording, the user can browse, manage,
-and play back the recording using the system TV app.</p>
-
-<p>If you want to provide recording functionality for your TV input service,
-you must indicate to the system that your app supports recording, implement
-the ability to record programs, handle and communicate any errors that occur
-during recording, and manage your recorded sessions.</p>
-
-<p class="note"><strong>Note:</strong> The Live Channels app does not yet
-provide a way for users to create or access recordings. Until changes are
-made to the Live Channels app, it may be difficult to fully test the recording
-experience for your TV input service.</p>
-
-<h2 id="supporting">Indicating Support for Recording</h2>
-
-<p>To tell the system that your TV input service supports recording, set
-the <code>android:canRecord</code> attribute in your service metadata XML file
-to <code>true</code>:
-</p>
-
-<pre>
-<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
- <b>android:canRecord="true"</b>
- android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity" />
-</pre>
-
-<p>For more information on the service metadata file, see
-<a href="{@docRoot}training/tv/tif/tvinput.html#manifest">Declare Your TV Input
-Service in the Manifest</a>.
-</p>
-
-<p>Alternatively, you can indicate recording support in your code using
-these steps:</p>
-
-<ol>
-<li>In your <code>TvInputService.onCreate()</code> method, create a new
-<code>TvInputInfo</code> object using the <code>TvInputInfo.Builder</code>
-class.</li>
-<li>When creating the new <code>TvInputInfo</code> object, call
-<code>setCanRecord(true)</code> before calling <code>build()</code> to
-indicate your service supports recording.</li>
-<li>Register your <code>TvInputInfo</code> object with the system by calling
-<code>TvInputManager.updateTvInputInfo()</code>.</li>
-</ol>
-
-<h2 id="recording">Recording a Session</h2>
-
-<p>After your TV input service registers that it supports recording
-functionality, the system calls your
-<code>TvInputService.onCreateRecordingSession()</code> when it needs to access
-your app's recording implementation. Implement your own
-<code>TvInputService.RecordingSession</code> subclass and return it
-when the <code>onCreateRecordingSession()</code> callback
-fires. This subclass is responsible for switching to the correct channel data,
-recording the requested data, and communicating recording status and errors to
-the system.</p>
-
-<p>When the system calls <code>RecordingSession.onTune()</code>, passing in a
-channel URI, tune to the channel that the URI specifies. Notify the system that
-your app has tuned to the desired channel by calling <code>notifyTuned()</code>,
-or, if your app could not tune to the proper channel, call
-<code>notifyError()</code>.</p>
-
-<p>The system next invokes the <code>RecordingSession.onStartRecording()</code>
-callback. Your app must start recording immediately. When the system invokes
-this callback, it may provide a URI that contains information about the program
-that is about to be recorded. When the recording is done, you need to copy this
-data to the <code>RecordedPrograms</code> data table.</p>
-
-<p>Finally, the system calls <code>RecordingSession.onStopRecording()</code>.
-At this point, your app must stop recording immediately. You also need to
-create an entry in the <code>RecordedPrograms</code> table. This entry should
-include the recorded session data URI in the
-<code>RecordedPrograms.COLUMN_RECORDING_DATA_URI</code> column, and any program
-information that the system provided in the initial call to
-<code>onStartRecording()</code>.</p>
-
-<p>For more details on how to access the <code>RecordedPrograms</code> table
-see <a href="#sessions">Managing Recorded Sessions</a>.</p>
-
-<h2 id="errors">Handling Recording Errors</h2>
-
-<p>If an error occurs during recording, rendering the recorded data unusable,
-notify the system by calling <code>RecordingSession.notifyError()</code>.
-Similarly, you can call <code>notifyError()</code> after a recording session is
-created to let the system know that your app can no longer record sessions.</p>
-
-<p>If an error occurs during recording, but you'd like to provide a usable
-partial recording to users for playback, call
-<code>RecordingSession.notifyRecordingStopped()</code> to enable the system to
-use the partial session.</p>
-
-<h2 id="sessions">Managing Recorded Sessions</h2>
-
-<p>The system maintains information for all recorded sessions from all
-recording-capable channel apps in the <code>TvContract.RecordedPrograms</code>
-content provider table. This information is accessible via the
-<code>RecordedPrograms.Uri</code> content URI. Use content provider APIs to
-read, add, and delete entries from this table.</p>
-
-<p>For more information on working with content provider data see
-<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
-Content Provider Basics</a> .</p>
-
-<h2 id="best">Best Practices</h2>
-
-<p>TV devices may have limited storage, so use your best judgment when
-allocating storage to save recorded sessions. Use
-<code>RecordingCallback.onError(RECORDING_ERROR_INSUFFICIENT_SPACE)</code> when
-there isn't enough space to save a recorded session.</p>
-
-<p>When the user initiates recording, you should start recording data as soon
-as possible. To facilitate this, complete any up-front time-consuming tasks,
-like accessing and allocating storage space, when the system invokes the
-<code>onCreateRecordingSession()</code> callback. Doing so lets you start
-recording immediately when the <code>onStartRecording()</code> callback
-fires.</p>
diff --git a/docs/html/preview/images/scoped-folder-access-dont-ask.png b/docs/html/preview/images/scoped-folder-access-dont-ask.png
deleted file mode 100644
index 5c505d9..0000000
--- a/docs/html/preview/images/scoped-folder-access-dont-ask.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/scoped-folder-access-dont-ask_2x.png b/docs/html/preview/images/scoped-folder-access-dont-ask_2x.png
deleted file mode 100644
index 612b69f..0000000
--- a/docs/html/preview/images/scoped-folder-access-dont-ask_2x.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/scoped-folder-access-framed.png b/docs/html/preview/images/scoped-folder-access-framed.png
deleted file mode 100644
index 0169e41..0000000
--- a/docs/html/preview/images/scoped-folder-access-framed.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/scoped-folder-access-framed_2x.png b/docs/html/preview/images/scoped-folder-access-framed_2x.png
deleted file mode 100644
index fd59ef1..0000000
--- a/docs/html/preview/images/scoped-folder-access-framed_2x.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/training/_book.yaml b/docs/html/training/_book.yaml
index 0e2083a..23d9c9e 100644
--- a/docs/html/training/_book.yaml
+++ b/docs/html/training/_book.yaml
@@ -695,6 +695,8 @@
path: /training/tv/playback/guided-step.html
- title: Enabling Background Playback
path: /training/tv/playback/options.html
+ - title: Adding Picture-in-picture
+ path: /training/tv/playback/picture-in-picture.html
- title: Helping Users Find Content on TV
path: /training/tv/discovery/index.html
path_attributes:
@@ -724,6 +726,8 @@
path: /training/tv/tif/channel.html
- title: Managing User Interaction
path: /training/tv/tif/ui.html
+ - title: Supporting Content Recording
+ path: /training/tv/tif/content-recording.html
- title: TV Apps Checklist
path: /training/tv/publishing/checklist.html
path_attributes:
@@ -1388,6 +1392,16 @@
path_attributes:
- name: description
value: How to create an application that enforces security policies on devices.
+ - title: Supporting Direct Boot
+ path: /training/articles/direct-boot.html
+ path_attributes:
+ - name: description
+ value: How use device encrypted storage during Direct Boot mode.
+ - title: Using Scoped Directory Access
+ path: /training/articles/scoped-directory-access.html
+ path_attributes:
+ - name: description
+ value: How to use scoped directory access to request access to external storage directories.
- title: Best Practices for Permissions & Identifiers
path: /training/best-permissions-ids.html
diff --git a/docs/html/preview/features/direct-boot.jd b/docs/html/training/articles/direct-boot.jd
similarity index 86%
rename from docs/html/preview/features/direct-boot.jd
rename to docs/html/training/articles/direct-boot.jd
index 60f6141..ea2686e 100644
--- a/docs/html/preview/features/direct-boot.jd
+++ b/docs/html/training/articles/direct-boot.jd
@@ -1,12 +1,10 @@
-page.title=Direct Boot
-page.keywords=preview,sdk,direct boot
-page.tags=androidn
-page.image=images/cards/card-nyc_2x.jpg
+page.title=Supporting Direct Boot
+page.keywords=direct boot
@jd:body
-<div id="qv-wrapper">
-<div id="qv">
+<div id="tb-wrapper">
+<div id="tb">
<h2>In this document</h2>
<ol>
<li><a href="#run">Requesting Access to Run During Direct Boot</a></li>
@@ -19,7 +17,7 @@
</div>
</div>
-<p>Android N runs in a secure, <i>Direct Boot</i> mode
+<p>Android 7.0 runs in a secure, <i>Direct Boot</i> mode
when the device has been powered on but the user has not unlocked the
device. To support this, the system provides two storage locations for data:</p>
@@ -63,21 +61,23 @@
<code>android:directBootAware</code> attribute to true in your manifest.<p>
<p>Encryption aware components can register to receive a
-<code>LOCKED_BOOT_COMPLETED</code> broadcast message from the
+{@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED
+ACTION_LOCKED_BOOT_COMPLETED} broadcast message from the
system when the device has been restarted. At this point device encrypted
storage is available, and your component can execute tasks that need to be
run during Direct Boot mode, such as triggering a scheduled alarm.</p>
<p>The following code snippet is an example of how to register a
{@link android.content.BroadcastReceiver} as encryption aware, and add an
-intent filter for <code>LOCKED_BOOT_COMPLETED</code>, in the app manifest:</p>
+intent filter for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED
+ACTION_LOCKED_BOOT_COMPLETED}, in the app manifest:</p>
<pre>
<receiver
android:directBootAware="true" >
...
<intent-filter>
- <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+ <action android:name="android.intent.action.ACTION_LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
</pre>
@@ -89,7 +89,8 @@
<p>To access device encrypted storage, create a second
{@link android.content.Context} instance by calling
-<code>Context.createDeviceProtectedStorageContext()</code>. All storage API
+{@link android.content.Context#createDeviceProtectedStorageContext
+Context.createDeviceProtectedStorageContext()}. All storage API
calls made using this context access the device encrypted storage. The
following example accesses the device encrypted storage and opens an existing
app data file:</p>
@@ -120,7 +121,8 @@
</p>
<ul>
<li>If your app has foreground processes that need immediate notification,
-listen for the {@code ACTION_USER_UNLOCKED} message.</li>
+listen for the {@link android.content.Intent#ACTION_USER_UNLOCKED
+ACTION_USER_UNLOCKED} message.</li>
<li>If your app only uses background processes that can act on a delayed
notification, listen for the
{@link android.content.Intent#ACTION_BOOT_COMPLETED ACTION_BOOT_COMPLETED}
@@ -128,14 +130,17 @@
</ul>
<p>If the user has unlocked the device, you can find out by calling
-<code>UserManager.isUserUnlocked()</code>.</p>
+{@link android.os.UserManager#isUserUnlocked UserManager.isUserUnlocked()}.
+</p>
<h2 id="migrating">Migrating Existing Data</h2>
<p>If a user updates their device to use Direct Boot mode, you might have
existing data that needs to get migrated to device encrypted storage. Use
-<code>Context.moveSharedPreferencesFrom()</code> and
-<code>Context.moveDatabaseFrom()</code> to migrate preference and database
+{@link android.content.Context#moveSharedPreferencesFrom
+Context.moveSharedPreferencesFrom()} and
+{@link android.content.Context#moveDatabaseFrom
+Context.moveDatabaseFrom()} to migrate preference and database
data between credential encrypted storage and device encrypted storage.</p>
<p>Use your best judgment when deciding what data to migrate from credential
@@ -146,13 +151,13 @@
<h2 id="testing">Testing Your Encryption Aware App</h2>
-<p>Test your encryption aware app using the new Direct Boot mode. There are
+<p>Test your encryption aware app with Direct Boot mode enabled. There are
two ways to enable Direct Boot.</p>
<p class="caution"><strong>Caution:</strong> Enabling Direct Boot
wipes all user data on the device.</p>
-<p>On supported devices with Android N installed, enable
+<p>On supported devices with Android 7.0 installed, enable
Direct Boot by doing one of the following:</p>
<ul>
@@ -194,14 +199,14 @@
{@link android.app.admin.DevicePolicyManager#getStorageEncryptionStatus
DevicePolicyManager.getStorageEncryptionStatus()} to check the current
encryption status of the device. If your app is targeting an API level
-lower than Android N,
+lower than 24.0 (Android 7.0),
{@link android.app.admin.DevicePolicyManager#getStorageEncryptionStatus
getStorageEncryptionStatus()} will return
{@link android.app.admin.DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE
ENCRYPTION_STATUS_ACTIVE} if the device is either using full-disk encryption,
or file-based encryption with Direct Boot. In both of these cases, data is
always stored encrypted at rest. If your app is targeting an API level of
-Android N or higher,
+24.0 or higher,
{@link android.app.admin.DevicePolicyManager#getStorageEncryptionStatus
getStorageEncryptionStatus()} will return
{@link android.app.admin.DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE
@@ -212,7 +217,7 @@
with Direct Boot.</p>
<p>If you build a device administration app
-that targets Android N, make sure to check for both
+that targets Android 7.0, make sure to check for both
{@link android.app.admin.DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE
ENCRYPTION_STATUS_ACTIVE} and
{@link android.app.admin.DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE_PER_USER
diff --git a/docs/html/preview/features/scoped-folder-access.jd b/docs/html/training/articles/scoped-directory-access.jd
similarity index 60%
rename from docs/html/preview/features/scoped-folder-access.jd
rename to docs/html/training/articles/scoped-directory-access.jd
index 06dd665..1e0bf39 100644
--- a/docs/html/preview/features/scoped-folder-access.jd
+++ b/docs/html/training/articles/scoped-directory-access.jd
@@ -1,11 +1,10 @@
-page.title=Scoped Directory Access
-page.keywords=preview,sdk,scoped directory access
-page.tags=androidn
+page.title=Using Scoped Directory Access
+page.keywords=scoped directory access
@jd:body
-<div id="qv-wrapper">
-<div id="qv">
+<div id="tb-wrapper">
+<div id="tb">
<h2>In this document</h2>
<ol>
<li><a href="#accessing">Accessing an External Storage Directory</a></li>
@@ -32,29 +31,37 @@
external directory.</li>
</ul>
-<p>Android N provides a new simplified API to access
-common external storage directories. </p>
+<p>Android 7.0 provides a simplified API to access common external storage
+directories.</p>
<h2 id="accessing">Accessing an External Storage Directory</h2>
-<p>Use the <code>StorageManager</code> class to get the appropriate
-<code>StorageVolume</code> instance. Then, create an intent by calling the
-<code>StorageVolume.createAccessIntent()</code> method of that instance.
+<p>Use the {@link android.os.storage.StorageManager} class to get the
+appropriate {@link android.os.storage.StorageVolume} instance. Then, create
+an intent by calling the
+{@link android.os.storage.StorageVolume#createAccessIntent
+StorageVolume.createAccessIntent()} method of that instance.
Use this intent to access external storage directories. To get a list of
-all available volumes, including removable media volumes, use
-<code>StorageManager.getVolumesList()</code>.</p>
+all available volumes, including removable media
+volumes, use {@link android.os.storage.StorageManager#getStorageVolumes
+StorageManager.getStorageVolumes()}.</p>
<p>If you have information about a specific file, use
-<code>StorageManager.getStorageVolume(File)</code> to get the
-<code>StorageVolume</code> that contains the file. Call
-<code>createAccessIntent()</code> on this <code>StorageVolume</code> to access
+{@link android.os.storage.StorageManager#getStorageVolume
+StorageManager.getStorageVolume(File)} to get the
+{@link android.os.storage.StorageVolume} that contains the file. Call
+{@link android.os.storage.StorageVolume#createAccessIntent
+createAccessIntent()} on this
+{@link android.os.storage.StorageVolume} to access
the external storage directory for the file.</p>
<p>
On secondary volumes, such as external SD cards, pass in null when calling
-<code>StorageVolume.createAccessIntent()</code> to request access to the entire
+{@link android.os.storage.StorageVolume#createAccessIntent
+createAccessIntent()} to request access to the entire
volume, instead of a specific directory.
-<code>StorageVolume.createAccessIntent()</code> returns null if you pass in
+{@link android.os.storage.StorageVolume#createAccessIntent
+createAccessIntent()} returns null if you pass in
null to the primary volume, or if you pass in an invalid directory name.
</p>
@@ -63,7 +70,7 @@
<pre>
StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
-StorageVolume volume = sm.getPrimaryVolume();
+StorageVolume volume = sm.getPrimaryStorageVolume();
Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
startActivityForResult(intent, request_code);
</pre>
@@ -71,25 +78,27 @@
<p>The system attempts to grant access to the external directory, and if
necessary confirms access with the user using a simplified UI:</p>
-<img src="{@docRoot}preview/images/scoped-folder-access-framed.png"
-srcset="{@docRoot}preview/images/scoped-folder-access-framed.png 1x,
-{@docRoot}preview/images/scoped-folder-access-framed_2x.png 2x" />
+<img src="{@docRoot}images/android-7.0/scoped-directory-access-framed.png"
+srcset="{@docRoot}images/android-7.0/scoped-directory-access-framed.png 1x,
+{@docRoot}images/android-7.0/scoped-directory-access-framed_2x.png 2x" />
<p class="img-caption"><strong>Figure 1.</strong> An application requesting
access to the Pictures directory.</p>
<p>If the user grants access, the system calls your
-<code>onActivityResult()</code> override with a result code of
-<code>Activity.RESULT_OK</code>, and intent data that contains the URI. Use
+{@link android.app.Activity#onActivityResult onActivityResult()} override
+with a result code of {@link android.app.Activity#RESULT_OK
+RESULT_OK}, and intent data that contains the URI. Use
the provided URI to access directory information, similar to using URIs
returned by the
<a href="{@docRoot}guide/topics/providers/document-provider.html">Storage
Access Framework</a>.</p>
<p>If the user doesn't grant access, the system calls your
-<code>onActivityResult()</code> override with a result code of
-<code>Activity.RESULT_CANCELED</code>, and null intent data.</p>
+{@link android.app.Activity#onActivityResult onActivityResult()} override
+with a result code of {@link android.app.Activity#RESULT_CANCELED
+RESULT_CANCELED}, and null intent data.</p>
-<p class="note"><b>Note</b>: Getting access to a specific external directory
+<p>Getting access to a specific external directory
also gains access to subdirectories within that directory.</p>
<h2 id="removable">Accessing a Directory on Removable Media</h2>
@@ -112,9 +121,9 @@
<p>When the user mounts removable media, like an SD card, the system sends a
{@link android.os.Environment#MEDIA_MOUNTED} notification. This notification
-provides a <code>StorageVolume</code> object in the intent data that you can
-use to access directories on the removable media. The following example
-accesses the <code>Pictures</code> directory on removable media:</p>
+provides a {@link android.os.storage.StorageVolume} object in the intent data
+that you can use to access directories on the removable media. The following
+example accesses the <code>Pictures</code> directory on removable media:</p>
<pre>
// BroadcastReceiver has already cached the MEDIA_MOUNTED
@@ -129,19 +138,22 @@
<p>Where possible, persist the external directory access URI so you don't have
to repeatedly ask the user for access. Once the user has granted access, call
-<code>getContentResolver().takePersistableUriPermssion()</code> with the
-directory access URI. The system will persist the URI and subsequent access
-requests will return <code>RESULT_OK</code> and not show confirmation UI to the
-user.</p>
+{@link android.content.Context#getContentResolver getContentResolver()} and
+with the returned {@link android.content.ContentResolver} call
+{@link android.content.ContentResolver#takePersistableUriPermission
+takePersistableUriPermission()} with the directory access URI. The system will
+persist the URI and subsequent access requests will return
+{@link android.app.Activity#RESULT_OK RESULT_OK} and not show confirmation
+UI to the user.</p>
<p>If the user denies access to an external directory, do not immediately
request access again. Repeatedly insisting on access results in a poor user
experience. If a request is denied by the user, and the app requests access
again, the UI displays a <b>Don't ask again</b> checkbox:</p>
-<img src="{@docRoot}preview/images/scoped-folder-access-dont-ask.png"
-srcset="{@docRoot}preview/images/scoped-folder-access-dont-ask.png 1x,
-{@docRoot}preview/images/scoped-folder-access-dont-ask_2x.png 2x" />
+<img src="{@docRoot}images/android-7.0/scoped-directory-access-dont-ask.png"
+srcset="{@docRoot}images/android-7.0/scoped-directory-access-dont-ask.png 1x,
+{@docRoot}images/android-7.0/scoped-directory-access-dont-ask_2x.png 2x" />
<p class="img-caption"><strong>Figure 1.</strong> An application making a
second request for access to removable media.</p>
diff --git a/docs/html/training/monitoring-device-state/doze-standby.jd b/docs/html/training/monitoring-device-state/doze-standby.jd
index 9250288..7caef402 100644
--- a/docs/html/training/monitoring-device-state/doze-standby.jd
+++ b/docs/html/training/monitoring-device-state/doze-standby.jd
@@ -306,25 +306,22 @@
</p>
<p>
- An app that is whitelisted can use the network and hold
-
- <a href="{@docRoot}reference/android/os/PowerManager.html#PARTIAL_WAKE_LOCK">
- partial wake locks</a> during Doze and
- App Standby. However, <strong>other restrictions still apply</strong> to the
- whitelisted app, just as they do to other apps. For example, the whitelisted
- app’s jobs and syncs are deferred, and its regular {@link android.app.AlarmManager} alarms do not
- fire. An app can check whether it is currently on the exemption whitelist by
- calling {@link
+ An app that is whitelisted can use the network and hold <a href=
+ "{@docRoot}reference/android/os/PowerManager.html#PARTIAL_WAKE_LOCK">partial
+ wake locks</a> during Doze and App Standby. However, <strong>other
+ restrictions still apply</strong> to the whitelisted app, just as they do to
+ other apps. For example, the whitelisted app’s jobs and syncs are deferred
+ (on API level 23 and below), and its regular {@link android.app.AlarmManager}
+ alarms do not fire. An app can check whether it is currently on the exemption
+ whitelist by calling {@link
android.os.PowerManager#isIgnoringBatteryOptimizations(java.lang.String)
isIgnoringBatteryOptimizations()}.
- </li>
</p>
<p>
Users can manually configure the whitelist in <strong>Settings > Battery
> Battery Optimization.</strong> Alternatively, the system provides
ways for apps to ask users to whitelist them.
-
</p>
<ul>
diff --git a/docs/html/preview/features/picture-in-picture.jd b/docs/html/training/tv/playback/picture-in-picture.jd
similarity index 87%
rename from docs/html/preview/features/picture-in-picture.jd
rename to docs/html/training/tv/playback/picture-in-picture.jd
index 03a1768..e48ae48 100644
--- a/docs/html/preview/features/picture-in-picture.jd
+++ b/docs/html/training/tv/playback/picture-in-picture.jd
@@ -1,11 +1,14 @@
-page.title=Picture-in-picture
+page.title=Adding Picture-in-picture
page.keywords=preview,sdk,PIP,Picture-in-picture
page.tags=androidn
+helpoutsWidget=true
+
+trainingnavtop=true
@jd:body
-<div id="qv-wrapper">
-<div id="qv">
+<div id="tb-wrapper">
+<div id="tb">
<h2>In this document</h2>
<ol>
@@ -31,12 +34,12 @@
</div>
</div>
-<p>In Android N, Android TV users can now watch a video
-in a pinned window in a corner of the screen when navigating within
-apps. Picture-in-picture (PIP) mode lets apps run a video
+<p>In Android 7.0, Android TV users can now watch a video
+in a pinned window in a corner of the screen when navigating within or
+between apps. Picture-in-picture (PIP) mode lets apps run a video
activity in the pinned window while another activity continues in the
-background. The PIP window lets users multitask while using your app, which
-helps users be more productive.</p>
+background. The PIP window lets users multitask while using Android TV,
+which helps users be more productive.</p>
<p>Your app can decide when to trigger PIP mode. Here are some examples of
when to enter PIP mode:</p>
@@ -57,14 +60,14 @@
PIP menu that lets them toggle the PIP window to full-screen, or close the PIP
window, by holding down the <b>Home</b> button on the remote. If another
video starts playing on the main screen, the PIP window is automatically
-closed. Users can also close the PIP window through Recents.</p>
+closed.</p>
-<img src="{@docRoot}preview/images/pip-active.png" />
+<img src="{@docRoot}images/android-7.0/pip-active.png" />
<p class="img-caption"><strong>Figure 1.</strong> A Picture-in-picture
video visible in a corner of the screen while the user browses content
on the main screen.</p>
-<p>PIP leverages the multi-window APIs available in Android N to
+<p>PIP leverages the multi-window APIs available in Android 7.0 to
provide the pinned video overlay window. To add PIP to your app, you need to
register your activities that support PIP, switch your activity to PIP mode as
needed, and make sure UI elements are hidden and video playback continues when
@@ -99,7 +102,8 @@
<h2 id="pip_button">Switching Your Activity to Picture-in-picture</h2>
When you need to switch your activity into PIP mode, call
-<code>Activity.enterPictureInPictureMode()</code>. The following example
+{@link android.app.Activity#enterPictureInPictureMode
+enterPictureInPictureMode()}. The following example
switches to PIP mode when the user selects a dedicated PIP button on a media
control bar:</p>
@@ -116,12 +120,13 @@
<p>Adding a PIP button to your media control bar lets your user easily switch
to PIP mode while controlling video playback.</p>
-<img src="{@docRoot}preview/images/pip-button.png" />
+<img src="{@docRoot}images/android-7.0/pip-button.png" />
<p class="img-caption"><strong>Figure 1.</strong> A Picture-in-picture
button on a media control bar.</p>
-<p>Android N includes a new
-<code>PlaybackControlsRow.PictureInPictureAction</code> class which defines
+<p>Android 7.0 includes a
+{@link android.support.v17.leanback.widget.PlaybackControlsRow.PictureInPictureAction
+PlaybackControlsRow.PictureInPictureAction} class which defines
control bar PIP actions and uses the PIP icon.</p>
<h2 id="handling_ui">Handling UI During Picture-in-picture</h2>
@@ -129,8 +134,10 @@
<p>When your activity enters PIP mode, your activity should only show video
playback. Remove UI elements before your activity enters PIP,
and restore these elements when your activity becomes full-screen again.
-Override <code>Activity.onPictureInPictureModeChanged()</code> or
-<code>Fragment.onPictureInPictureModeChanged()</code> and enable or
+Override {@link android.app.Activity#onPictureInPictureModeChanged
+Activity.onPictureInPictureModeChanged()} or
+{@link android.app.Fragment#onPictureInPictureModeChanged
+Fragment.onPictureInPictureModeChanged()} and enable or
disable your UI elements as needed, for example:</p>
<pre>
@@ -154,7 +161,7 @@
onPause()} method. Video playback should not be paused and should continue
playing if the activity is paused due to PIP mode.</p>
-<p>In Android N, you should pause and resume video playback when the system
+<p>In Android 7.0, you should pause and resume video playback when the system
calls your activity's {@link android.app.Activity#onStop onStop()} and
{@link android.app.Activity#onStart onStart()}. By doing this, you can avoid
having to check if your app is in PIP mode in
@@ -204,7 +211,7 @@
</pre>
<p>In your activity, override {@link android.app.Activity#onNewIntent
-Activity.onNewIntent()} and handle the new video, stopping any existing video
+onNewIntent()} and handle the new video, stopping any existing video
playback if needed.</p>
<h2 id="best">Best Practices</h2>
diff --git a/docs/html/training/tv/tif/content-recording.jd b/docs/html/training/tv/tif/content-recording.jd
new file mode 100644
index 0000000..ffdd14c
--- /dev/null
+++ b/docs/html/training/tv/tif/content-recording.jd
@@ -0,0 +1,171 @@
+page.title=Supporting Content Recording
+page.keywords=tv,recording,channel,tif
+page.tags=tv, tif
+helpoutsWidget=true
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#supporting">Indicating Support for Recording</a></li>
+ <li><a href="#recording">Recording a Session</a></li>
+ <li><a href="#errors">Handling Recording Errors</a></li>
+ <li><a href="#sessions">Managing Recorded Sessions</a></li>
+ <li><a href="#best">Best Practices</a></li>
+ </ol>
+</div>
+</div>
+
+<p>TV input services let the user pause and resume channel playback via
+time-shifting APIs. Android 7.0 expands on time-shifting
+by letting the user save multiple recorded sessions.</p>
+
+<p>Users can schedule recordings in advance, or start a recording as they watch
+a program. Once the system has saved a recording, the user can browse, manage,
+and play back the recording using the system TV app.</p>
+
+<p>If you want to provide recording functionality for your TV input service,
+you must indicate to the system that your app supports recording, implement
+the ability to record programs, handle and communicate any errors that occur
+during recording, and manage your recorded sessions.</p>
+
+<p class="note"><strong>Note:</strong> The Live Channels app does not yet
+provide a way for users to create or access recordings. Until changes are
+made to the Live Channels app, it may be difficult to fully test the recording
+experience for your TV input service.</p>
+
+<h2 id="supporting">Indicating Support for Recording</h2>
+
+<p>To tell the system that your TV input service supports recording, set
+the <code>android:canRecord</code> attribute in your service metadata XML file
+to <code>true</code>:
+</p>
+
+<pre>
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+ <b>android:canRecord="true"</b>
+ android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity" />
+</pre>
+
+<p>For more information on the service metadata file, see
+<a href="{@docRoot}training/tv/tif/tvinput.html#manifest">Declare Your TV Input
+Service in the Manifest</a>.
+</p>
+
+<p>Alternatively, you can indicate recording support in your code using
+these steps:</p>
+
+<ol>
+<li>In your TV input service {@link android.app.Service#onCreate onCreate()}
+method, create a new {@link android.media.tv.TvInputInfo} object using the
+{@link android.media.tv.TvInputInfo.Builder TvInputInfo.Builder} class.</li>
+<li>When creating the new {@link android.media.tv.TvInputInfo} object, call
+{@link android.media.tv.TvInputInfo.Builder#setCanRecord
+setCanRecord(true)} before calling
+{@link android.media.tv.TvInputInfo.Builder#build build()} to indicate your
+service supports recording.</li>
+<li>Register your {@link android.media.tv.TvInputInfo} object with the
+system by calling
+{@link android.media.tv.TvInputManager#updateTvInputInfo
+TvInputManager.updateTvInputInfo()}.</li>
+</ol>
+
+<h2 id="recording">Recording a Session</h2>
+
+<p>After your TV input service registers that it supports recording
+functionality, the system calls your
+{@link android.media.tv.TvInputService#onCreateRecordingSession
+TvInputService.onCreateRecordingSession()} method when it needs to access
+your app's recording implementation. Implement your own
+{@link android.media.tv.TvInputService.RecordingSession
+TvInputService.RecordingSession} subclass and return it
+when the {@link android.media.tv.TvInputService#onCreateRecordingSession
+onCreateRecordingSession()} callback fires. This subclass is responsible
+for switching to the correct channel data, recording the requested data,
+and communicating recording status and errors to the system.</p>
+
+<p>When the system calls
+{@link android.media.tv.TvInputService.RecordingSession#onTune
+RecordingSession.onTune()}, passing in a channel URI, tune to the channel
+that the URI specifies. Notify the system that your app has tuned to the
+desired channel by calling
+{@link android.media.tv.TvInputService.RecordingSession#notifyTuned
+notifyTuned()} or, if your app could not tune to the proper channel, call
+{@link android.media.tv.TvInputService.RecordingSession#notifyError
+notifyError()}.</p>
+
+<p>The system next invokes the
+{@link android.media.tv.TvInputService.RecordingSession#onStartRecording
+RecordingSession.onStartRecording()} callback. Your app must start recording
+immediately. When the system invokes this callback, it may provide a URI
+that contains information about the program that is about to be recorded.
+When the recording is done, you'll copy this data to the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms}
+data table.</p>
+
+<p>Finally, the system calls
+{@link android.media.tv.TvInputService.RecordingSession#onStopRecording
+RecordingSession.onStopRecording()}. At this point, your app must stop
+recording immediately. You also need to create an entry in the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms}
+table. This entry should include the recorded session data URI in the
+{@link android.media.tv.TvContract.RecordedPrograms#COLUMN_RECORDING_DATA_URI
+RecordedPrograms.COLUMN_RECORDING_DATA_URI} column, and any program
+information that the system provided in the initial call to
+{@link android.media.tv.TvInputService.RecordingSession#onStartRecording
+onStartRecording()}.</p>
+
+<p>For more details on how to access the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms} table
+see <a href="#sessions">Managing Recorded Sessions</a>.</p>
+
+<h2 id="errors">Handling Recording Errors</h2>
+
+<p>If an error occurs during recording, resulting in unusable recorded data,
+notify the system by calling
+{@link android.media.tv.TvInputService.RecordingSession#notifyError
+notifyError()}. Similarly, you can call
+{@link android.media.tv.TvInputService.RecordingSession#notifyError
+notifyError()} after a recording session is created to let the system know
+that your app can no longer record sessions.</p>
+
+<p>If an error occurs during recording, but you'd like to provide a
+partial recording to users for playback, call
+{@link android.media.tv.TvInputService.RecordingSession#notifyRecordingStopped
+notifyRecordingStopped()} to enable the system to
+use the partial session.</p>
+
+<h2 id="sessions">Managing Recorded Sessions</h2>
+
+<p>The system maintains information for all recorded sessions from all
+recording-capable channel apps in the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms}
+content provider table. This information is accessible via the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms}
+content recording URIs. Use content provider APIs to
+read, add, and delete entries from this table.</p>
+
+<p>For more information on working with content provider data see
+<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+Content Provider Basics</a>.</p>
+
+<h2 id="best">Best Practices</h2>
+
+<p>TV devices may have limited storage, so use your best judgment when
+allocating storage to save recorded sessions. Use
+{@link android.media.tv.TvRecordingClient.RecordingCallback#onError
+RecordingCallback.onError(RECORDING_ERROR_INSUFFICIENT_SPACE)} when
+there isn't enough space to save a recorded session.</p>
+
+<p>When the user initiates recording, you should start recording data as soon
+as possible. To facilitate this, complete any up-front time-consuming tasks,
+like accessing and allocating storage space, when the system invokes the
+{@link android.media.tv.TvInputService#onCreateRecordingSession
+onCreateRecordingSession()} callback. Doing so lets you start
+recording immediately when the
+{@link android.media.tv.TvInputService.RecordingSession#onStartRecording
+onStartRecording()} callback fires.</p>
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index d5143da..cc7f5c7 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -851,8 +851,8 @@
private Drawable prepareDrawable(Drawable child) {
child.setLayoutDirection(mLayoutDirection);
- child.setCallback(mOwner);
child = child.mutate();
+ child.setCallback(mOwner);
return child;
}
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index cbefccb..1802fd4 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -268,7 +268,7 @@
// Geometry
void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) {
- if (floatCount < 2) return;
+ if (CC_UNLIKELY(floatCount < 2 || PaintUtils::paintWillNotDraw(paint))) return;
floatCount &= ~0x1; // round down to nearest two
addOp(alloc().create_trivial<PointsOp>(
@@ -279,7 +279,7 @@
}
void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
- if (floatCount < 4) return;
+ if (CC_UNLIKELY(floatCount < 4 || PaintUtils::paintWillNotDraw(paint))) return;
floatCount &= ~0x3; // round down to nearest four
addOp(alloc().create_trivial<LinesOp>(
@@ -290,6 +290,8 @@
}
void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
addOp(alloc().create_trivial<RectOp>(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
@@ -331,6 +333,8 @@
}
void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
if (paint.getStyle() == SkPaint::kFill_Style
&& (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
int count = 0;
@@ -355,8 +359,11 @@
}
}
}
+
void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) {
addOp(alloc().create_trivial<RoundRectOp>(
Rect(left, top, right, bottom),
@@ -391,7 +398,8 @@
void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
// TODO: move to Canvas.h
- if (radius <= 0) return;
+ if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
+
drawOval(x - radius, y - radius, x + radius, y + radius, paint);
}
@@ -411,6 +419,8 @@
}
void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
addOp(alloc().create_trivial<OvalOp>(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
@@ -420,6 +430,8 @@
void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
if (fabs(sweepAngle) >= 360.0f) {
drawOval(left, top, right, bottom, paint);
} else {
@@ -433,6 +445,8 @@
}
void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+ if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
addOp(alloc().create_trivial<PathOp>(
Rect(path.getBounds()),
*(mState.currentSnapshot()->transform),
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index e67dfdd..a0c3d9d 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -678,6 +678,7 @@
TreeProperties* mutateProperties() { return &mProperties; }
// This should always be called from RT.
+ void markDirty() { mCache.dirty = true; }
bool isDirty() const { return mCache.dirty; }
bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; }
void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; }
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index af54e07..53dbede 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -1459,7 +1459,8 @@
static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
SkPaint paint;
- paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
+ // order put in blue channel, transparent so overlapped content doesn't get rejected
+ paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder));
canvas->drawRect(0, 0, 100, 100, paint);
}
static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 9cd504e..28b375f 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -81,6 +81,27 @@
ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected.";
}
+TEST(RecordingCanvas, emptyPaintRejection) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint emptyPaint;
+ emptyPaint.setColor(Color::Transparent);
+
+ float points[] = {0, 0, 200, 200};
+ canvas.drawPoints(points, 4, emptyPaint);
+ canvas.drawLines(points, 4, emptyPaint);
+ canvas.drawRect(0, 0, 200, 200, emptyPaint);
+ canvas.drawRegion(SkRegion(SkIRect::MakeWH(200, 200)), emptyPaint);
+ canvas.drawRoundRect(0, 0, 200, 200, 10, 10, emptyPaint);
+ canvas.drawCircle(100, 100, 100, emptyPaint);
+ canvas.drawOval(0, 0, 200, 200, emptyPaint);
+ canvas.drawArc(0, 0, 200, 200, 0, 360, true, emptyPaint);
+ SkPath path;
+ path.addRect(0, 0, 200, 200);
+ canvas.drawPath(path, emptyPaint);
+ });
+ EXPECT_EQ(0u, dl->getOps().size()) << "Op should be rejected";
+}
+
TEST(RecordingCanvas, drawArc) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint());
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 354339c..50dbd03 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -57,12 +57,12 @@
/**
* @hide
*/
- public AudioRecordingConfiguration(int session, int source, AudioFormat devFormat,
- AudioFormat clientFormat, int patchHandle) {
+ public AudioRecordingConfiguration(int session, int source, AudioFormat clientFormat,
+ AudioFormat devFormat, int patchHandle) {
mSessionId = session;
mClientSource = source;
- mDeviceFormat = devFormat;
mClientFormat = clientFormat;
+ mDeviceFormat = devFormat;
mPatchHandle = patchHandle;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 47df940..b7c0a9c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -664,10 +664,10 @@
return true;
case R.id.menu_copy_to:
- transferDocuments(selection, FileOperationService.OPERATION_COPY);
// TODO: Only finish selection mode if copy-to is not canceled.
// Need to plum down into handling the way we do with deleteDocuments.
mode.finish();
+ transferDocuments(selection, FileOperationService.OPERATION_COPY);
return true;
case R.id.menu_move_to:
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index dec1fd2..1b83ccd 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -50,6 +50,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -259,10 +260,14 @@
handleFinishedGoingToSleep(msg.arg1);
break;
case MSG_STARTED_WAKING_UP:
+ Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
handleStartedWakingUp();
+ Trace.endSection();
break;
case MSG_FACE_UNLOCK_STATE_CHANGED:
+ Trace.beginSection("KeyguardUpdateMonitor#handler MSG_FACE_UNLOCK_STATE_CHANGED");
handleFaceUnlockStateChanged(msg.arg1 != 0, msg.arg2);
+ Trace.endSection();
break;
case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
handleSimSubscriptionInfoChanged();
@@ -277,7 +282,9 @@
handleScreenTurnedOn();
break;
case MSG_SCREEN_TURNED_OFF:
+ Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_ON");
handleScreenTurnedOff();
+ Trace.endSection();
break;
}
}
@@ -399,6 +406,7 @@
}
private void onFingerprintAuthenticated(int userId) {
+ Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
mUserFingerprintAuthenticated.put(userId, true);
// If fingerprint unlocking is allowed, this event will lead to a Keyguard dismiss or to a
@@ -411,6 +419,7 @@
cb.onFingerprintAuthenticated(userId);
}
}
+ Trace.endSection();
}
private void handleFingerprintAuthFailed() {
@@ -436,6 +445,7 @@
}
private void handleFingerprintAuthenticated() {
+ Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
try {
final int userId;
try {
@@ -452,6 +462,7 @@
} finally {
setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
}
+ Trace.endSection();
}
private void handleFingerprintHelp(int msgId, String helpString) {
@@ -690,8 +701,10 @@
mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_INFO_CHANGED,
intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId()), 0));
} else if (ACTION_FACE_UNLOCK_STARTED.equals(action)) {
+ Trace.beginSection("KeyguardUpdateMonitor.mBroadcastAllReceiver#onReceive ACTION_FACE_UNLOCK_STARTED");
mHandler.sendMessage(mHandler.obtainMessage(MSG_FACE_UNLOCK_STATE_CHANGED, 1,
getSendingUserId()));
+ Trace.endSection();
} else if (ACTION_FACE_UNLOCK_STOPPED.equals(action)) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_FACE_UNLOCK_STATE_CHANGED, 0,
getSendingUserId()));
@@ -731,7 +744,9 @@
@Override
public void onAuthenticationSucceeded(AuthenticationResult result) {
+ Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
handleFingerprintAuthenticated();
+ Trace.endSection();
}
@Override
@@ -906,6 +921,7 @@
}
protected void handleStartedWakingUp() {
+ Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
updateFingerprintListeningState();
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
@@ -914,6 +930,7 @@
cb.onStartedWakingUp();
}
}
+ Trace.endSection();
}
protected void handleStartedGoingToSleep(int arg1) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 16bfc56..a22a051 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -749,6 +749,7 @@
static final int MSG_LOAD_ICONS = 3;
static final int MSG_LOAD_SIZES = 4;
static final int MSG_LOAD_LAUNCHER = 5;
+ static final int MSG_LOAD_HOME_APP = 6;
boolean mRunning;
@@ -817,13 +818,33 @@
if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
}
- sendEmptyMessage(MSG_LOAD_LAUNCHER);
+ sendEmptyMessage(MSG_LOAD_HOME_APP);
}
} break;
+ case MSG_LOAD_HOME_APP: {
+ final List<ResolveInfo> homeActivities = new ArrayList<>();
+ mPm.getHomeActivities(homeActivities);
+ synchronized (mEntriesMap) {
+ final int entryCount = mEntriesMap.size();
+ for (int i = 0; i < entryCount; i++) {
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock");
+ final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
+ for (ResolveInfo activity : homeActivities) {
+ String packageName = activity.activityInfo.packageName;
+ AppEntry entry = userEntries.get(packageName);
+ if (entry != null) {
+ entry.isHomeApp = true;
+ }
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock");
+ }
+ }
+ sendEmptyMessage(MSG_LOAD_LAUNCHER);
+ }
+ break;
case MSG_LOAD_LAUNCHER: {
Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
.addCategory(Intent.CATEGORY_LAUNCHER);
-
for (int i = 0; i < mEntriesMap.size(); i++) {
int userId = mEntriesMap.keyAt(i);
// If we do not specify MATCH_DIRECT_BOOT_AWARE or
@@ -1119,6 +1140,11 @@
*/
public boolean hasLauncherEntry;
+ /**
+ * Whether or not it's a Home app.
+ */
+ public boolean isHomeApp;
+
public String getNormalizedLabel() {
if (normalizedLabel != null) {
return normalizedLabel;
@@ -1310,6 +1336,8 @@
return true;
} else if (entry.hasLauncherEntry) {
return true;
+ } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && entry.isHomeApp) {
+ return true;
}
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java
index 85a9bee..4c64711 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java
@@ -16,6 +16,8 @@
package com.android.systemui.classifier;
+import android.os.SystemClock;
+
import java.util.ArrayList;
/**
@@ -31,7 +33,7 @@
private long mLastUpdate;
public HistoryEvaluator() {
- mLastUpdate = System.currentTimeMillis();
+ mLastUpdate = SystemClock.elapsedRealtime();
}
public void addStroke(float evaluation) {
@@ -69,15 +71,18 @@
}
private void decayValue() {
- long currentTimeMillis = System.currentTimeMillis();
+ long time = SystemClock.elapsedRealtime();
+
+ if (time <= mLastUpdate) {
+ return;
+ }
// All weights are multiplied by HISTORY_FACTOR after each INTERVAL milliseconds.
- float factor = (float) Math.pow(HISTORY_FACTOR,
- (float) (currentTimeMillis - mLastUpdate) / INTERVAL);
+ float factor = (float) Math.pow(HISTORY_FACTOR, (time - mLastUpdate) / INTERVAL);
decayValue(mStrokes, factor);
decayValue(mGestureWeights, factor);
- mLastUpdate = currentTimeMillis;
+ mLastUpdate = time;
}
private void decayValue(ArrayList<Data> list, float factor) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 84d3599..b354a4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -23,6 +23,7 @@
import android.os.Debug;
import android.os.IBinder;
import android.os.Process;
+import android.os.Trace;
import android.util.Log;
import com.android.internal.policy.IKeyguardDrawnCallback;
@@ -73,21 +74,27 @@
@Override // Binder interface
public void verifyUnlock(IKeyguardExitCallback callback) {
+ Trace.beginSection("KeyguardService.mBinder#verifyUnlock");
checkPermission();
mKeyguardViewMediator.verifyUnlock(callback);
+ Trace.endSection();
}
@Override // Binder interface
public void keyguardDone(boolean authenticated, boolean wakeup) {
+ Trace.beginSection("KeyguardService.mBinder#keyguardDone");
checkPermission();
// TODO: Remove wakeup
mKeyguardViewMediator.keyguardDone(authenticated);
+ Trace.endSection();
}
@Override // Binder interface
public void setOccluded(boolean isOccluded) {
+ Trace.beginSection("KeyguardService.mBinder#setOccluded");
checkPermission();
mKeyguardViewMediator.setOccluded(isOccluded);
+ Trace.endSection();
}
@Override // Binder interface
@@ -122,20 +129,26 @@
@Override // Binder interface
public void onStartedWakingUp() {
+ Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp");
checkPermission();
mKeyguardViewMediator.onStartedWakingUp();
+ Trace.endSection();
}
@Override // Binder interface
public void onScreenTurningOn(IKeyguardDrawnCallback callback) {
+ Trace.beginSection("KeyguardService.mBinder#onScreenTurningOn");
checkPermission();
mKeyguardViewMediator.onScreenTurningOn(callback);
+ Trace.endSection();
}
@Override // Binder interface
public void onScreenTurnedOn() {
+ Trace.beginSection("KeyguardService.mBinder#onScreenTurningOn");
checkPermission();
mKeyguardViewMediator.onScreenTurnedOn();
+ Trace.endSection();
}
@Override // Binder interface
@@ -152,8 +165,10 @@
@Override // Binder interface
public void onSystemReady() {
+ Trace.beginSection("KeyguardService.mBinder#onSystemReady");
checkPermission();
mKeyguardViewMediator.onSystemReady();
+ Trace.endSection();
}
@Override // Binder interface
@@ -176,8 +191,10 @@
@Override
public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation");
checkPermission();
mKeyguardViewMediator.startKeyguardExitAnimation(startTime, fadeoutDuration);
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 42a9ed5..cfa4661 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -42,6 +42,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -520,7 +521,9 @@
@Override
public void keyguardDoneDrawing() {
+ Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDoneDrawing");
mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING);
+ Trace.endSection();
}
@Override
@@ -530,6 +533,7 @@
@Override
public void keyguardDonePending(boolean strongAuth) {
+ Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
mKeyguardDonePending = true;
mHideAnimationRun = true;
mStatusBarKeyguardViewManager.startPreHideAnimation(null /* finishRunnable */);
@@ -538,20 +542,25 @@
if (strongAuth) {
mUpdateMonitor.reportSuccessfulStrongAuthUnlockAttempt();
}
+ Trace.endSection();
}
@Override
public void keyguardGone() {
+ Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardGone");
mKeyguardDisplayManager.hide();
+ Trace.endSection();
}
@Override
public void readyForKeyguardDone() {
+ Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#readyForKeyguardDone");
if (mKeyguardDonePending) {
// Somebody has called keyguardDonePending before, which means that we are
// authenticated
KeyguardViewMediator.this.keyguardDone(true /* authenticated */);
}
+ Trace.endSection();
}
@Override
@@ -892,6 +901,7 @@
* Let's us know when the device is waking up.
*/
public void onStartedWakingUp() {
+ Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
// TODO: Rename all screen off/on references to interactive/sleeping
synchronized (this) {
@@ -903,15 +913,20 @@
}
KeyguardUpdateMonitor.getInstance(mContext).dispatchStartedWakingUp();
maybeSendUserPresentBroadcast();
+ Trace.endSection();
}
public void onScreenTurningOn(IKeyguardDrawnCallback callback) {
+ Trace.beginSection("KeyguardViewMediator#onScreenTurningOn");
notifyScreenOn(callback);
+ Trace.endSection();
}
public void onScreenTurnedOn() {
+ Trace.beginSection("KeyguardViewMediator#onScreenTurnedOn");
notifyScreenTurnedOn();
mUpdateMonitor.dispatchScreenTurnedOn();
+ Trace.endSection();
}
public void onScreenTurnedOff() {
@@ -1025,6 +1040,7 @@
* @see android.app.KeyguardManager#exitKeyguardSecurely
*/
public void verifyUnlock(IKeyguardExitCallback callback) {
+ Trace.beginSection("KeyguardViewMediator#verifyUnlock");
synchronized (this) {
if (DEBUG) Log.d(TAG, "verifyUnlock");
if (shouldWaitForProvisioning()) {
@@ -1075,6 +1091,7 @@
}
}
}
+ Trace.endSection();
}
/**
@@ -1088,16 +1105,19 @@
* Notify us when the keyguard is occluded by another window
*/
public void setOccluded(boolean isOccluded) {
+ Trace.beginSection("KeyguardViewMediator#setOccluded");
if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
mHandler.removeMessages(SET_OCCLUDED);
Message msg = mHandler.obtainMessage(SET_OCCLUDED, (isOccluded ? 1 : 0), 0);
mHandler.sendMessage(msg);
+ Trace.endSection();
}
/**
* Handles SET_OCCLUDED message sent by setOccluded()
*/
private void handleSetOccluded(boolean isOccluded) {
+ Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
synchronized (KeyguardViewMediator.this) {
if (mHiding && isOccluded) {
// We're in the process of going away but WindowManager wants to show a
@@ -1112,6 +1132,7 @@
adjustStatusBarLocked();
}
}
+ Trace.endSection();
}
/**
@@ -1300,11 +1321,13 @@
* @see #handleShow
*/
private void showLocked(Bundle options) {
+ Trace.beginSection("KeyguardViewMediator#showLocked aqcuiring mShowKeyguardWakeLock");
if (DEBUG) Log.d(TAG, "showLocked");
// ensure we stay awake until we are finished displaying the keyguard
mShowKeyguardWakeLock.acquire();
Message msg = mHandler.obtainMessage(SHOW, options);
mHandler.sendMessage(msg);
+ Trace.endSection();
}
/**
@@ -1312,9 +1335,11 @@
* @see #handleHide()
*/
private void hideLocked() {
+ Trace.beginSection("KeyguardViewMediator#hideLocked");
if (DEBUG) Log.d(TAG, "hideLocked");
Message msg = mHandler.obtainMessage(HIDE);
mHandler.sendMessage(msg);
+ Trace.endSection();
}
public boolean isSecure() {
@@ -1359,10 +1384,12 @@
};
public void keyguardDone(boolean authenticated) {
+ Trace.beginSection("KeyguardViewMediator#keyguardDone");
if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated +")");
EventLog.writeEvent(70000, 2);
Message msg = mHandler.obtainMessage(KEYGUARD_DONE, authenticated ? 1 : 0);
mHandler.sendMessage(msg);
+ Trace.endSection();
}
/**
@@ -1386,7 +1413,9 @@
handleReset();
break;
case VERIFY_UNLOCK:
+ Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK");
handleVerifyUnlock();
+ Trace.endSection();
break;
case NOTIFY_STARTED_GOING_TO_SLEEP:
handleNotifyStartedGoingToSleep();
@@ -1395,25 +1424,37 @@
handleNotifyFinishedGoingToSleep();
break;
case NOTIFY_SCREEN_TURNING_ON:
+ Trace.beginSection("KeyguardViewMediator#handleMessage NOTIFY_SCREEN_TURNING_ON");
handleNotifyScreenTurningOn((IKeyguardDrawnCallback) msg.obj);
+ Trace.endSection();
break;
case NOTIFY_SCREEN_TURNED_ON:
+ Trace.beginSection("KeyguardViewMediator#handleMessage NOTIFY_SCREEN_TURNED_ON");
handleNotifyScreenTurnedOn();
+ Trace.endSection();
break;
case NOTIFY_SCREEN_TURNED_OFF:
handleNotifyScreenTurnedOff();
break;
case NOTIFY_STARTED_WAKING_UP:
+ Trace.beginSection("KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP");
handleNotifyStartedWakingUp();
+ Trace.endSection();
break;
case KEYGUARD_DONE:
+ Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE");
handleKeyguardDone(msg.arg1 != 0);
+ Trace.endSection();
break;
case KEYGUARD_DONE_DRAWING:
+ Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING");
handleKeyguardDoneDrawing();
+ Trace.endSection();
break;
case SET_OCCLUDED:
+ Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED");
handleSetOccluded(msg.arg1 != 0);
+ Trace.endSection();
break;
case KEYGUARD_TIMEOUT:
synchronized (KeyguardViewMediator.this) {
@@ -1424,12 +1465,16 @@
handleDismiss();
break;
case START_KEYGUARD_EXIT_ANIM:
+ Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
FalsingManager.getInstance(mContext).onSucccessfulUnlock();
+ Trace.endSection();
break;
case KEYGUARD_DONE_PENDING_TIMEOUT:
+ Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_PENDING_TIMEOUT");
Log.w(TAG, "Timeout while waiting for activity drawn!");
+ Trace.endSection();
// Fall through.
case ON_ACTIVITY_DRAWN:
handleOnActivityDrawn();
@@ -1443,6 +1488,7 @@
* @see #KEYGUARD_DONE
*/
private void handleKeyguardDone(boolean authenticated) {
+ Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
if (mLockPatternUtils.isSecure(currentUser)) {
mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
@@ -1480,6 +1526,7 @@
}
handleHide();
+ Trace.endSection();
}
private void sendUserPresentBroadcast() {
@@ -1504,6 +1551,7 @@
* @see #KEYGUARD_DONE_DRAWING
*/
private void handleKeyguardDoneDrawing() {
+ Trace.beginSection("KeyguardViewMediator#handleKeyguardDoneDrawing");
synchronized(this) {
if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing");
if (mWaitingUntilKeyguardVisible) {
@@ -1517,6 +1565,7 @@
mHandler.removeMessages(KEYGUARD_DONE_DRAWING);
}
}
+ Trace.endSection();
}
private void playSounds(boolean locked) {
@@ -1548,10 +1597,12 @@
}
private void updateActivityLockScreenState() {
+ Trace.beginSection("KeyguardViewMediator#updateActivityLockScreenState");
try {
ActivityManagerNative.getDefault().setLockScreenShown(mShowing, mOccluded);
} catch (RemoteException e) {
}
+ Trace.endSection();
}
/**
@@ -1559,6 +1610,7 @@
* @see #SHOW
*/
private void handleShow(Bundle options) {
+ Trace.beginSection("KeyguardViewMediator#handleShow");
final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
if (mLockPatternUtils.isSecure(currentUser)) {
mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser);
@@ -1584,11 +1636,13 @@
mShowKeyguardWakeLock.release();
}
mKeyguardDisplayManager.show();
+ Trace.endSection();
}
private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
@Override
public void run() {
+ Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
try {
mStatusBarKeyguardViewManager.keyguardGoingAway();
@@ -1611,6 +1665,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Error while calling WindowManager", e);
}
+ Trace.endSection();
}
};
@@ -1619,6 +1674,7 @@
* @see #HIDE
*/
private void handleHide() {
+ Trace.beginSection("KeyguardViewMediator#handleHide");
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
@@ -1646,6 +1702,7 @@
mHideAnimation.getDuration());
}
}
+ Trace.endSection();
}
private void handleOnActivityDrawn() {
@@ -1656,6 +1713,7 @@
}
private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
synchronized (KeyguardViewMediator.this) {
if (!mHiding) {
@@ -1686,6 +1744,7 @@
adjustStatusBarLocked();
sendUserPresentBroadcast();
}
+ Trace.endSection();
}
private void adjustStatusBarLocked() {
@@ -1737,12 +1796,14 @@
* @see #VERIFY_UNLOCK
*/
private void handleVerifyUnlock() {
+ Trace.beginSection("KeyguardViewMediator#handleVerifyUnlock");
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
setShowingLocked(true);
mStatusBarKeyguardViewManager.verifyUnlock();
updateActivityLockScreenState();
}
+ Trace.endSection();
}
private void handleNotifyStartedGoingToSleep() {
@@ -1764,13 +1825,16 @@
}
private void handleNotifyStartedWakingUp() {
+ Trace.beginSection("KeyguardViewMediator#handleMotifyStartedWakingUp");
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleNotifyWakingUp");
mStatusBarKeyguardViewManager.onStartedWakingUp();
}
+ Trace.endSection();
}
private void handleNotifyScreenTurningOn(IKeyguardDrawnCallback callback) {
+ Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurningOn");
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn");
mStatusBarKeyguardViewManager.onScreenTurningOn();
@@ -1782,13 +1846,16 @@
}
}
}
+ Trace.endSection();
}
private void handleNotifyScreenTurnedOn() {
+ Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn");
synchronized (this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn");
mStatusBarKeyguardViewManager.onScreenTurnedOn();
}
+ Trace.endSection();
}
private void handleNotifyScreenTurnedOff() {
@@ -1800,11 +1867,13 @@
}
private void notifyDrawn(final IKeyguardDrawnCallback callback) {
+ Trace.beginSection("KeyguardViewMediator#notifyDrawn");
try {
callback.onDrawn();
} catch (RemoteException e) {
Slog.w(TAG, "Exception calling onDrawn():", e);
}
+ Trace.endSection();
}
private void resetKeyguardDonePendingLocked() {
@@ -1824,8 +1893,10 @@
}
public void onWakeAndUnlocking() {
+ Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");
mWakeAndUnlocking = true;
keyguardDone(true /* authenticated */);
+ Trace.endSection();
}
public StatusBarKeyguardViewManager registerStatusBar(PhoneStatusBar phoneStatusBar,
@@ -1838,9 +1909,11 @@
}
public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ Trace.beginSection("KeyguardViewMediator#startKeyguardExitAnimation");
Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM,
new StartKeyguardExitAnimParams(startTime, fadeoutDuration));
mHandler.sendMessage(msg);
+ Trace.endSection();
}
public void onActivityDrawn() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 1c9d937..2c3e805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -20,6 +20,7 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.Log;
import com.android.keyguard.KeyguardConstants;
@@ -140,11 +141,14 @@
@Override
public void onFingerprintAcquired() {
+ Trace.beginSection("FingerprintUnlockController#onFingerprintAcquired");
releaseFingerprintWakeLock();
if (!mUpdateMonitor.isDeviceInteractive()) {
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, FINGERPRINT_WAKE_LOCK_NAME);
+ Trace.beginSection("acquiring wake-and-unlock");
mWakeLock.acquire();
+ Trace.endSection();
if (DEBUG_FP_WAKELOCK) {
Log.i(TAG, "fingerprint acquired, grabbing fp wakelock");
}
@@ -159,12 +163,15 @@
mStatusBarWindowManager.setForceDozeBrightness(true);
}
}
+ Trace.endSection();
}
@Override
public void onFingerprintAuthenticated(int userId) {
+ Trace.beginSection("FingerprintUnlockController#onFingerprintAuthenticated");
if (mUpdateMonitor.isGoingToSleep()) {
mPendingAuthenticatedUserId = userId;
+ Trace.endSection();
return;
}
boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
@@ -175,25 +182,34 @@
}
mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:FINGERPRINT");
}
+ Trace.beginSection("release wake-and-unlock");
releaseFingerprintWakeLock();
+ Trace.endSection();
switch (mMode) {
case MODE_DISMISS_BOUNCER:
+ Trace.beginSection("MODE_DISMISS");
mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated(
false /* strongAuth */);
+ Trace.endSection();
break;
case MODE_UNLOCK:
case MODE_SHOW_BOUNCER:
+ Trace.beginSection("MODE_UNLOCK or MODE_SHOW_BOUNCER");
if (!wasDeviceInteractive) {
mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
}
mStatusBarKeyguardViewManager.animateCollapsePanels(
FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
+ Trace.endSection();
break;
case MODE_WAKE_AND_UNLOCK_PULSING:
+ Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
mPhoneStatusBar.updateMediaMetaData(false /* metaDataChanged */,
true /* allowEnterAnimation */);
// Fall through.
+ Trace.endSection();
case MODE_WAKE_AND_UNLOCK:
+ Trace.beginSection("MODE_WAKE_AND_UNLOCK");
mStatusBarWindowManager.setStatusBarFocusable(false);
mDozeScrimController.abortPulsing();
mKeyguardViewMediator.onWakeAndUnlocking();
@@ -201,6 +217,7 @@
if (mPhoneStatusBar.getNavigationBarView() != null) {
mPhoneStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
}
+ Trace.endSection();
break;
case MODE_ONLY_WAKE:
case MODE_NONE:
@@ -210,6 +227,7 @@
mStatusBarWindowManager.setForceDozeBrightness(false);
}
mPhoneStatusBar.notifyFpAuthModeChanged();
+ Trace.endSection();
}
@Override
@@ -219,6 +237,7 @@
@Override
public void onFinishedGoingToSleep(int why) {
+ Trace.beginSection("FingerprintUnlockController#onFinishedGoingToSleep");
if (mPendingAuthenticatedUserId != -1) {
// Post this to make sure it's executed after the device is fully locked.
@@ -230,6 +249,7 @@
});
}
mPendingAuthenticatedUserId = -1;
+ Trace.endSection();
}
public int getMode() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 34ce6d5..b623f19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -84,6 +84,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
@@ -1182,6 +1183,7 @@
}
protected void startKeyguard() {
+ Trace.beginSection("PhoneStatusBar#startKeyguard");
KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
mFingerprintUnlockController = new FingerprintUnlockController(mContext,
mStatusBarWindowManager, mDozeScrimController, keyguardViewMediator,
@@ -1216,6 +1218,7 @@
mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback();
mLightStatusBarController.setFingerprintUnlockController(mFingerprintUnlockController);
+ Trace.endSection();
}
@Override
@@ -2088,12 +2091,20 @@
* Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
*/
public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
- if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) return;
+ Trace.beginSection("PhoneStatusBar#updateMediaMetaData");
+ if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
+ Trace.endSection();
+ return;
+ }
- if (mBackdrop == null) return; // called too early
+ if (mBackdrop == null) {
+ Trace.endSection();
+ return; // called too early
+ }
if (mLaunchTransitionFadingAway) {
mBackdrop.setVisibility(View.INVISIBLE);
+ Trace.endSection();
return;
}
@@ -2246,6 +2257,7 @@
}
}
}
+ Trace.endSection();
}
protected int adjustDisableFlags(int state) {
@@ -4085,6 +4097,7 @@
* @return true if we would like to stay in the shade, false if it should go away entirely
*/
public boolean hideKeyguard() {
+ Trace.beginSection("PhoneStatusBar#hideKeyguard");
boolean staying = mLeaveOpenOnKeyguardHide;
setBarState(StatusBarState.SHADE);
View viewToClick = null;
@@ -4129,6 +4142,7 @@
mNotificationPanel.onAffordanceLaunchEnded();
mNotificationPanel.animate().cancel();
mNotificationPanel.setAlpha(1f);
+ Trace.endSection();
return staying;
}
@@ -4203,6 +4217,7 @@
}
protected void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) {
+ Trace.beginSection("PhoneStatusBar#updateKeyguardState");
if (mState == StatusBarState.KEYGUARD) {
mKeyguardIndicationController.setVisible(true);
mNotificationPanel.resetViews();
@@ -4234,9 +4249,11 @@
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mStatusBarKeyguardViewManager.isSecure());
+ Trace.endSection();
}
private void updateDozingState() {
+ Trace.beginSection("PhoneStatusBar#updateDozingState");
boolean animate = !mDozing && mDozeScrimController.isPulsing();
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
@@ -4247,6 +4264,7 @@
mDozeScrimController.setDozing(mDozing &&
mFingerprintUnlockController.getMode()
!= FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, animate);
+ Trace.endSection();
}
public void updateStackScrollerState(boolean goingToFullShade, boolean fromShadeLocked) {
@@ -4848,11 +4866,13 @@
}
private void updateDozing() {
+ Trace.beginSection("PhoneStatusBar#updateDozing");
// When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
|| mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
updateDozingState();
+ Trace.endSection();
}
private final class ShadeUpdates {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3691a42..c72f994 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -185,13 +185,17 @@
}
public void onStartedWakingUp() {
+ Trace.beginSection("StatusBarKeyguardViewManager#onStartedWakingUp");
mDeviceInteractive = true;
mDeviceWillWakeUp = false;
mPhoneStatusBar.onStartedWakingUp();
+ Trace.endSection();
}
public void onScreenTurningOn() {
+ Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurningOn");
mPhoneStatusBar.onScreenTurningOn();
+ Trace.endSection();
}
public boolean isScreenTurnedOn() {
@@ -199,6 +203,7 @@
}
public void onScreenTurnedOn() {
+ Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
mScreenTurnedOn = true;
if (mDeferScrimFadeOut) {
mDeferScrimFadeOut = false;
@@ -207,6 +212,7 @@
updateStates();
}
mPhoneStatusBar.onScreenTurnedOn();
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index a91cd51..7e92edf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
+import android.os.Trace;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -85,6 +86,7 @@
}
private void update(boolean updateAlways) {
+ Trace.beginSection("UnlockMethodCache#update");
int user = KeyguardUpdateMonitor.getCurrentUser();
boolean secure = mLockPatternUtils.isSecure(user);
boolean canSkipBouncer = !secure || mKeyguardUpdateMonitor.getUserCanSkipBouncer(user);
@@ -102,6 +104,7 @@
mFaceUnlockRunning = faceUnlockRunning;
notifyListeners();
}
+ Trace.endSection();
}
private void notifyListeners() {
@@ -133,10 +136,13 @@
@Override
public void onFingerprintAuthenticated(int userId) {
+ Trace.beginSection("KeyguardUpdateMonitorCallback#onFingerprintAuthenticated");
if (!mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()) {
+ Trace.endSection();
return;
}
update(false /* updateAlways */);
+ Trace.endSection();
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 71280be..b8f8bfa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1087,9 +1087,9 @@
}
@Override
- public void onChange(boolean selfChange, Uri uri) {
+ public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
if (mFontScaleUri.equals(uri)) {
- updateFontScaleIfNeeded();
+ updateFontScaleIfNeeded(userId);
}
}
}
@@ -18726,23 +18726,28 @@
int userId = UserHandle.getCallingUserId();
synchronized(this) {
- final long origId = Binder.clearCallingIdentity();
+ updatePersistentConfigurationLocked(values, userId);
+ }
+ }
+
+ private void updatePersistentConfigurationLocked(Configuration values, @UserIdInt int userId) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
updateConfigurationLocked(values, null, false, true, userId, false /* deferResume */);
+ } finally {
Binder.restoreCallingIdentity(origId);
}
}
- private void updateFontScaleIfNeeded() {
- final int currentUserId;
- synchronized(this) {
- currentUserId = mUserController.getCurrentUserIdLocked();
- }
+ private void updateFontScaleIfNeeded(@UserIdInt int userId) {
final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
- FONT_SCALE, 1.0f, currentUserId);
+ FONT_SCALE, 1.0f, userId);
if (mConfiguration.fontScale != scaleFactor) {
final Configuration configuration = mWindowManager.computeNewConfiguration();
configuration.fontScale = scaleFactor;
- updatePersistentConfiguration(configuration);
+ synchronized (this) {
+ updatePersistentConfigurationLocked(configuration, userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 752dbd9..5a66140 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3562,11 +3562,18 @@
if (mode == FINISH_IMMEDIATELY
|| (prevState == ActivityState.PAUSED
- && (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
+ && (mode == FINISH_AFTER_PAUSE || mode == FINISH_AFTER_VISIBLE
+ || mStackId == PINNED_STACK_ID))
|| prevState == ActivityState.STOPPED
|| prevState == ActivityState.INITIALIZING) {
r.makeFinishingLocked();
boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
+
+ if (prevState == ActivityState.PAUSED && mode == FINISH_AFTER_VISIBLE) {
+ // Finishing activity that was in paused state - this can happen if it was in
+ // not currently focused stack. Need to make something visible in its place.
+ mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+ }
if (activityRemoved) {
mStackSupervisor.resumeFocusedStackTopActivityLocked();
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c16fc62..e31df57 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1051,7 +1051,7 @@
return r;
}
- // Return to the home stack.
+ // Look in other non-focused and non-home stacks.
final ArrayList<ActivityStack> stacks = mHomeStack.mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index a7d09d0..b24edb9 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -21,6 +21,7 @@
import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -50,6 +51,7 @@
private final ActivityManagerService mService;
private final int mUserId;
private final ProgressReporter mProgress;
+ private final boolean mQuiet;
private final Intent mIntent;
private final List<ResolveInfo> mTargets;
@@ -61,16 +63,13 @@
mService = service;
mUserId = userId;
mProgress = progress;
+ mQuiet = quiet;
mIntent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
mIntent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE | Intent.FLAG_DEBUG_TRIAGED_MISSING);
mTargets = mService.mContext.getPackageManager().queryBroadcastReceiversAsUser(mIntent,
MATCH_SYSTEM_ONLY, UserHandle.of(userId));
-
- if (!quiet) {
- mHandler.obtainMessage(MSG_SHOW).sendToTarget();
- }
}
public void sendNext() {
@@ -87,6 +86,10 @@
return;
}
+ if (!mQuiet) {
+ mHandler.obtainMessage(MSG_SHOW, mTargets.size(), mIndex).sendToTarget();
+ }
+
final ResolveInfo ri = mTargets.get(mIndex++);
final ComponentName componentName = ri.activityInfo.getComponentName();
@@ -121,13 +124,26 @@
final Context context = mService.mContext;
final NotificationManager notifManager = context
.getSystemService(NotificationManager.class);
+ final int max = msg.arg1;
+ final int index = msg.arg2;
switch (msg.what) {
case MSG_SHOW:
final CharSequence title = context
- .getText(R.string.android_upgrading_notification_title);
- final CharSequence message = context
- .getText(R.string.android_upgrading_notification_body);
+ .getText(R.string.android_upgrading_title);
+
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.HelpTrampoline");
+ intent.putExtra(Intent.EXTRA_TEXT, "help_url_upgrading");
+
+ final PendingIntent contentIntent;
+ if (context.getPackageManager().resolveActivity(intent, 0) != null) {
+ contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
+ } else {
+ contentIntent = null;
+ }
+
final Notification notif = new Notification.Builder(mService.mContext)
.setSmallIcon(R.drawable.stat_sys_adb)
.setWhen(0)
@@ -138,8 +154,9 @@
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
- .setContentText(message)
+ .setContentIntent(contentIntent)
.setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setProgress(max, index, false)
.build();
notifManager.notifyAsUser(TAG, 0, notif, UserHandle.of(mUserId));
break;
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 1cff926..9c9e97e 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -474,6 +474,7 @@
if (emailPackage != null
&& doesPackageSupportRuntimePermissions(emailPackage)) {
grantRuntimePermissionsLPw(emailPackage, CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissionsLPw(emailPackage, CALENDAR_PERMISSIONS, userId);
}
// Browser
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 54c36e1..a4c90f7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1963,10 +1963,6 @@
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
- // Disable any carrier apps. We do this very early in boot to prevent the apps from being
- // disabled after already being started.
- CarrierAppUtils.disableCarrierAppsUntilPrivileged(context.getOpPackageName(), m,
- UserHandle.USER_SYSTEM);
ServiceManager.addService("package", m);
return m;
}
@@ -17875,6 +17871,11 @@
public void systemReady() {
mSystemReady = true;
+ // Disable any carrier apps. We do this very early in boot to prevent the apps from being
+ // disabled after already being started.
+ CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
+ mContext.getContentResolver(), UserHandle.USER_SYSTEM);
+
// Read the compatibilty setting when the system is ready.
boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
mContext.getContentResolver(),
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index 2a2a4b2..3cf4200 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -259,7 +259,7 @@
continue;
}
- ShortcutService.warnForInvalidTag(depth, tag);
+ Log.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
}
} finally {
if (parser != null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 93693ce..d875f1e9 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1037,6 +1037,24 @@
}
}
+ private boolean isUserUnlocked(@UserIdInt int userId) {
+ final long token = injectClearCallingIdentity();
+ try {
+ // Weird: when SystemService.onUnlockUser() is called, the user state is still
+ // unlocking, as opposed to unlocked. So we need to accept the "unlocking" state too.
+ // We know when the user is unlocking, the CE storage is already unlocked.
+ return mUserManager.isUserUnlockingOrUnlocked(userId);
+ } finally {
+ injectRestoreCallingIdentity(token);
+ }
+ }
+
+ void throwIfUserLocked(@UserIdInt int userId) {
+ if (!isUserUnlocked(userId)) {
+ throw new IllegalStateException("User " + userId + " is locked or not running");
+ }
+ }
+
@GuardedBy("mLock")
@NonNull
private boolean isUserLoadedLocked(@UserIdInt int userId) {
@@ -1047,6 +1065,11 @@
@GuardedBy("mLock")
@NonNull
ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
+ if (!isUserUnlocked(userId)) {
+ wtf("User still locked");
+ return new ShortcutUser(this, userId);
+ }
+
ShortcutUser userPackages = mUsers.get(userId);
if (userPackages == null) {
userPackages = loadUserLocked(userId);
@@ -1535,6 +1558,7 @@
public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
final int size = newShortcuts.size();
@@ -1585,6 +1609,7 @@
public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
final int size = newShortcuts.size();
@@ -1664,6 +1689,7 @@
public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
final int size = newShortcuts.size();
@@ -1715,6 +1741,7 @@
CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) {
verifyCaller(packageName, userId);
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+ throwIfUserLocked(userId);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
@@ -1743,6 +1770,7 @@
public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) {
verifyCaller(packageName, userId);
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+ throwIfUserLocked(userId);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
@@ -1764,6 +1792,7 @@
@UserIdInt int userId) {
verifyCaller(packageName, userId);
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+ throwIfUserLocked(userId);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
@@ -1787,6 +1816,7 @@
@Override
public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
@@ -1802,6 +1832,8 @@
public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
+
synchronized (mLock) {
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1813,6 +1845,8 @@
public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
+
synchronized (mLock) {
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1824,6 +1858,8 @@
public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
+
synchronized (mLock) {
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1854,6 +1890,7 @@
@Override
public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
@@ -1865,6 +1902,7 @@
@Override
public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
synchronized (mLock) {
return getNextResetTimeLocked();
@@ -1883,6 +1921,7 @@
@Override
public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
verifyCaller(packageName, userId);
+ throwIfUserLocked(userId);
Preconditions.checkNotNull(shortcutId);
@@ -1951,6 +1990,10 @@
Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId);
}
enforceResetThrottlingPermission();
+ if (!isUserUnlocked(userId)) {
+ // This is called by system UI, so no need to throw. Just ignore.
+ return;
+ }
resetPackageThrottling(packageName, userId);
}
@@ -1968,6 +2011,8 @@
// even when hasShortcutPermission() is overridden.
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
+ throwIfUserLocked(userId);
+
synchronized (mLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
@@ -2125,6 +2170,10 @@
@Nullable ComponentName componentName,
int queryFlags, int userId) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
+ if (!isUserUnlocked(userId) || !isUserUnlocked(launcherUserId)) {
+ return ret;
+ }
+
final boolean cloneKeyFieldOnly =
((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO
@@ -2202,6 +2251,10 @@
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
+ if (!isUserUnlocked(userId) || !isUserUnlocked(launcherUserId)) {
+ return false;
+ }
+
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave();
@@ -2218,6 +2271,10 @@
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
+ if (!isUserUnlocked(userId) || !isUserUnlocked(launcherUserId)) {
+ return null;
+ }
+
final ShortcutPackage p = getUserShortcutsLocked(userId)
.getPackageShortcutsIfExists(packageName);
if (p == null) {
@@ -2239,6 +2296,10 @@
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkNotNull(shortcutIds, "shortcutIds");
+ if (!isUserUnlocked(userId) || !isUserUnlocked(launcherUserId)) {
+ return;
+ }
+
synchronized (mLock) {
final ShortcutLauncher launcher =
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
@@ -2259,6 +2320,10 @@
Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
+ if (!isUserUnlocked(userId) || !isUserUnlocked(launcherUserId)) {
+ return null;
+ }
+
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave();
@@ -2289,6 +2354,10 @@
Preconditions.checkNotNull(packageName, "packageName");
Preconditions.checkNotNull(shortcutId, "shortcutId");
+ if (!isUserUnlocked(userId) || !isUserUnlocked(launcherUserId)) {
+ return 0;
+ }
+
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave();
@@ -2313,6 +2382,10 @@
Preconditions.checkNotNull(packageName, "packageName");
Preconditions.checkNotNull(shortcutId, "shortcutId");
+ if (!isUserUnlocked(userId) || !isUserUnlocked(launcherUserId)) {
+ return null;
+ }
+
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave();
@@ -2345,6 +2418,9 @@
@Override
public boolean hasShortcutHostPermission(int launcherUserId,
@NonNull String callingPackage) {
+ if (!isUserUnlocked(launcherUserId)) {
+ return false;
+ }
return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
}
}
@@ -2395,7 +2471,7 @@
final long token = injectClearCallingIdentity();
try {
- if (!mUserManager.isUserUnlocked(userId)) {
+ if (!isUserUnlocked(userId)) {
if (DEBUG) {
Slog.d(TAG, "Ignoring package broadcast " + action
+ " for locked/stopped user " + userId);
@@ -3208,7 +3284,7 @@
case "--user":
if (takeUser) {
mUserId = UserHandle.parseUserArg(getNextArgRequired());
- if (!mUserManager.isUserUnlocked(mUserId)) {
+ if (!isUserUnlocked(mUserId)) {
throw new CommandException(
"User " + mUserId + " is not running or locked");
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 490ff9e..09c2b27 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6185,7 +6185,9 @@
return;
}
mPendingPanicGestureUptime = SystemClock.uptimeMillis();
- mNavigationBarController.showTransient();
+ if (!isNavBarEmpty(mLastSystemUiFlags)) {
+ mNavigationBarController.showTransient();
+ }
}
}
};
@@ -6197,7 +6199,8 @@
return;
}
boolean sb = mStatusBarController.checkShowTransientBarLw();
- boolean nb = mNavigationBarController.checkShowTransientBarLw();
+ boolean nb = mNavigationBarController.checkShowTransientBarLw()
+ && !isNavBarEmpty(mLastSystemUiFlags);
if (sb || nb) {
// Don't show status bar when swiping on already visible navigation bar
if (!nb && swipeTarget == mNavigationBar) {
@@ -7550,7 +7553,9 @@
// we're no longer on the Keyguard and the screen is ready. We can now request the bars.
mPendingPanicGestureUptime = 0;
mStatusBarController.showTransient();
- mNavigationBarController.showTransient();
+ if (!isNavBarEmpty(vis)) {
+ mNavigationBarController.showTransient();
+ }
}
final boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 75f6120..74a6131 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -252,6 +252,7 @@
if (DEBUG) {
Slog.v(TAG, "Wallpaper written; generating crop");
}
+ SELinux.restorecon(changedFile);
if (moved) {
// This is a restore, so generate the crop using any just-restored new
// crop guidelines, making sure to preserve our local dimension hints.
@@ -259,7 +260,6 @@
if (DEBUG) {
Slog.v(TAG, "moved-to, therefore restore; reloading metadata");
}
- SELinux.restorecon(changedFile);
loadSettingsLocked(wallpaper.userId, true);
}
generateCrop(wallpaper);
@@ -353,8 +353,8 @@
(cropHint.bottom > options.outHeight ? options.outHeight - cropHint.bottom : 0));
// Don't bother cropping if what we're left with is identity
- needCrop = (options.outHeight >= cropHint.height()
- && options.outWidth >= cropHint.width());
+ needCrop = (options.outHeight > cropHint.height()
+ && options.outWidth > cropHint.width());
}
// scale if the crop height winds up not matching the recommended metrics
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index eac72b0..a9624cf 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -337,6 +337,30 @@
}
}
+ void clearAnimatingFlags() {
+ for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = allAppWindows.get(i);
+ // We don't want to clear it out for windows that get replaced, because the
+ // animation depends on the flag to remove the replaced window.
+ //
+ // We also don't clear the mAnimatingExit flag for windows which have the
+ // mRemoveOnExit flag. This indicates an explicit remove request has been issued
+ // by the client. We should let animation proceed and not clear this flag or
+ // they won't eventually be removed by WindowStateAnimator#finishExit.
+ if (!win.mWillReplaceWindow && !win.mRemoveOnExit) {
+ win.mAnimatingExit = false;
+ // Clear mAnimating flag together with mAnimatingExit. When animation
+ // changes from exiting to entering, we need to clear this flag until the
+ // new animation gets applied, so that isAnimationStarting() becomes true
+ // until then.
+ // Otherwise applySurfaceChangesTransaction will faill to skip surface
+ // placement for this window during this period, one or more frame will
+ // show up with wrong position or scale.
+ win.mWinAnimator.mAnimating = false;
+ }
+ }
+ }
+
void destroySurfaces() {
destroySurfaces(false /*cleanupOnResume*/);
}
@@ -363,15 +387,6 @@
win.mWinAnimator.destroyPreservedSurfaceLocked();
- if (cleanupOnResume) {
- // If the window has an unfinished exit animation, consider that animation
- // done and mark the window destroying so that it goes through the cleanup.
- if (win.mAnimatingExit) {
- win.mDestroying = true;
- win.mAnimatingExit = false;
- }
- }
-
if (!win.mDestroying) {
continue;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8c5481d..6451c74 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3081,6 +3081,9 @@
if (oldVisibility == View.GONE) {
winAnimator.mEnterAnimationPending = true;
}
+
+ win.mLastVisibleLayoutRotation = mRotation;
+
winAnimator.mEnteringAnimation = true;
if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
win.prepareWindowToDisplayDuringRelayout(outConfig);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 25ad07e..155d8d0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -395,6 +395,13 @@
boolean mOrientationChanging;
/**
+ * The orientation during the last visible call to relayout. If our
+ * current orientation is different, the window can't be ready
+ * to be shown.
+ */
+ int mLastVisibleLayoutRotation = -1;
+
+ /**
* How long we last kept the screen frozen.
*/
int mLastFreezeDuration;
@@ -2095,6 +2102,7 @@
Slog.v(TAG, "Destroying saved surface: " + this);
}
mWinAnimator.destroySurfaceLocked();
+ mSurfaceSaved = false;
}
mWasVisibleBeforeClientHidden = false;
}
@@ -2103,7 +2111,18 @@
if (!mSurfaceSaved) {
return;
}
+
+ // Sometimes we save surfaces due to layout invisible
+ // directly after rotation occurs. However this means
+ // the surface was never laid out in the new orientation.
+ // We can only restore to the last rotation we were
+ // laid out as visible in.
+ if (mLastVisibleLayoutRotation != mService.mRotation) {
+ destroySavedSurface();
+ return;
+ }
mSurfaceSaved = false;
+
if (mWinAnimator.mSurfaceController != null) {
setHasSurface(true);
mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 0bd5eaf..ee4a9a4 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1138,6 +1138,12 @@
if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
openingAppHasWallpaper = true;
}
+ // Clearing the mAnimatingExit flag before entering animation. It's set to
+ // true if app window is removed, or window relayout to invisible.
+ // This also affects window visibility. We need to clear it *before*
+ // maybeUpdateTransitToWallpaper() as the transition selection depends on
+ // wallpaper target visibility.
+ wtoken.clearAnimatingFlags();
}
voiceInteraction |= wtoken.voiceInteraction;
@@ -1262,26 +1268,6 @@
int layer = -1;
for (int j = 0; j < wtoken.allAppWindows.size(); j++) {
final WindowState win = wtoken.allAppWindows.get(j);
- // Clearing the mAnimatingExit flag before entering animation. It will be set to true
- // if app window is removed, or window relayout to invisible. We don't want to
- // clear it out for windows that get replaced, because the animation depends on
- // the flag to remove the replaced window.
- //
- // We also don't clear the mAnimatingExit flag for windows which have the
- // mRemoveOnExit flag. This indicates an explicit remove request has been issued
- // by the client. We should let animation proceed and not clear this flag or
- // they won't eventually be removed by WindowStateAnimator#finishExit.
- if (!win.mWillReplaceWindow && !win.mRemoveOnExit) {
- win.mAnimatingExit = false;
- // Clear mAnimating flag together with mAnimatingExit. When animation
- // changes from exiting to entering, we need to clear this flag until the
- // new animation gets applied, so that isAnimationStarting() becomes true
- // until then.
- // Otherwise applySurfaceChangesTransaction will faill to skip surface
- // placement for this window during this period, one or more frame will
- // show up with wrong position or scale.
- win.mWinAnimator.mAnimating = false;
- }
if (win.mWinAnimator.mAnimLayer > layer) {
layer = win.mWinAnimator.mAnimLayer;
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index ecebbc4..0515a9a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -687,19 +687,25 @@
// Set up isUserRunning and isUserUnlocked.
when(mMockUserManager.isUserRunning(anyInt())).thenAnswer(new AnswerWithSystemCheck<>(
- inv -> mRunningUsers.get((Integer) inv.getArguments()[0])));
+ inv -> b(mRunningUsers.get((Integer) inv.getArguments()[0]))));
- when(mMockUserManager.isUserUnlocked(anyInt())).thenAnswer(new AnswerWithSystemCheck<>(
- inv -> {
- final int userId = (Integer) inv.getArguments()[0];
- return mRunningUsers.get(userId) && mUnlockedUsers.get(userId);
- }));
+ when(mMockUserManager.isUserUnlocked(anyInt()))
+ .thenAnswer(new AnswerWithSystemCheck<>(inv -> {
+ final int userId = (Integer) inv.getArguments()[0];
+ return b(mRunningUsers.get(userId)) && b(mUnlockedUsers.get(userId));
+ }));
+ // isUserUnlockingOrUnlocked() return the same value as isUserUnlocked().
+ when(mMockUserManager.isUserUnlockingOrUnlocked(anyInt()))
+ .thenAnswer(new AnswerWithSystemCheck<>(inv -> {
+ final int userId = (Integer) inv.getArguments()[0];
+ return b(mRunningUsers.get(userId)) && b(mUnlockedUsers.get(userId));
+ }));
- // User 0 is always running
+ // User 0 and P0 are always running
mRunningUsers.put(USER_0, true);
mRunningUsers.put(USER_10, false);
mRunningUsers.put(USER_11, false);
- mRunningUsers.put(USER_P0, false);
+ mRunningUsers.put(USER_P0, true);
// Unlock all users by default.
mUnlockedUsers.put(USER_0, true);
@@ -715,6 +721,10 @@
setCaller(CALLING_PACKAGE_1);
}
+ private static boolean b(Boolean value) {
+ return (value != null && value);
+ }
+
/**
* Returns a boolean but also checks if the current UID is SYSTEM_UID.
*/
@@ -1726,6 +1736,8 @@
}
protected void prepareCrossProfileDataSet() {
+ mRunningUsers.put(USER_10, true); // this test needs user 10.
+
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index c0752e0..b1c0ed4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -294,6 +294,8 @@
// TODO Check max number
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
});
@@ -340,6 +342,8 @@
// TODO Check fields.
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1"))));
});
@@ -348,6 +352,8 @@
public void testPublishWithNoActivity() {
// If activity is not explicitly set, use the default one.
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
// s1 and s3 has no activities.
final ShortcutInfo si1 = new ShortcutInfo.Builder(mClientContext, "si1")
@@ -450,6 +456,8 @@
}
public void testPublishWithNoActivity_noMainActivityInPackage() {
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
final ShortcutInfo si1 = new ShortcutInfo.Builder(mClientContext, "si1")
.setShortLabel("label1")
@@ -722,6 +730,8 @@
});
// For USER-10, let's try without updating the times.
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("10s1", bmp32x32),
@@ -1071,6 +1081,8 @@
// TODO Check bitmap removal too.
+ mRunningUsers.put(USER_11, true);
+
runWithCaller(CALLING_PACKAGE_2, USER_11, () -> {
mManager.updateShortcuts(list());
});
@@ -2048,6 +2060,9 @@
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
+
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
@@ -2769,6 +2784,8 @@
.areAllWithKeyFieldsOnly()
.areAllDynamic();
+ mRunningUsers.put(USER_10, true);
+
// Different user, callback shouldn't be called.
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -2980,27 +2997,7 @@
assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_3,
"s1", "s2", "s3", "s4", "s5", "s6");
- // Work profile, but not running, so don't send notifications.
-
- resetAll(all);
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- mManager.removeDynamicShortcuts(list());
- });
- waitOnMainThread();
-
- assertCallbackNotReceived(c0_1);
- assertCallbackNotReceived(c0_2);
- assertCallbackNotReceived(c0_3);
- assertCallbackNotReceived(c0_4);
- assertCallbackNotReceived(cP0_1);
- assertCallbackNotReceived(c10_1);
- assertCallbackNotReceived(c10_2);
- assertCallbackNotReceived(c11_1);
-
- // Work profile, now running.
- mRunningUsers.clear();
- mRunningUsers.put(USER_P0, true);
-
+ // Work profile.
resetAll(all);
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
mManager.removeDynamicShortcuts(list());
@@ -3017,7 +3014,6 @@
assertCallbackReceived(cP0_1, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
// Normal secondary user.
- mRunningUsers.clear();
mRunningUsers.put(USER_10, true);
resetAll(all);
@@ -3112,6 +3108,9 @@
assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
assertEquals(2, mManager.getRemainingCallCount());
});
+
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
@@ -3223,6 +3222,8 @@
HANDLE_USER_0);
});
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s10_1"))));
@@ -3558,6 +3559,9 @@
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
+
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
@@ -3876,6 +3880,8 @@
setCaller(CALLING_PACKAGE_3, USER_0);
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+ mRunningUsers.put(USER_10, true);
+
setCaller(CALLING_PACKAGE_1, USER_10);
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
@@ -3988,6 +3994,8 @@
setCaller(CALLING_PACKAGE_3, USER_0);
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
+ mRunningUsers.put(USER_10, true);
+
setCaller(CALLING_PACKAGE_1, USER_10);
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
@@ -4114,6 +4122,8 @@
makeShortcutWithIcon("s1", res32x32))));
});
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("s1", res32x32),
@@ -5441,6 +5451,8 @@
});
// Try on another user, with some packages uninstalled.
+ mRunningUsers.put(USER_10, true);
+
uninstallPackage(USER_10, CALLING_PACKAGE_1);
uninstallPackage(USER_10, CALLING_PACKAGE_3);
@@ -5621,6 +5633,8 @@
uninstallPackage(USER_10, CALLING_PACKAGE_4);
mService.handleUnlockUser(USER_0);
+
+ mRunningUsers.put(USER_10, true);
mService.handleUnlockUser(USER_10);
// Originally no manifest shortcuts.
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 54b2a15..ad5bc77 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -851,6 +851,8 @@
}
public void testShortcutInfoSaveAndLoad() throws InterruptedException {
+ mRunningUsers.put(USER_10, true);
+
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
@@ -930,6 +932,8 @@
}
public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
+ mRunningUsers.put(USER_10, true);
+
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32);
@@ -1794,6 +1798,8 @@
}
public void testReportShortcutUsed() {
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
reset(mMockUsageStatsManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java
index 5f24637..ba4dbc1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java
@@ -183,6 +183,8 @@
}
public void testHasShortcutHostPermissionInner_multiUser() {
+ mRunningUsers.put(USER_10, true);
+
prepareGetHomeActivitiesAsUser(
/* preferred */ null,
list(getSystemLauncher(), getFallbackLauncher()),
@@ -220,6 +222,8 @@
}
public void testHasShortcutHostPermissionInner_clearCache() {
+ mRunningUsers.put(USER_10, true);
+
prepareGetHomeActivitiesAsUser(
/* preferred */ null,
list(getSystemLauncher(), getFallbackLauncher()),
@@ -242,12 +246,18 @@
assertEquals(cn(CALLING_PACKAGE_2, "name"),
mService.getUserShortcutsLocked(USER_10).getCachedLauncher());
+ // Test it on a non-running user.
// Send ACTION_PREFERRED_ACTIVITY_CHANGED on user 10.
// But the user is not running, so will be ignored.
+ mRunningUsers.put(USER_10, false);
+
mService.mPackageMonitor.onReceive(mServiceContext,
new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED).putExtra(
Intent.EXTRA_USER_HANDLE, USER_10));
+ // Need to run the user again to access the internal status.
+ mRunningUsers.put(USER_10, true);
+
assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
index f9ff514..3c99174 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
@@ -151,10 +151,14 @@
mInjectedCallingUid = Process.SHELL_UID;
+ mRunningUsers.put(USER_10, false);
+
assertTrue(resultContains(
callShellCommand("reset-throttling", "--user", "10"),
"User 10 is not running or locked"));
+ mRunningUsers.put(USER_10, true);
+
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.getRemainingCallCount() < 3);
});
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 5d29363..e5c91aa 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -55,6 +55,8 @@
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
+import org.json.JSONException;
+import org.json.JSONObject;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -86,7 +88,7 @@
public class ShortcutManagerTestUtils {
private static final String TAG = "ShortcutManagerUtils";
- private static final boolean ENABLE_DUMPSYS = false; // DO NOT SUBMIT WITH true
+ private static final boolean ENABLE_DUMPSYS = true; // DO NOT SUBMIT WITH true
private static final int STANDARD_TIMEOUT_SEC = 5;
@@ -233,6 +235,29 @@
+ " --user " + userId + " " + packageName);
}
+ public static void anyContains(List<String> result, String expected) {
+ for (String l : result) {
+ if (l.contains(expected)) {
+ return;
+ }
+ }
+ fail("Result didn't contain '" + expected + "': was\n" + result);
+ }
+
+ public static void enableComponent(Instrumentation instrumentation, ComponentName cn,
+ boolean enable) {
+
+ final String word = (enable ? "enable" : "disable");
+ runCommand(instrumentation,
+ "pm " + word + " " + cn.flattenToString()
+ , result ->concatResult(result).contains(word));
+ }
+
+ public static void appOps(Instrumentation instrumentation, String packageName,
+ String op, String mode) {
+ runCommand(instrumentation, "appops set " + packageName + " " + op + " " + mode);
+ }
+
public static void dumpsysShortcut(Instrumentation instrumentation) {
if (!ENABLE_DUMPSYS) {
return;
@@ -243,6 +268,18 @@
}
}
+ public static JSONObject getCheckinDump(Instrumentation instrumentation) throws JSONException {
+ return new JSONObject(concatResult(runCommand(instrumentation, "dumpsys shortcut -c")));
+ }
+
+ public static boolean isLowRamDevice(Instrumentation instrumentation) throws JSONException {
+ return getCheckinDump(instrumentation).getBoolean("lowRam");
+ }
+
+ public static int getIconSize(Instrumentation instrumentation) throws JSONException {
+ return getCheckinDump(instrumentation).getInt("iconSize");
+ }
+
public static Bundle makeBundle(Object... keysAndValues) {
assertTrue((keysAndValues.length % 2) == 0);
@@ -1014,4 +1051,15 @@
return asserter;
}
+
+ public static void retryUntil(BooleanSupplier checker, String message) {
+ final long timeOut = System.currentTimeMillis() + 30 * 1000; // wait for 30 seconds.
+ while (!checker.getAsBoolean()) {
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignore) {
+ }
+ }
+ assertTrue(message, checker.getAsBoolean());
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1d19637..df9242d 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -49,7 +49,6 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.SomeArgs;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.FgThread;
@@ -321,7 +320,6 @@
private boolean mConnected;
private boolean mHostConnected;
private boolean mSourcePower;
- private boolean mSinkPower;
private boolean mConfigured;
private boolean mUsbDataUnlocked;
private String mCurrentFunctions;
@@ -403,19 +401,7 @@
public void updateHostState(UsbPort port, UsbPortStatus status) {
boolean hostConnected = status.getCurrentDataRole() == UsbPort.DATA_ROLE_HOST;
boolean sourcePower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE;
- boolean sinkPower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SINK;
-
- if (DEBUG) {
- Slog.i(TAG, "updateHostState " + port + ": dataRole=" + status.getCurrentDataRole() +
- ", powerRole=" + status.getCurrentPowerRole());
- }
-
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = hostConnected ? 1 :0;
- args.argi2 = sourcePower ? 1 :0;
- args.argi3 = sinkPower ? 1 :0;
-
- obtainMessage(MSG_UPDATE_HOST_STATE, args).sendToTarget();
+ obtainMessage(MSG_UPDATE_HOST_STATE, hostConnected ? 1 :0, sourcePower ? 1 :0).sendToTarget();
}
private boolean waitForState(String state) {
@@ -732,11 +718,8 @@
}
break;
case MSG_UPDATE_HOST_STATE:
- SomeArgs args = (SomeArgs) msg.obj;
- mHostConnected = (args.argi1 == 1);
- mSourcePower = (args.argi2 == 1);
- mSinkPower = (args.argi3 == 1);
- args.recycle();
+ mHostConnected = (msg.arg1 == 1);
+ mSourcePower = (msg.arg2 == 1);
updateUsbNotification();
if (mBootCompleted) {
updateUsbStateBroadcastIfNeeded();
@@ -826,8 +809,6 @@
}
} else if (mSourcePower) {
id = com.android.internal.R.string.usb_supplying_notification_title;
- } else if (mSinkPower) {
- id = com.android.internal.R.string.usb_charging_notification_title;
}
if (id != mUsbNotificationId) {
// clear notification if title needs changing
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ee055f4..4da5ff2 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -514,67 +514,87 @@
* @return A human readable string representation.
*/
public static String capabilitiesToString(int capabilities) {
+ return capabilitiesToStringInternal(capabilities, true /* isLong */);
+ }
+
+ /**
+ * Renders a set of capability bits ({@code CAPABILITY_*}) as a *short* human readable
+ * string.
+ *
+ * @param capabilities A capability bit field.
+ * @return A human readable string representation.
+ * @hide
+ */
+ public static String capabilitiesToStringShort(int capabilities) {
+ return capabilitiesToStringInternal(capabilities, false /* isLong */);
+ }
+
+ private static String capabilitiesToStringInternal(int capabilities, boolean isLong) {
StringBuilder builder = new StringBuilder();
- builder.append("[Capabilities:");
+ builder.append("[");
+ if (isLong) {
+ builder.append("Capabilities:");
+ }
+
if (can(capabilities, CAPABILITY_HOLD)) {
- builder.append(" CAPABILITY_HOLD");
+ builder.append(isLong ? " CAPABILITY_HOLD" : " hld");
}
if (can(capabilities, CAPABILITY_SUPPORT_HOLD)) {
- builder.append(" CAPABILITY_SUPPORT_HOLD");
+ builder.append(isLong ? " CAPABILITY_SUPPORT_HOLD" : " sup_hld");
}
if (can(capabilities, CAPABILITY_MERGE_CONFERENCE)) {
- builder.append(" CAPABILITY_MERGE_CONFERENCE");
+ builder.append(isLong ? " CAPABILITY_MERGE_CONFERENCE" : " mrg_cnf");
}
if (can(capabilities, CAPABILITY_SWAP_CONFERENCE)) {
- builder.append(" CAPABILITY_SWAP_CONFERENCE");
+ builder.append(isLong ? " CAPABILITY_SWAP_CONFERENCE" : " swp_cnf");
}
if (can(capabilities, CAPABILITY_RESPOND_VIA_TEXT)) {
- builder.append(" CAPABILITY_RESPOND_VIA_TEXT");
+ builder.append(isLong ? " CAPABILITY_RESPOND_VIA_TEXT" : " txt");
}
if (can(capabilities, CAPABILITY_MUTE)) {
- builder.append(" CAPABILITY_MUTE");
+ builder.append(isLong ? " CAPABILITY_MUTE" : " mut");
}
if (can(capabilities, CAPABILITY_MANAGE_CONFERENCE)) {
- builder.append(" CAPABILITY_MANAGE_CONFERENCE");
+ builder.append(isLong ? " CAPABILITY_MANAGE_CONFERENCE" : " mng_cnf");
}
if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_RX)) {
- builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_RX");
+ builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_LOCAL_RX" : " VTlrx");
}
if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_TX)) {
- builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_TX");
+ builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_LOCAL_TX" : " VTltx");
}
if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
- builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL");
+ builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL" : " VTlbi");
}
if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_RX)) {
- builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_RX");
+ builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_REMOTE_RX" : " VTrrx");
}
if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_TX)) {
- builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_TX");
+ builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_REMOTE_TX" : " VTrtx");
}
if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
- builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL");
+ builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL" : " VTrbi");
}
if (can(capabilities, CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO)) {
- builder.append(" CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO");
+ builder.append(isLong ? " CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO" : " !v2a");
}
if (can(capabilities, CAPABILITY_SPEED_UP_MT_AUDIO)) {
- builder.append(" CAPABILITY_SPEED_UP_MT_AUDIO");
+ builder.append(isLong ? " CAPABILITY_SPEED_UP_MT_AUDIO" : " spd_aud");
}
if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
- builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO");
+ builder.append(isLong ? " CAPABILITY_CAN_UPGRADE_TO_VIDEO" : " a2v");
}
if (can(capabilities, CAPABILITY_CAN_PAUSE_VIDEO)) {
- builder.append(" CAPABILITY_CAN_PAUSE_VIDEO");
+ builder.append(isLong ? " CAPABILITY_CAN_PAUSE_VIDEO" : " paus_VT");
}
if (can(capabilities, CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
- builder.append(" CAPABILITY_SINGLE_PARTY_CONFERENCE");
+ builder.append(isLong ? " CAPABILITY_SINGLE_PARTY_CONFERENCE" : " 1p_cnf");
}
if (can(capabilities, CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
- builder.append(" CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION");
+ builder.append(isLong ? " CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION" : " rsp_by_con");
}
if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
- builder.append(" CAPABILITY_CAN_PULL_CALL");
+ builder.append(isLong ? " CAPABILITY_CAN_PULL_CALL" : " pull");
}
builder.append("]");
@@ -588,31 +608,49 @@
* @return A human readable string representation.
*/
public static String propertiesToString(int properties) {
+ return propertiesToStringInternal(properties, true /* isLong */);
+ }
+
+ /**
+ * Renders a set of property bits ({@code PROPERTY_*}) as a *short* human readable string.
+ *
+ * @param properties A property bit field.
+ * @return A human readable string representation.
+ * @hide
+ */
+ public static String propertiesToStringShort(int properties) {
+ return propertiesToStringInternal(properties, false /* isLong */);
+ }
+
+ private static String propertiesToStringInternal(int properties, boolean isLong) {
StringBuilder builder = new StringBuilder();
- builder.append("[Properties:");
+ builder.append("[");
+ if (isLong) {
+ builder.append("Properties:");
+ }
if (can(properties, PROPERTY_SHOW_CALLBACK_NUMBER)) {
- builder.append(" PROPERTY_SHOW_CALLBACK_NUMBER");
+ builder.append(isLong ? " PROPERTY_SHOW_CALLBACK_NUMBER" : " clbk");
}
if (can(properties, PROPERTY_HIGH_DEF_AUDIO)) {
- builder.append(" PROPERTY_HIGH_DEF_AUDIO");
+ builder.append(isLong ? " PROPERTY_HIGH_DEF_AUDIO" : " HD");
}
if (can(properties, PROPERTY_WIFI)) {
- builder.append(" PROPERTY_WIFI");
+ builder.append(isLong ? " PROPERTY_WIFI" : " wifi");
}
if (can(properties, PROPERTY_GENERIC_CONFERENCE)) {
- builder.append(" PROPERTY_GENERIC_CONFERENCE");
+ builder.append(isLong ? " PROPERTY_GENERIC_CONFERENCE" : " gen_conf");
}
if (can(properties, PROPERTY_IS_EXTERNAL_CALL)) {
- builder.append(" PROPERTY_IS_EXTERNAL_CALL");
+ builder.append(isLong ? " PROPERTY_IS_EXTERNAL_CALL" : " xtrnl");
}
if (can(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
- builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
+ builder.append(isLong ? " PROPERTY_HAS_CDMA_VOICE_PRIVACY" : " priv");
}
builder.append("]");
diff --git a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java b/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
index ca7354f..8b81b0d 100644
--- a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
@@ -17,18 +17,23 @@
package com.android.internal.telephony;
import android.annotation.Nullable;
+import android.content.ContentResolver;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.RemoteException;
+import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemConfig;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Utilities for handling carrier applications.
@@ -53,6 +58,11 @@
* in the default state (e.g. not explicitly DISABLED/DISABLED_BY_USER/ENABLED), or we enable if
* the app is carrier privileged and in either the default state or DISABLED_UNTIL_USED.
*
+ * In addition, there is a list of carrier-associated applications in
+ * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierAssociatedApps}. Each app in this
+ * list is associated with a carrier app. When the given carrier app is enabled/disabled per the
+ * above, the associated applications are enabled/disabled to match.
+ *
* When enabling a carrier app we also grant it default permissions.
*
* This method is idempotent and is safe to be called at any time; it should be called once at
@@ -60,19 +70,24 @@
* privileged apps may have changed.
*/
public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
- IPackageManager packageManager, TelephonyManager telephonyManager, int userId) {
+ IPackageManager packageManager, TelephonyManager telephonyManager,
+ ContentResolver contentResolver, int userId) {
if (DEBUG) {
Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
}
+ SystemConfig config = SystemConfig.getInstance();
String[] systemCarrierAppsDisabledUntilUsed = Resources.getSystem().getStringArray(
com.android.internal.R.array.config_disabledUntilUsedPreinstalledCarrierApps);
- disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager, userId,
- systemCarrierAppsDisabledUntilUsed);
+ ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
+ config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+ disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager,
+ contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
+ systemCarrierAssociatedAppsDisabledUntilUsed);
}
/**
* Like {@link #disableCarrierAppsUntilPrivileged(String, IPackageManager, TelephonyManager,
- * int)}, but assumes that no carrier apps have carrier privileges.
+ * ContentResolver, int)}, but assumes that no carrier apps have carrier privileges.
*
* This prevents a potential race condition on first boot - since the app's default state is
* enabled, we will initially disable it when the telephony stack is first initialized as it has
@@ -82,29 +97,43 @@
* Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
*/
public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
- IPackageManager packageManager, int userId) {
+ IPackageManager packageManager, ContentResolver contentResolver, int userId) {
if (DEBUG) {
Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
}
+ SystemConfig config = SystemConfig.getInstance();
String[] systemCarrierAppsDisabledUntilUsed = Resources.getSystem().getStringArray(
com.android.internal.R.array.config_disabledUntilUsedPreinstalledCarrierApps);
+ ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
+ config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
disableCarrierAppsUntilPrivileged(callingPackage, packageManager,
- null /* telephonyManager */, userId, systemCarrierAppsDisabledUntilUsed);
+ null /* telephonyManager */, contentResolver, userId,
+ systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
}
// Must be public b/c framework unit tests can't access package-private methods.
@VisibleForTesting
public static void disableCarrierAppsUntilPrivileged(String callingPackage,
- IPackageManager packageManager, @Nullable TelephonyManager telephonyManager, int userId,
- String[] systemCarrierAppsDisabledUntilUsed) {
+ IPackageManager packageManager, @Nullable TelephonyManager telephonyManager,
+ ContentResolver contentResolver, int userId,
+ String[] systemCarrierAppsDisabledUntilUsed,
+ ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager,
userId, systemCarrierAppsDisabledUntilUsed);
if (candidates == null || candidates.isEmpty()) {
return;
}
+ Map<String, List<ApplicationInfo>> associatedApps = getDefaultCarrierAssociatedAppsHelper(
+ packageManager,
+ userId,
+ systemCarrierAssociatedAppsDisabledUntilUsed);
+
List<String> enabledCarrierPackages = new ArrayList<>();
+ boolean hasRunOnce = Settings.Secure.getIntForUser(
+ contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+
try {
for (ApplicationInfo ai : candidates) {
String packageName = ai.packageName;
@@ -112,33 +141,92 @@
telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) ==
TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
- // Only update enabled state for the app on /system. Once it has been updated we
- // shouldn't touch it.
- if (!ai.isUpdatedSystemApp()) {
- if (hasPrivileges
- && (ai.enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ if (hasPrivileges) {
+ // Only update enabled state for the app on /system. Once it has been
+ // updated we shouldn't touch it.
+ if (!ai.isUpdatedSystemApp()
+ && (ai.enabledSetting ==
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| ai.enabledSetting ==
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
Slog.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+ userId);
- packageManager.setApplicationEnabledSetting(packageName,
+ packageManager.setApplicationEnabledSetting(
+ packageName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
- PackageManager.DONT_KILL_APP, userId, callingPackage);
- } else if (!hasPrivileges
+ PackageManager.DONT_KILL_APP,
+ userId,
+ callingPackage);
+ }
+
+ // Also enable any associated apps for this carrier app.
+ List<ApplicationInfo> associatedAppList = associatedApps.get(packageName);
+ if (associatedAppList != null) {
+ for (ApplicationInfo associatedApp : associatedAppList) {
+ if (associatedApp.enabledSetting ==
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || associatedApp.enabledSetting ==
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ Slog.i(TAG, "Update associated state(" + associatedApp.packageName
+ + "): ENABLED for user " + userId);
+ packageManager.setApplicationEnabledSetting(
+ associatedApp.packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP,
+ userId,
+ callingPackage);
+ }
+ }
+ }
+
+ // Always re-grant default permissions to carrier apps w/ privileges.
+ enabledCarrierPackages.add(ai.packageName);
+ } else { // No carrier privileges
+ // Only update enabled state for the app on /system. Once it has been
+ // updated we shouldn't touch it.
+ if (!ai.isUpdatedSystemApp()
&& ai.enabledSetting ==
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
Slog.i(TAG, "Update state(" + packageName
+ "): DISABLED_UNTIL_USED for user " + userId);
- packageManager.setApplicationEnabledSetting(packageName,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0,
- userId, callingPackage);
+ packageManager.setApplicationEnabledSetting(
+ packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ 0,
+ userId,
+ callingPackage);
+ }
+
+ // Also disable any associated apps for this carrier app if this is the first
+ // run. We avoid doing this a second time because it is brittle to rely on the
+ // distinction between "default" and "enabled".
+ if (!hasRunOnce) {
+ List<ApplicationInfo> associatedAppList = associatedApps.get(packageName);
+ if (associatedAppList != null) {
+ for (ApplicationInfo associatedApp : associatedAppList) {
+ if (associatedApp.enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+ Slog.i(TAG,
+ "Update associated state(" + associatedApp.packageName
+ + "): DISABLED_UNTIL_USED for user " + userId);
+ packageManager.setApplicationEnabledSetting(
+ associatedApp.packageName,
+ PackageManager
+ .COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ 0,
+ userId,
+ callingPackage);
+ }
+ }
+ }
}
}
+ }
- // Always re-grant default permissions to carrier apps w/ privileges.
- if (hasPrivileges) {
- enabledCarrierPackages.add(ai.packageName);
- }
+ // Mark the execution so we do not disable apps again.
+ if (!hasRunOnce) {
+ Settings.Secure.putIntForUser(
+ contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
}
if (!enabledCarrierPackages.isEmpty()) {
@@ -190,8 +278,8 @@
*
* These are the apps subject to the hiding/showing logic in
* {@link CarrierAppUtils#disableCarrierAppsUntilPrivileged(String, IPackageManager,
- * TelephonyManager, int)}, as well as the apps which should have default permissions granted,
- * when a matching SIM is inserted.
+ * TelephonyManager, ContentResolver, int)}, as well as the apps which should have default
+ * permissions granted, when a matching SIM is inserted.
*
* Whether or not the app is actually considered a default app depends on whether the app has
* carrier privileges as determined by the SIMs in the device.
@@ -205,30 +293,68 @@
}
private static List<ApplicationInfo> getDefaultCarrierAppCandidatesHelper(
- IPackageManager packageManager, int userId,
+ IPackageManager packageManager,
+ int userId,
String[] systemCarrierAppsDisabledUntilUsed) {
if (systemCarrierAppsDisabledUntilUsed == null
|| systemCarrierAppsDisabledUntilUsed.length == 0) {
return null;
}
- List<ApplicationInfo> apps = null;
- try {
- apps = new ArrayList<>(systemCarrierAppsDisabledUntilUsed.length);
- for (String packageName : systemCarrierAppsDisabledUntilUsed) {
- ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
- PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
- if (ai == null) {
- // No app found for packageName
- continue;
- }
- if (!ai.isSystemApp()) {
- continue;
- }
+ List<ApplicationInfo> apps = new ArrayList<>(systemCarrierAppsDisabledUntilUsed.length);
+ for (int i = 0; i < systemCarrierAppsDisabledUntilUsed.length; i++) {
+ String packageName = systemCarrierAppsDisabledUntilUsed[i];
+ ApplicationInfo ai =
+ getApplicationInfoIfSystemApp(packageManager, userId, packageName);
+ if (ai != null) {
apps.add(ai);
}
+ }
+ return apps;
+ }
+
+ private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
+ IPackageManager packageManager,
+ int userId,
+ ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
+ int size = systemCarrierAssociatedAppsDisabledUntilUsed.size();
+ Map<String, List<ApplicationInfo>> associatedApps = new ArrayMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String carrierAppPackage = systemCarrierAssociatedAppsDisabledUntilUsed.keyAt(i);
+ List<String> associatedAppPackages =
+ systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
+ for (int j = 0; j < associatedAppPackages.size(); j++) {
+ ApplicationInfo ai =
+ getApplicationInfoIfSystemApp(
+ packageManager, userId, associatedAppPackages.get(j));
+ // Only update enabled state for the app on /system. Once it has been updated we
+ // shouldn't touch it.
+ if (ai != null && !ai.isUpdatedSystemApp()) {
+ List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
+ if (appList == null) {
+ appList = new ArrayList<>();
+ associatedApps.put(carrierAppPackage, appList);
+ }
+ appList.add(ai);
+ }
+ }
+ }
+ return associatedApps;
+ }
+
+ @Nullable
+ private static ApplicationInfo getApplicationInfoIfSystemApp(
+ IPackageManager packageManager,
+ int userId,
+ String packageName) {
+ try {
+ ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, userId);
+ if (ai != null && ai.isSystemApp()) {
+ return ai;
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Could not reach PackageManager", e);
}
- return apps;
+ return null;
}
}