diff options
Diffstat (limited to 'core/java')
166 files changed, 8511 insertions, 6399 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 7fcbe35..b5817df 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3726,6 +3726,95 @@ public class Activity extends ContextThemeWrapper } /** + * Requests permissions to be granted to this application. These permissions + * must be requested in your manifest, they should not be granted to your app, + * and they should have protection level {@link android.content.pm.PermissionInfo + * #PROTECTION_DANGEROUS dangerous}, regardless whether they are declared by + * the platform or a third-party app. + * <p> + * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL} + * are granted at install time if requested in the manifest. Signature permissions + * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at + * install time if requested in the manifest and the signature of your app matches + * the signature of the app declaring the permissions. + * </p> + * <p> + * If your app does not have the requested permissions the user will be presented + * with UI for accepting them. After the user has accepted or rejected the + * requested permissions you will receive a callback on {@link + * #onRequestPermissionsResult(int, String[], int[])} reporting whether the + * permissions were granted or not. + * </p> + * <p> + * Note that requesting a permission does not guarantee it will be granted and + * your app should be able to run without having this permission. + * </p> + * <p> + * This method may start an activity allowing the user to choose which permissions + * to grant and which to reject. Hence, you should be prepared that your activity + * may be paused and resumed. Further, granting some permissions may require + * a restart of you application. In such a case, the system will recreate the + * activity stack before delivering the result to {@link + * #onRequestPermissionsResult(int, String[], int[])}. + * </p> + * <p> + * When checking whether you have a permission you should use {@link + * #checkSelfPermission(String)}. + * </p> + * <p> + * A sample permissions request looks like this: + * </p> + * <code><pre><p> + * private void showContacts() { + * if (checkSelfPermission(Manifest.permission.READ_CONTACTS) + * != PackageManager.PERMISSION_GRANTED) { + * requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, + * PERMISSIONS_REQUEST_READ_CONTACTS); + * } else { + * doShowContacts(); + * } + * } + * + * {@literal @}Override + * public void onRequestPermissionsResult(int requestCode, String[] permissions, + * int[] grantResults) { + * if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS + * && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + * showContacts(); + * } + * } + * </code></pre></p> + * + * @param permissions The requested permissions. + * @param requestCode Application specific request code to match with a result + * reported to {@link #onRequestPermissionsResult(int, String[], int[])}. + * + * @see #onRequestPermissionsResult(int, String[], int[]) + * @see #checkSelfPermission(String) + */ + public final void requestPermissions(@NonNull String[] permissions, int requestCode) { + Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions); + startActivityForResult(intent, requestCode); + } + + /** + * Callback for the result from requesting permissions. This method + * is invoked for every call on {@link #requestPermissions(String[], int)}. + * + * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}. + * @param permissions The requested permissions. Never null. + * @param grantResults The grant results for the corresponding permissions + * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED} + * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null. + * + * @see #requestPermissions(String[], int) + */ + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + /* callback - no nothing */ + } + + /** * Same as calling {@link #startActivityForResult(Intent, int, Bundle)} * with no options. * @@ -6269,11 +6358,19 @@ public class Activity extends ContextThemeWrapper + ", resCode=" + resultCode + ", data=" + data); mFragments.noteStateNotSaved(); if (who == null) { - onActivityResult(requestCode, resultCode, data); + if (isRequestPermissionResult(data)) { + dispatchRequestPermissionsResult(requestCode, data); + } else { + onActivityResult(requestCode, resultCode, data); + } } else { Fragment frag = mFragments.findFragmentByWho(who); if (frag != null) { - frag.onActivityResult(requestCode, resultCode, data); + if (isRequestPermissionResult(data)) { + dispatchRequestPermissionsResultToFragment(requestCode, data, frag); + } else { + frag.onActivityResult(requestCode, resultCode, data); + } } } } @@ -6343,4 +6440,26 @@ public class Activity extends ContextThemeWrapper */ public void onTranslucentConversionComplete(boolean drawComplete); } + + private void dispatchRequestPermissionsResult(int requestCode, Intent data) { + String[] permissions = data.getStringArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES); + final int[] grantResults = data.getIntArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS); + onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + private void dispatchRequestPermissionsResultToFragment(int requestCode, Intent data, + Fragment fragement) { + String[] permissions = data.getStringArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES); + final int[] grantResults = data.getIntArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS); + fragement.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + private static boolean isRequestPermissionResult(Intent intent) { + return intent != null + && PackageManager.ACTION_REQUEST_PERMISSIONS.equals(intent.getAction()); + } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 29b024ac..d143f8b 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2493,7 +2493,8 @@ public class ActivityManager { public static int checkComponentPermission(String permission, int uid, int owningUid, boolean exported) { // Root, system server get to do everything. - if (uid == 0 || uid == Process.SYSTEM_UID) { + final int appId = UserHandle.getAppId(uid); + if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) { return PackageManager.PERMISSION_GRANTED; } // Isolated processes don't get any permissions. diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7b8ec74..4880db1 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -74,6 +74,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; +import android.security.NetworkSecurityPolicy; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -4480,6 +4481,9 @@ public final class ActivityThread { StrictMode.enableDeathOnNetwork(); } + NetworkSecurityPolicy.getInstance().setCleartextTrafficPermitted( + (data.appInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0); + if (data.debugMode != IApplicationThread.DEBUG_OFF) { // XXX should have option to change the port. Debug.changeDebugPort(8100); diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 2cb27b0..eafcdb2 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -350,10 +350,15 @@ public class ActivityView extends ViewGroup { if (activityView != null) { final ActivityViewCallback callback = activityView.mActivityViewCallback; if (callback != null) { + final WeakReference<ActivityViewCallback> callbackRef = + new WeakReference<>(callback); activityView.post(new Runnable() { @Override public void run() { - callback.onAllActivitiesComplete(activityView); + ActivityViewCallback callback = callbackRef.get(); + if (callback != null) { + callback.onAllActivitiesComplete(activityView); + } } }); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 9f81670..6d74905 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -81,7 +81,6 @@ import java.util.List; /*package*/ final class ApplicationPackageManager extends PackageManager { private static final String TAG = "ApplicationPackageManager"; - private final static boolean DEBUG = false; private final static boolean DEBUG_ICONS = false; // Default flags to use with PackageManager when no flags are given. @@ -186,8 +185,8 @@ final class ApplicationPackageManager extends PackageManager { public int[] getPackageGids(String packageName) throws NameNotFoundException { try { - int[] gids = mPM.getPackageGids(packageName); - if (gids == null || gids.length > 0) { + int[] gids = mPM.getPackageGids(packageName, mContext.getUserId()); + if (gids != null) { return gids; } } catch (RemoteException e) { @@ -398,7 +397,7 @@ final class ApplicationPackageManager extends PackageManager { @Override public int checkPermission(String permName, String pkgName) { try { - return mPM.checkPermission(permName, pkgName); + return mPM.checkPermission(permName, pkgName, mContext.getUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -432,18 +431,18 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public void grantPermission(String packageName, String permissionName) { + public void grantPermission(String packageName, String permissionName, UserHandle user) { try { - mPM.grantPermission(packageName, permissionName); + mPM.grantPermission(packageName, permissionName, user.getIdentifier()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } @Override - public void revokePermission(String packageName, String permissionName) { + public void revokePermission(String packageName, String permissionName, UserHandle user) { try { - mPM.revokePermission(packageName, permissionName); + mPM.revokePermission(packageName, permissionName, user.getIdentifier()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java index 25153fc..c435ccb 100644 --- a/core/java/android/app/AssistStructure.java +++ b/core/java/android/app/AssistStructure.java @@ -17,6 +17,7 @@ package android.app; import android.content.ComponentName; +import android.content.res.Resources; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; @@ -53,7 +54,7 @@ final public class AssistStructure implements Parcelable { final ComponentName mActivityComponent; - final ArrayList<ViewNodeImpl> mRootViews = new ArrayList<>(); + final ArrayList<WindowNode> mWindowNodes = new ArrayList<>(); ViewAssistStructureImpl mTmpViewAssistStructureImpl = new ViewAssistStructureImpl(); Bundle mTmpExtras = new Bundle(); @@ -178,7 +179,91 @@ final public class AssistStructure implements Parcelable { } } - final static class ViewNodeImpl { + /** + * Describes a window in the assist data. + */ + static public class WindowNode { + final int mX; + final int mY; + final int mWidth; + final int mHeight; + final CharSequence mTitle; + final ViewNode mRoot; + + WindowNode(AssistStructure assist, ViewRootImpl root) { + View view = root.getView(); + Rect rect = new Rect(); + view.getBoundsOnScreen(rect); + mX = rect.left - view.getLeft(); + mY = rect.top - view.getTop(); + mWidth = rect.width(); + mHeight = rect.height(); + mTitle = root.getTitle(); + mRoot = new ViewNode(assist, view); + } + + WindowNode(Parcel in, PooledStringReader preader) { + mX = in.readInt(); + mY = in.readInt(); + mWidth = in.readInt(); + mHeight = in.readInt(); + mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mRoot = new ViewNode(in, preader); + } + + void writeToParcel(Parcel out, PooledStringWriter pwriter) { + out.writeInt(mX); + out.writeInt(mY); + out.writeInt(mWidth); + out.writeInt(mHeight); + TextUtils.writeToParcel(mTitle, out, 0); + mRoot.writeToParcel(out, pwriter); + } + + public int getLeft() { + return mX; + } + + public int getTop() { + return mY; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } + + public CharSequence getTitle() { + return mTitle; + } + + public ViewNode getRootViewNode() { + return mRoot; + } + } + + /** + * Describes a single view in the assist data. + */ + static public class ViewNode { + /** + * Magic value for text color that has not been defined, which is very unlikely + * to be confused with a real text color. + */ + public static final int TEXT_COLOR_UNDEFINED = 1; + + public static final int TEXT_STYLE_BOLD = 1<<0; + public static final int TEXT_STYLE_ITALIC = 1<<1; + public static final int TEXT_STYLE_UNDERLINE = 1<<2; + public static final int TEXT_STYLE_STRIKE_THRU = 1<<3; + + final int mId; + final String mIdPackage; + final String mIdType; + final String mIdEntry; final int mX; final int mY; final int mScrollX; @@ -201,17 +286,34 @@ final public class AssistStructure implements Parcelable { final int mFlags; final String mClassName; - final String mContentDescription; + final CharSequence mContentDescription; final ViewNodeTextImpl mText; final Bundle mExtras; - final ViewNodeImpl[] mChildren; - - ViewNodeImpl(AssistStructure assistStructure, View view, int left, int top, - CharSequence contentDescription) { - mX = left; - mY = top; + final ViewNode[] mChildren; + + ViewNode(AssistStructure assistStructure, View view) { + mId = view.getId(); + if (mId > 0 && (mId&0xff000000) != 0 && (mId&0x00ff0000) != 0 + && (mId&0x0000ffff) != 0) { + String pkg, type, entry; + try { + Resources res = view.getResources(); + entry = res.getResourceEntryName(mId); + type = res.getResourceTypeName(mId); + pkg = res.getResourcePackageName(mId); + } catch (Resources.NotFoundException e) { + entry = type = pkg = null; + } + mIdPackage = pkg; + mIdType = type; + mIdEntry = entry; + } else { + mIdPackage = mIdType = mIdEntry = null; + } + mX = view.getLeft(); + mY = view.getTop(); mScrollX = view.getScrollX(); mScrollY = view.getScrollY(); mWidth = view.getWidth(); @@ -249,7 +351,7 @@ final public class AssistStructure implements Parcelable { } mFlags = flags; mClassName = view.getAccessibilityClassName().toString(); - mContentDescription = contentDescription != null ? contentDescription.toString() : null; + mContentDescription = view.getContentDescription(); final ViewAssistStructureImpl viewData = assistStructure.mTmpViewAssistStructureImpl; final Bundle extras = assistStructure.mTmpExtras; view.onProvideAssistStructure(viewData, extras); @@ -269,9 +371,9 @@ final public class AssistStructure implements Parcelable { ViewGroup vg = (ViewGroup)view; final int NCHILDREN = vg.getChildCount(); if (NCHILDREN > 0) { - mChildren = new ViewNodeImpl[NCHILDREN]; + mChildren = new ViewNode[NCHILDREN]; for (int i=0; i<NCHILDREN; i++) { - mChildren[i] = new ViewNodeImpl(assistStructure, vg.getChildAt(i)); + mChildren[i] = new ViewNode(assistStructure, vg.getChildAt(i)); } } else { mChildren = null; @@ -281,11 +383,19 @@ final public class AssistStructure implements Parcelable { } } - ViewNodeImpl(AssistStructure assistStructure, View view) { - this(assistStructure, view, view.getLeft(), view.getTop(), view.getContentDescription()); - } - - ViewNodeImpl(Parcel in, PooledStringReader preader) { + ViewNode(Parcel in, PooledStringReader preader) { + mId = in.readInt(); + if (mId != 0) { + mIdEntry = preader.readString(); + if (mIdEntry != null) { + mIdType = preader.readString(); + mIdPackage = preader.readString(); + } else { + mIdPackage = mIdType = null; + } + } else { + mIdPackage = mIdType = mIdEntry = null; + } mX = in.readInt(); mY = in.readInt(); mScrollX = in.readInt(); @@ -294,7 +404,7 @@ final public class AssistStructure implements Parcelable { mHeight = in.readInt(); mFlags = in.readInt(); mClassName = preader.readString(); - mContentDescription = in.readString(); + mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); if (in.readInt() != 0) { mText = new ViewNodeTextImpl(in); } else { @@ -303,9 +413,9 @@ final public class AssistStructure implements Parcelable { mExtras = in.readBundle(); final int NCHILDREN = in.readInt(); if (NCHILDREN > 0) { - mChildren = new ViewNodeImpl[NCHILDREN]; + mChildren = new ViewNode[NCHILDREN]; for (int i=0; i<NCHILDREN; i++) { - mChildren[i] = new ViewNodeImpl(in, preader); + mChildren[i] = new ViewNode(in, preader); } } else { mChildren = null; @@ -313,6 +423,14 @@ final public class AssistStructure implements Parcelable { } void writeToParcel(Parcel out, PooledStringWriter pwriter) { + out.writeInt(mId); + if (mId != 0) { + pwriter.writeString(mIdEntry); + if (mIdEntry != null) { + pwriter.writeString(mIdType); + pwriter.writeString(mIdPackage); + } + } out.writeInt(mX); out.writeInt(mY); out.writeInt(mScrollX); @@ -321,7 +439,7 @@ final public class AssistStructure implements Parcelable { out.writeInt(mHeight); out.writeInt(mFlags); pwriter.writeString(mClassName); - out.writeString(mContentDescription); + TextUtils.writeToParcel(mContentDescription, out, 0); if (mText != null) { out.writeInt(1); mText.writeToParcel(out); @@ -339,146 +457,141 @@ final public class AssistStructure implements Parcelable { out.writeInt(0); } } - } - /** - * Provides access to information about a single view in the assist data. - */ - static public class ViewNode { - /** - * Magic value for text color that has not been defined, which is very unlikely - * to be confused with a real text color. - */ - public static final int TEXT_COLOR_UNDEFINED = 1; + public int getId() { + return mId; + } - public static final int TEXT_STYLE_BOLD = 1<<0; - public static final int TEXT_STYLE_ITALIC = 1<<1; - public static final int TEXT_STYLE_UNDERLINE = 1<<2; - public static final int TEXT_STYLE_STRIKE_THRU = 1<<3; + public String getIdPackage() { + return mIdPackage; + } - ViewNodeImpl mImpl; + public String getIdType() { + return mIdType; + } - public ViewNode() { + public String getIdEntry() { + return mIdEntry; } public int getLeft() { - return mImpl.mX; + return mX; } public int getTop() { - return mImpl.mY; + return mY; } public int getScrollX() { - return mImpl.mScrollX; + return mScrollX; } public int getScrollY() { - return mImpl.mScrollY; + return mScrollY; } public int getWidth() { - return mImpl.mWidth; + return mWidth; } public int getHeight() { - return mImpl.mHeight; + return mHeight; } public int getVisibility() { - return mImpl.mFlags&ViewNodeImpl.FLAGS_VISIBILITY_MASK; + return mFlags&ViewNode.FLAGS_VISIBILITY_MASK; } public boolean isEnabled() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_DISABLED) == 0; + return (mFlags&ViewNode.FLAGS_DISABLED) == 0; } public boolean isClickable() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_CLICKABLE) != 0; + return (mFlags&ViewNode.FLAGS_CLICKABLE) != 0; } public boolean isFocusable() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_FOCUSABLE) != 0; + return (mFlags&ViewNode.FLAGS_FOCUSABLE) != 0; } public boolean isFocused() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_FOCUSED) != 0; + return (mFlags&ViewNode.FLAGS_FOCUSED) != 0; } public boolean isAccessibilityFocused() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_ACCESSIBILITY_FOCUSED) != 0; + return (mFlags&ViewNode.FLAGS_ACCESSIBILITY_FOCUSED) != 0; } public boolean isCheckable() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_CHECKABLE) != 0; + return (mFlags&ViewNode.FLAGS_CHECKABLE) != 0; } public boolean isChecked() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_CHECKED) != 0; + return (mFlags&ViewNode.FLAGS_CHECKED) != 0; } public boolean isSelected() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_SELECTED) != 0; + return (mFlags&ViewNode.FLAGS_SELECTED) != 0; } public boolean isActivated() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_ACTIVATED) != 0; + return (mFlags&ViewNode.FLAGS_ACTIVATED) != 0; } public boolean isLongClickable() { - return (mImpl.mFlags&ViewNodeImpl.FLAGS_LONG_CLICKABLE) != 0; + return (mFlags&ViewNode.FLAGS_LONG_CLICKABLE) != 0; } public String getClassName() { - return mImpl.mClassName; + return mClassName; } - public String getContentDescription() { - return mImpl.mContentDescription; + public CharSequence getContentDescription() { + return mContentDescription; } public CharSequence getText() { - return mImpl.mText != null ? mImpl.mText.mText : null; + return mText != null ? mText.mText : null; } public int getTextSelectionStart() { - return mImpl.mText != null ? mImpl.mText.mTextSelectionStart : -1; + return mText != null ? mText.mTextSelectionStart : -1; } public int getTextSelectionEnd() { - return mImpl.mText != null ? mImpl.mText.mTextSelectionEnd : -1; + return mText != null ? mText.mTextSelectionEnd : -1; } public int getTextColor() { - return mImpl.mText != null ? mImpl.mText.mTextColor : TEXT_COLOR_UNDEFINED; + return mText != null ? mText.mTextColor : TEXT_COLOR_UNDEFINED; } public int getTextBackgroundColor() { - return mImpl.mText != null ? mImpl.mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED; + return mText != null ? mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED; } public float getTextSize() { - return mImpl.mText != null ? mImpl.mText.mTextSize : 0; + return mText != null ? mText.mTextSize : 0; } public int getTextStyle() { - return mImpl.mText != null ? mImpl.mText.mTextStyle : 0; + return mText != null ? mText.mTextStyle : 0; } public String getHint() { - return mImpl.mText != null ? mImpl.mText.mHint : null; + return mText != null ? mText.mHint : null; } public Bundle getExtras() { - return mImpl.mExtras; + return mExtras; } public int getChildCount() { - return mImpl.mChildren != null ? mImpl.mChildren.length : 0; + return mChildren != null ? mChildren.length : 0; } - public void getChildAt(int index, ViewNode outNode) { - outNode.mImpl = mImpl.mChildren[index]; + public ViewNode getChildAt(int index) { + return mChildren[index]; } } @@ -488,12 +601,7 @@ final public class AssistStructure implements Parcelable { activity.getActivityToken()); for (int i=0; i<views.size(); i++) { ViewRootImpl root = views.get(i); - View view = root.getView(); - Rect rect = new Rect(); - view.getBoundsOnScreen(rect); - CharSequence title = root.getTitle(); - mRootViews.add(new ViewNodeImpl(this, view, rect.left, rect.top, - title != null ? title : view.getContentDescription())); + mWindowNodes.add(new WindowNode(this, root)); } } @@ -502,7 +610,7 @@ final public class AssistStructure implements Parcelable { mActivityComponent = ComponentName.readFromParcel(in); final int N = in.readInt(); for (int i=0; i<N; i++) { - mRootViews.add(new ViewNodeImpl(in, preader)); + mWindowNodes.add(new WindowNode(in, preader)); } //dump(); } @@ -510,24 +618,37 @@ final public class AssistStructure implements Parcelable { /** @hide */ public void dump() { Log.i(TAG, "Activity: " + mActivityComponent.flattenToShortString()); - ViewNode node = new ViewNode(); - final int N = getWindowCount(); + final int N = getWindowNodeCount(); for (int i=0; i<N; i++) { - Log.i(TAG, "Window #" + i + ":"); - getWindowAt(i, node); - dump(" ", node); + WindowNode node = getWindowNodeAt(i); + Log.i(TAG, "Window #" + i + " [" + node.getLeft() + "," + node.getTop() + + " " + node.getWidth() + "x" + node.getHeight() + "]" + " " + node.getTitle()); + dump(" ", node.getRootViewNode()); } } void dump(String prefix, ViewNode node) { Log.i(TAG, prefix + "View [" + node.getLeft() + "," + node.getTop() + " " + node.getWidth() + "x" + node.getHeight() + "]" + " " + node.getClassName()); + int id = node.getId(); + if (id != 0) { + StringBuilder sb = new StringBuilder(); + sb.append(prefix); sb.append(" ID: #"); sb.append(Integer.toHexString(id)); + String entry = node.getIdEntry(); + if (entry != null) { + String type = node.getIdType(); + String pkg = node.getIdPackage(); + sb.append(" "); sb.append(pkg); sb.append(":"); sb.append(type); + sb.append("/"); sb.append(entry); + } + Log.i(TAG, sb.toString()); + } int scrollX = node.getScrollX(); int scrollY = node.getScrollY(); if (scrollX != 0 || scrollY != 0) { Log.i(TAG, prefix + " Scroll: " + scrollX + "," + scrollY); } - String contentDescription = node.getContentDescription(); + CharSequence contentDescription = node.getContentDescription(); if (contentDescription != null) { Log.i(TAG, prefix + " Content description: " + contentDescription); } @@ -552,9 +673,8 @@ final public class AssistStructure implements Parcelable { if (NCHILDREN > 0) { Log.i(TAG, prefix + " Children:"); String cprefix = prefix + " "; - ViewNode cnode = new ViewNode(); for (int i=0; i<NCHILDREN; i++) { - node.getChildAt(i, cnode); + ViewNode cnode = node.getChildAt(i); dump(cprefix, cnode); } } @@ -575,17 +695,16 @@ final public class AssistStructure implements Parcelable { /** * Return the number of window contents that have been collected in this assist data. */ - public int getWindowCount() { - return mRootViews.size(); + public int getWindowNodeCount() { + return mWindowNodes.size(); } /** - * Return the root view for one of the windows in the assist data. - * @param index Which window to retrieve, may be 0 to {@link #getWindowCount()}-1. - * @param outNode Node in which to place the window's root view. + * Return one of the windows in the assist data. + * @param index Which window to retrieve, may be 0 to {@link #getWindowNodeCount()}-1. */ - public void getWindowAt(int index, ViewNode outNode) { - outNode.mImpl = mRootViews.get(index); + public WindowNode getWindowNodeAt(int index) { + return mWindowNodes.get(index); } public int describeContents() { @@ -596,10 +715,10 @@ final public class AssistStructure implements Parcelable { int start = out.dataPosition(); PooledStringWriter pwriter = new PooledStringWriter(out); ComponentName.writeToParcel(mActivityComponent, out); - final int N = mRootViews.size(); + final int N = mWindowNodes.size(); out.writeInt(N); for (int i=0; i<N; i++) { - mRootViews.get(i).writeToParcel(out, pwriter); + mWindowNodes.get(i).writeToParcel(out, pwriter); } pwriter.finish(); Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes"); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index eb27830..4ccd69f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1323,6 +1323,15 @@ class ContextImpl extends Context { Binder.getCallingUid()); } + @Override + public int checkSelfPermission(String permission) { + if (permission == null) { + throw new IllegalArgumentException("permission is null"); + } + + return checkPermission(permission, Process.myPid(), Process.myUid()); + } + private void enforce( String permission, int resultOfCheck, boolean selfToo, int uid, String message) { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index bdcc312..4fdae7f 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -17,6 +17,7 @@ package android.app; import android.animation.Animator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.content.ComponentCallbacks2; @@ -1092,13 +1093,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (mActivity == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - if (options != null) { - mActivity.startActivityFromFragment(this, intent, requestCode, options); - } else { - // Note we want to go through this call for compatibility with - // applications that may have overridden the method. - mActivity.startActivityFromFragment(this, intent, requestCode, options); - } + mActivity.startActivityFromFragment(this, intent, requestCode, options); } /** @@ -1119,6 +1114,98 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } /** + * Requests permissions to be granted to this application. These permissions + * must be requested in your manifest, they should not be granted to your app, + * and they should have protection level {@link android.content.pm.PermissionInfo + * #PROTECTION_DANGEROUS dangerous}, regardless whether they are declared by + * the platform or a third-party app. + * <p> + * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL} + * are granted at install time if requested in the manifest. Signature permissions + * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at + * install time if requested in the manifest and the signature of your app matches + * the signature of the app declaring the permissions. + * </p> + * <p> + * If your app does not have the requested permissions the user will be presented + * with UI for accepting them. After the user has accepted or rejected the + * requested permissions you will receive a callback on {@link + * #onRequestPermissionsResult(int, String[], int[])} reporting whether the + * permissions were granted or not. + * </p> + * <p> + * Note that requesting a permission does not guarantee it will be granted and + * your app should be able to run without having this permission. + * </p> + * <p> + * This method may start an activity allowing the user to choose which permissions + * to grant and which to reject. Hence, you should be prepared that your activity + * may be paused and resumed. Further, granting some permissions may require + * a restart of you application. In such a case, the system will recreate the + * activity stack before delivering the result to {@link + * #onRequestPermissionsResult(int, String[], int[])}. + * </p> + * <p> + * When checking whether you have a permission you should use {@link + * android.content.Context#checkSelfPermission(String)}. + * </p> + * <p> + * A sample permissions request looks like this: + * </p> + * <code><pre><p> + * private void showContacts() { + * if (getActivity().checkSelfPermission(Manifest.permission.READ_CONTACTS) + * != PackageManager.PERMISSION_GRANTED) { + * requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, + * PERMISSIONS_REQUEST_READ_CONTACTS); + * } else { + * doShowContacts(); + * } + * } + * + * {@literal @}Override + * public void onRequestPermissionsResult(int requestCode, String[] permissions, + * int[] grantResults) { + * if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS + * && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + * doShowContacts(); + * } + * } + * </code></pre></p> + * + * @param permissions The requested permissions. + * @param requestCode Application specific request code to match with a result + * reported to {@link #onRequestPermissionsResult(int, String[], int[])}. + * + * @see #onRequestPermissionsResult(int, String[], int[]) + * @see android.content.Context#checkSelfPermission(String) + */ + public final void requestPermissions(@NonNull String[] permissions, int requestCode) { + if (mActivity == null) { + throw new IllegalStateException("Fragment " + this + " not attached to Activity"); + } + Intent intent = mActivity.getPackageManager().buildRequestPermissionsIntent(permissions); + mActivity.startActivityFromFragment(this, intent, requestCode, null); + } + + /** + * Callback for the result from requesting permissions. This method + * is invoked for every call on {@link #requestPermissions(String[], int)}. + * + * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}. + * @param permissions The requested permissions. Never null. + * @param grantResults The grant results for the corresponding permissions + * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED} + * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null. + * + * @see #requestPermissions(String[], int) + */ + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + /* callback - do nothing */ + } + + /** * @hide Hack so that DialogFragment can make its Dialog before creating * its views, and the view construction can use the dialog's context for * inflation. Maybe this should become a public API. Note sure. diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 5d864df..33262b3 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -48,6 +48,9 @@ interface INotificationManager void setPackagePriority(String pkg, int uid, int priority); int getPackagePriority(String pkg, int uid); + void setPackagePeekable(String pkg, int uid, boolean peekable); + boolean getPackagePeekable(String pkg, int uid); + void setPackageVisibilityOverride(String pkg, int uid, int visibility); int getPackageVisibilityOverride(String pkg, int uid); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 85a6aff..b31ce04 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1840,6 +1840,26 @@ public class Notification implements Parcelable } /** + * {@hide} + */ + public static String priorityToString(@Priority int pri) { + switch (pri) { + case PRIORITY_MIN: + return "MIN"; + case PRIORITY_LOW: + return "LOW"; + case PRIORITY_DEFAULT: + return "DEFAULT"; + case PRIORITY_HIGH: + return "HIGH"; + case PRIORITY_MAX: + return "MAX"; + default: + return "UNKNOWN(" + String.valueOf(pri) + ")"; + } + } + + /** * @hide */ public boolean isValid() { @@ -2870,7 +2890,7 @@ public class Notification implements Parcelable contentView.setProgressBar( R.id.progress, mProgressMax, mProgress, mProgressIndeterminate); contentView.setProgressBackgroundTintList( - R.id.progress, ColorStateList.valueOf(mContext.getResources().getColor( + R.id.progress, ColorStateList.valueOf(mContext.getColor( R.color.notification_progress_background_color))); if (mColor != COLOR_DEFAULT) { ColorStateList colorStateList = ColorStateList.valueOf(mColor); @@ -3044,7 +3064,7 @@ public class Notification implements Parcelable private void processLegacyAction(Action action, RemoteViews button) { if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.icon)) { button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0, - mContext.getResources().getColor(R.color.notification_action_color_filter), + mContext.getColor(R.color.notification_action_color_filter), PorterDuff.Mode.MULTIPLY); } } @@ -3142,7 +3162,7 @@ public class Notification implements Parcelable private int resolveColor() { if (mColor == COLOR_DEFAULT) { - return mContext.getResources().getColor(R.color.notification_icon_bg_color); + return mContext.getColor(R.color.notification_icon_bg_color); } return mColor; } @@ -4321,9 +4341,9 @@ public class Notification implements Parcelable * Applies the special text colors for media notifications to all text views. */ private void styleText(RemoteViews contentView) { - int primaryColor = mBuilder.mContext.getResources().getColor( + int primaryColor = mBuilder.mContext.getColor( R.color.notification_media_primary_color); - int secondaryColor = mBuilder.mContext.getResources().getColor( + int secondaryColor = mBuilder.mContext.getColor( R.color.notification_media_secondary_color); contentView.setTextColor(R.id.title, primaryColor); if (mBuilder.showsTimeOrChronometer()) { @@ -5468,6 +5488,470 @@ public class Notification implements Parcelable } /** + * <p> + * Helper class to add content info extensions to notifications. To create a notification with + * content info extensions: + * <ol> + * <li>Create an {@link Notification.Builder}, setting any desired properties. + * <li>Create a {@link ContentInfoExtender}. + * <li>Set content info specific properties using the {@code add} and {@code set} methods of + * {@link ContentInfoExtender}. + * <li>Call {@link Notification.Builder#extend(Notification.Extender)} to apply the extensions + * to a notification. + * </ol> + * + * <pre class="prettyprint">Notification notification = new Notification.Builder(context) * ... * .extend(new ContentInfoExtender() * .set*(...)) * .build(); * </pre> + * <p> + * Content info extensions can be accessed on an existing notification by using the + * {@code ContentInfoExtender(Notification)} constructor, and then using the {@code get} methods + * to access values. + */ + public static final class ContentInfoExtender implements Extender { + private static final String TAG = "ContentInfoExtender"; + + // Key for the Content info extensions bundle in the main Notification extras bundle + private static final String EXTRA_CONTENT_INFO_EXTENDER = "android.CONTENT_INFO_EXTENSIONS"; + + // Keys within EXTRA_CONTENT_INFO_EXTENDER for individual content info options. + + private static final String KEY_CONTENT_TYPE = "android.contentType"; + + private static final String KEY_CONTENT_GENRES = "android.contentGenre"; + + private static final String KEY_CONTENT_PRICING_TYPE = "android.contentPricing.type"; + + private static final String KEY_CONTENT_PRICING_VALUE = "android.contentPricing.value"; + + private static final String KEY_CONTENT_STATUS = "android.contentStatus"; + + private static final String KEY_CONTENT_MATURITY_RATING = "android.contentMaturity"; + + private static final String KEY_CONTENT_RUN_LENGTH = "android.contentLength"; + + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a video clip. + */ + public static final String CONTENT_TYPE_VIDEO = "android.contentType.video"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a movie. + */ + public static final String CONTENT_TYPE_MOVIE = "android.contentType.movie"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a trailer. + */ + public static final String CONTENT_TYPE_TRAILER = "android.contentType.trailer"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is serial. It can refer to an entire show, a single season or + * series, or a single episode. + */ + public static final String CONTENT_TYPE_SERIAL = "android.contentType.serial"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a song or album. + */ + public static final String CONTENT_TYPE_MUSIC = "android.contentType.music"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a radio station. + */ + public static final String CONTENT_TYPE_RADIO = "android.contentType.radio"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a podcast. + */ + public static final String CONTENT_TYPE_PODCAST = "android.contentType.podcast"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a news item. + */ + public static final String CONTENT_TYPE_NEWS = "android.contentType.news"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is sports. + */ + public static final String CONTENT_TYPE_SPORTS = "android.contentType.sports"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is an application. + */ + public static final String CONTENT_TYPE_APP = "android.contentType.app"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a game. + */ + public static final String CONTENT_TYPE_GAME = "android.contentType.game"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a book. + */ + public static final String CONTENT_TYPE_BOOK = "android.contentType.book"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a comic book. + */ + public static final String CONTENT_TYPE_COMIC = "android.contentType.comic"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a magazine. + */ + public static final String CONTENT_TYPE_MAGAZINE = "android.contentType.magazine"; + + /** + * Value to be used with {@link #setContentTypes} to indicate that the content referred by + * the notification item is a website. + */ + public static final String CONTENT_TYPE_WEBSITE = "android.contentType.website"; + + + /** + * Value to be used with {@link #setPricingInformation} to indicate that the content + * referred by the notification item is free to consume. + */ + public static final String CONTENT_PRICING_FREE = "android.contentPrice.free"; + + /** + * Value to be used with {@link #setPricingInformation} to indicate that the content + * referred by the notification item is available as a rental, and the price value provided + * is the rental price for the item. + */ + public static final String CONTENT_PRICING_RENTAL = "android.contentPrice.rental"; + + /** + * Value to be used with {@link #setPricingInformation} to indicate that the content + * referred by the notification item is available for purchase, and the price value provided + * is the purchase price for the item. + */ + public static final String CONTENT_PRICING_PURCHASE = "android.contentPrice.purchase"; + + /** + * Value to be used with {@link #setPricingInformation} to indicate that the content + * referred by the notification item is available as part of a subscription based service, + * and the price value provided is the subscription price for the service. + */ + public static final String CONTENT_PRICING_SUBSCRIPTION = + "android.contentPrice.subscription"; + + /** + * Value to be used with {@link #setStatus} to indicate that the content referred by the + * notification is available and ready to be consumed immediately. + */ + public static final int CONTENT_STATUS_READY = 0; + + /** + * Value to be used with {@link #setStatus} to indicate that the content referred by the + * notification is pending, waiting on either a download or purchase operation to complete + * before it can be consumed. + */ + public static final int CONTENT_STATUS_PENDING = 1; + + /** + * Value to be used with {@link #setStatus} to indicate that the content referred by the + * notification is available, but needs to be first purchased, rented, subscribed or + * downloaded before it can be consumed. + */ + public static final int CONTENT_STATUS_AVAILABLE = 2; + + /** + * Value to be used with {@link #setStatus} to indicate that the content referred by the + * notification is not available. This could be content not available in a certain region or + * incompatible with the device in use. + */ + public static final int CONTENT_STATUS_UNAVAILABLE = 3; + + /** + * Value to be used with {@link #setMaturityRating} to indicate that the content referred by + * the notification is suitable for all audiences. + */ + public static final String CONTENT_MATURITY_ALL = "android.contentMaturity.all"; + + /** + * Value to be used with {@link #setMaturityRating} to indicate that the content + * referred by the notification is suitable for audiences of low maturity and above. + */ + public static final String CONTENT_MATURITY_LOW = "android.contentMaturity.low"; + + /** + * Value to be used with {@link #setMaturityRating} to indicate that the content + * referred by the notification is suitable for audiences of medium maturity and above. + */ + public static final String CONTENT_MATURITY_MEDIUM = "android.contentMaturity.medium"; + + /** + * Value to be used with {@link #setMaturityRating} to indicate that the content + * referred by the notification is suitable for audiences of high maturity and above. + */ + public static final String CONTENT_MATURITY_HIGH = "android.contentMaturity.high"; + + private String[] mTypes; + private String[] mGenres; + private String mPricingType; + private String mPricingValue; + private int mContentStatus = -1; + private String mMaturityRating; + private long mRunLength = -1; + + /** + * Create a {@link ContentInfoExtender} with default options. + */ + public ContentInfoExtender() { + } + + /** + * Create a {@link ContentInfoExtender} from the ContentInfoExtender options of an existing + * Notification. + * + * @param notif The notification from which to copy options. + */ + public ContentInfoExtender(Notification notif) { + Bundle contentBundle = notif.extras == null ? + null : notif.extras.getBundle(EXTRA_CONTENT_INFO_EXTENDER); + if (contentBundle != null) { + mTypes = contentBundle.getStringArray(KEY_CONTENT_TYPE); + mGenres = contentBundle.getStringArray(KEY_CONTENT_GENRES); + mPricingType = contentBundle.getString(KEY_CONTENT_PRICING_TYPE); + mPricingValue = contentBundle.getString(KEY_CONTENT_PRICING_VALUE); + mContentStatus = contentBundle.getInt(KEY_CONTENT_STATUS, -1); + mMaturityRating = contentBundle.getString(KEY_CONTENT_MATURITY_RATING); + mRunLength = contentBundle.getLong(KEY_CONTENT_RUN_LENGTH, -1); + } + } + + /** + * Apply content extensions to a notification that is being built. This is typically called + * by the {@link Notification.Builder#extend(Notification.Extender)} method of + * {@link Notification.Builder}. + */ + @Override + public Notification.Builder extend(Notification.Builder builder) { + Bundle contentBundle = new Bundle(); + + if (mTypes != null) { + contentBundle.putStringArray(KEY_CONTENT_TYPE, mTypes); + } + if (mGenres != null) { + contentBundle.putStringArray(KEY_CONTENT_GENRES, mGenres); + } + if (mPricingType != null) { + contentBundle.putString(KEY_CONTENT_PRICING_TYPE, mPricingType); + } + if (mPricingValue != null) { + contentBundle.putString(KEY_CONTENT_PRICING_VALUE, mPricingValue); + } + if (mContentStatus != -1) { + contentBundle.putInt(KEY_CONTENT_STATUS, mContentStatus); + } + if (mMaturityRating != null) { + contentBundle.putString(KEY_CONTENT_MATURITY_RATING, mMaturityRating); + } + if (mRunLength > 0) { + contentBundle.putLong(KEY_CONTENT_RUN_LENGTH, mRunLength); + } + + builder.getExtras().putBundle(EXTRA_CONTENT_INFO_EXTENDER, contentBundle); + return builder; + } + + /** + * Sets the content types associated with the notification content. The first tag entry will + * be considered the primary type for the content and will be used for ranking purposes. + * Other secondary type tags may be provided, if applicable, and may be used for filtering + * purposes. + * + * @param types Array of predefined type tags (see the <code>CONTENT_TYPE_*</code> + * constants) that describe the content referred to by a notification. + */ + public ContentInfoExtender setContentTypes(String[] types) { + mTypes = types; + return this; + } + + /** + * Returns an array containing the content types that describe the content associated with + * the notification. The first tag entry is considered the primary type for the content, and + * is used for content ranking purposes. + * + * @return An array of predefined type tags (see the <code>CONTENT_TYPE_*</code> constants) + * that describe the content associated with the notification. + * @see ContentInfoExtender#setContentTypes + */ + public String[] getContentTypes() { + return mTypes; + } + + /** + * Returns the primary content type tag for the content associated with the notification. + * + * @return A predefined type tag (see the <code>CONTENT_TYPE_*</code> constants) indicating + * the primary type for the content associated with the notification. + * @see ContentInfoExtender#setContentTypes + */ + public String getPrimaryContentType() { + if (mTypes == null || mTypes.length == 0) { + return null; + } + return mTypes[0]; + } + + /** + * Sets the content genres associated with the notification content. These genres may be + * used for content ranking. Genres are open ended String tags. + * <p> + * Some examples: "comedy", "action", "dance", "electronica", "racing", etc. + * + * @param genres Array of genre string tags that describe the content referred to by a + * notification. + */ + public ContentInfoExtender setGenres(String[] genres) { + mGenres = genres; + return this; + } + + /** + * Returns an array containing the content genres that describe the content associated with + * the notification. + * + * @return An array of genre tags that describe the content associated with the + * notification. + * @see ContentInfoExtender#setGenres + */ + public String[] getGenres() { + return mGenres; + } + + /** + * Sets the pricing and availability information for the content associated with the + * notification. The provided information will indicate the access model for the content + * (free, rental, purchase or subscription) and the price value (if not free). + * + * @param priceType Pricing type for this content. Must be one of the predefined pricing + * type tags (see the <code>CONTENT_PRICING_*</code> constants). + * @param priceValue A string containing a representation of the content price in the + * current locale and currency. + * @return This object for method chaining. + */ + public ContentInfoExtender setPricingInformation(String priceType, String priceValue) { + mPricingType = priceType; + mPricingValue = priceValue; + return this; + } + + /** + * Gets the pricing type for the content associated with the notification. + * + * @return A predefined tag indicating the pricing type for the content (see the <code> + * CONTENT_PRICING_*</code> constants). + * @see ContentInfoExtender#setPricingInformation + */ + public String getPricingType() { + return mPricingType; + } + + /** + * Gets the price value (when applicable) for the content associated with a notification. + * The value will be provided as a String containing the price in the appropriate currency + * for the current locale. + * + * @return A string containing a representation of the content price in the current locale + * and currency. + * @see ContentInfoExtender#setPricingInformation + */ + public String getPricingValue() { + if (mPricingType == null || CONTENT_PRICING_FREE.equals(mPricingType)) { + return null; + } + return mPricingValue; + } + + /** + * Sets the availability status for the content associated with the notification. This + * status indicates whether the referred content is ready to be consumed on the device, or + * if the user must first purchase, rent, subscribe to, or download the content. + * + * @param contentStatus The status value for this content. Must be one of the predefined + * content status values (see the <code>CONTENT_STATUS_*</code> constants). + */ + public ContentInfoExtender setStatus(int contentStatus) { + mContentStatus = contentStatus; + return this; + } + + /** + * Returns status value for the content associated with the notification. This status + * indicates whether the referred content is ready to be consumed on the device, or if the + * user must first purchase, rent, subscribe to, or download the content. + * + * @return The status value for this content, or -1 is a valid status has not been specified + * (see the <code>CONTENT_STATUS_*</code> for the defined valid status values). + * @see ContentInfoExtender#setStatus + */ + public int getStatus() { + return mContentStatus; + } + + /** + * Sets the maturity level rating for the content associated with the notification. + * + * @param maturityRating A tag indicating the maturity level rating for the content. This + * tag must be one of the predefined maturity rating tags (see the <code> + * CONTENT_MATURITY_*</code> constants). + */ + public ContentInfoExtender setMaturityRating(String maturityRating) { + mMaturityRating = maturityRating; + return this; + } + + /** + * Returns the maturity level rating for the content associated with the notification. + * + * @return returns a predefined tag indicating the maturity level rating for the content + * (see the <code> CONTENT_MATURITY_*</code> constants). + * @see ContentInfoExtender#setMaturityRating + */ + public String getMaturityRating() { + return mMaturityRating; + } + + /** + * Sets the running time (when applicable) for the content associated with the notification. + * + * @param length The runing time, in seconds, of the content associated with the + * notification. + */ + public ContentInfoExtender setRunningTime(long length) { + mRunLength = length; + return this; + } + + /** + * Returns the running time for the content associated with the notification. + * + * @return The running time, in seconds, of the content associated with the notification. + * @see ContentInfoExtender#setRunningTime + */ + public long getRunningTime() { + return mRunLength; + } + } + + /** * Get an array of Notification objects from a parcelable array bundle field. * Update the bundle to have a typed array so fetches in the future don't need * to do an array copy. diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index fd7bae7..59fe490 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -27,6 +27,7 @@ import android.app.job.IJobScheduler; import android.app.job.JobScheduler; import android.app.trust.TrustManager; import android.app.usage.IUsageStatsManager; +import android.app.usage.NetworkStatsManager; import android.app.usage.UsageStatsManager; import android.appwidget.AppWidgetManager; import android.bluetooth.BluetoothManager; @@ -639,6 +640,13 @@ final class SystemServiceRegistry { return new UsageStatsManager(ctx.getOuterContext(), service); }}); + registerService(Context.NETWORK_STATS_SERVICE, NetworkStatsManager.class, + new CachedServiceFetcher<NetworkStatsManager>() { + @Override + public NetworkStatsManager createService(ContextImpl ctx) { + return new NetworkStatsManager(ctx.getOuterContext()); + }}); + registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class, new StaticServiceFetcher<JobScheduler>() { @Override diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 81bcb39..9ba6a8e 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -227,7 +227,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override - public void executeShellCommand(String command, ParcelFileDescriptor sink) + public void executeShellCommand(final String command, final ParcelFileDescriptor sink) throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); @@ -235,30 +235,35 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { throwIfNotConnectedLocked(); } - InputStream in = null; - OutputStream out = null; - - try { - java.lang.Process process = Runtime.getRuntime().exec(command); - - in = process.getInputStream(); - out = new FileOutputStream(sink.getFileDescriptor()); - - final byte[] buffer = new byte[8192]; - while (true) { - final int readByteCount = in.read(buffer); - if (readByteCount < 0) { - break; + Thread streamReader = new Thread() { + public void run() { + InputStream in = null; + OutputStream out = null; + + try { + java.lang.Process process = Runtime.getRuntime().exec(command); + + in = process.getInputStream(); + out = new FileOutputStream(sink.getFileDescriptor()); + + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + break; + } + out.write(buffer, 0, readByteCount); + } + } catch (IOException ioe) { + throw new RuntimeException("Error running shell command", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + IoUtils.closeQuietly(sink); } - out.write(buffer, 0, readByteCount); - } - } catch (IOException ioe) { - throw new RuntimeException("Error running shell command", ioe); - } finally { - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(out); - IoUtils.closeQuietly(sink); - } + }; + }; + streamReader.start(); } @Override diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index a659acb..cf6619f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -20,7 +20,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.app.Activity; -import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -176,7 +175,8 @@ public class DevicePolicyManager { * * <p>This component is set as device owner and active admin when device owner provisioning is * started by an NFC message containing an NFC record with MIME type - * {@link #MIME_TYPE_PROVISIONING_NFC}. + * {@link #MIME_TYPE_PROVISIONING_NFC_V2}. For the NFC record, the component name should be + * flattened to a string, via {@link ComponentName#flattenToShortString()}. * * @see DeviceAdminReceiver */ @@ -341,6 +341,18 @@ public class DevicePolicyManager { = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION"; /** + * An int extra holding a minimum required version code for the device admin package. If the + * device admin is already installed on the device, it will only be re-downloaded from + * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION} if the version of the + * installed package is less than this version code. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE + = "android.app.extra.PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE"; + + /** * A String extra holding a http cookie header which should be used in the http request to the * url specified in {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}. * @@ -351,10 +363,10 @@ public class DevicePolicyManager { = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER"; /** - * A String extra holding the SHA-1 checksum of the file at download location specified in - * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}. If this doesn't match - * the file at the download location an error will be shown to the user and the user will be - * asked to factory reset the device. + * A String extra holding the URL-safe base64 encoded SHA-1 checksum of the file at download + * location specified in {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}. If + * this doesn't match the file at the download location an error will be shown to the user and + * the user will be asked to factory reset the device. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner * provisioning via an NFC bump. @@ -380,21 +392,23 @@ public class DevicePolicyManager { * A boolean extra indicating whether device encryption is required as part of Device Owner * provisioning. * - * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; /** - * On devices managed by a device owner app, a String representation of a Component name extra - * indicating the component of the application that is temporarily granted device owner - * privileges during device initialization and profile owner privileges during secondary user - * initialization. + * On devices managed by a device owner app, a {@link ComponentName} extra indicating the + * component of the application that is temporarily granted device owner privileges during + * device initialization and profile owner privileges during secondary user initialization. * - * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. - * @see ComponentName#unflattenFromString() + * <p> + * It can also be used in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts + * device owner provisioning via an NFC bump. For the NFC record, it should be flattened to a + * string first. + * + * @see ComponentName#flattenToShortString() */ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME"; @@ -404,53 +418,105 @@ public class DevicePolicyManager { * initializer package. When not provided it is assumed that the device initializer package is * already installed. * - * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION"; /** + * An int extra holding a minimum required version code for the device initializer package. + * If the initializer is already installed on the device, it will only be re-downloaded from + * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION} if the version of + * the installed package is less than this version code. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_MINIMUM_VERSION_CODE + = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_MINIMUM_VERSION_CODE"; + + /** * A String extra holding a http cookie header which should be used in the http request to the * url specified in {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}. * - * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER"; /** - * A String extra holding the SHA-1 checksum of the file at download location specified in + * A String extra holding the URL-safe base64 encoded SHA-1 checksum of the file at download + * location specified in * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}. If this doesn't * match the file at the download location an error will be shown to the user and the user will * be asked to factory reset the device. * - * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM"; /** - * This MIME type is used for starting the Device Owner provisioning. + * A String extra holding the MAC address of the Bluetooth device to connect to with status + * updates during provisioning. * - * <p>During device owner provisioning a device admin app is set as the owner of the device. - * A device owner has full control over the device. The device owner can not be modified by the - * user and the only way of resetting the device is if the device owner app calls a factory - * reset. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_BT_MAC_ADDRESS + = "android.app.extra.PROVISIONING_BT_MAC_ADDRESS"; + + /** + * A String extra holding the Bluetooth service UUID on the device to connect to with status + * updates during provisioning. * - * <p> A typical use case would be a device that is owned by a company, but used by either an - * employee or client. + * <p>This value must be specified when {@code #EXTRA_PROVISIONING_BT_MAC_ADDRESS} is present. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_BT_UUID + = "android.app.extra.PROVISIONING_BT_UUID"; + + /** + * A String extra holding a unique identifier used to identify the device connecting over + * Bluetooth. This identifier will be part of every status message sent to the remote device. + * + * <p>This value must be specified when {@code #EXTRA_PROVISIONING_BT_MAC_ADDRESS} is present. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_BT_DEVICE_ID + = "android.app.extra.PROVISIONING_BT_DEVICE_ID"; + + /** + * A Boolean extra that that will cause a provisioned device to temporarily proxy network + * traffic over Bluetooth. When a Wi-Fi network is available, the network proxy will stop. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_BT_USE_PROXY + = "android.app.extra.PROVISIONING_BT_USE_PROXY"; + /** + * This MIME type is used for starting the Device Owner provisioning that does not require + * provisioning features introduced in Android API level + * {@link android.os.Build.VERSION_CODES#MNC} or later levels. * - * <p> The NFC message should be send to an unprovisioned device. + * <p>For more information about the provisioning process see + * {@link #MIME_TYPE_PROVISIONING_NFC_V2}. * * <p>The NFC record must contain a serialized {@link java.util.Properties} object which * contains the following properties: * <ul> - * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li> * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOCAL_TIME} (convert to String), optional</li> * <li>{@link #EXTRA_PROVISIONING_TIME_ZONE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOCALE}, optional</li> @@ -461,17 +527,56 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, optional</li> * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT} (convert to String), optional</li> * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li></ul> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li></ul> * * <p> - * In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, it should also contain - * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}. - * As of {@link android.os.Build.VERSION_CODES#MNC}, it should contain - * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, (although - * specifying only {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported). - * This componentName must have been converted to a String via - * {@link android.content.ComponentName#flattenToString()} + * As of {@link android.os.Build.VERSION_CODES#MNC}, the properties should contain + * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead of + * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}, (although specifying only + * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported). + * + * @see #MIME_TYPE_PROVISIONING_NFC_V2 + * + */ + public static final String MIME_TYPE_PROVISIONING_NFC + = "application/com.android.managedprovisioning"; + + + /** + * This MIME type is used for starting the Device Owner provisioning that requires + * new provisioning features introduced in API version + * {@link android.os.Build.VERSION_CODES#MNC} in addition to those supported in earlier + * versions. + * + * <p>During device owner provisioning a device admin app is set as the owner of the device. + * A device owner has full control over the device. The device owner can not be modified by the + * user and the only way of resetting the device is if the device owner app calls a factory + * reset. + * + * <p> A typical use case would be a device that is owned by a company, but used by either an + * employee or client. + * + * <p> The NFC message should be sent to an unprovisioned device. + * + * <p>The NFC record must contain a serialized {@link java.util.Properties} object which + * contains the following properties in addition to properties listed at + * {@link #MIME_TYPE_PROVISIONING_NFC}: + * <ul> + * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_MINIMUM_VERSION_CODE}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}. + * Replaces {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}. The value of the property + * should be converted to a String via + * {@link android.content.ComponentName#flattenToString()}</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_BT_MAC_ADDRESS}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_BT_UUID}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_BT_DEVICE_ID}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_BT_USE_PROXY}, optional</li></ul> * * <p> When device owner provisioning has completed, an intent of the type * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcasted to the @@ -483,8 +588,8 @@ public class DevicePolicyManager { * <p>Input: Nothing.</p> * <p>Output: Nothing</p> */ - public static final String MIME_TYPE_PROVISIONING_NFC - = "application/com.android.managedprovisioning"; + public static final String MIME_TYPE_PROVISIONING_NFC_V2 + = "application/com.android.managedprovisioning.v2"; /** * Activity action: ask the user to add a new device administrator to the system. @@ -509,11 +614,10 @@ public class DevicePolicyManager { /** * @hide * Activity action: ask the user to add a new device administrator as the profile owner - * for this user. Only system privileged apps that have MANAGE_USERS and MANAGE_DEVICE_ADMINS - * permission can call this API. + * for this user. Only system apps can launch this intent. * - * <p>The ComponentName of the profile owner admin is pass in {@link #EXTRA_DEVICE_ADMIN} extra - * field. This will invoke a UI to bring the user through adding the profile owner admin + * <p>The ComponentName of the profile owner admin is passed in the {@link #EXTRA_DEVICE_ADMIN} + * extra field. This will invoke a UI to bring the user through adding the profile owner admin * to remotely control restrictions on the user. * * <p>The intent must be invoked via {@link Activity#startActivityForResult()} to receive the @@ -525,8 +629,8 @@ public class DevicePolicyManager { * field to provide the user with additional explanation (in addition * to your component's description) about what is being added. * - * <p>If there is already a profile owner active or the caller doesn't have the required - * permissions, the operation will return a failure result. + * <p>If there is already a profile owner active or the caller is not a system app, the + * operation will return a failure result. */ @SystemApi public static final String ACTION_SET_PROFILE_OWNER @@ -1941,7 +2045,8 @@ public class DevicePolicyManager { /** * Installs the given certificate as a user CA. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Use + * <code>null</code> if calling from a delegated certificate installer. * @param certBuffer encoded form of the certificate to install. * * @return false if the certBuffer cannot be parsed or installation is @@ -1961,7 +2066,8 @@ public class DevicePolicyManager { /** * Uninstalls the given certificate from trusted user CAs, if present. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Use + * <code>null</code> if calling from a delegated certificate installer. * @param certBuffer encoded form of the certificate to remove. */ public void uninstallCaCert(ComponentName admin, byte[] certBuffer) { @@ -1982,7 +2088,8 @@ public class DevicePolicyManager { * If a user has installed any certificates by other means than device policy these will be * included too. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Use + * <code>null</code> if calling from a delegated certificate installer. * @return a List of byte[] arrays, each encoding one user CA certificate. */ public List<byte[]> getInstalledCaCerts(ComponentName admin) { @@ -2009,7 +2116,8 @@ public class DevicePolicyManager { * Uninstalls all custom trusted CA certificates from the profile. Certificates installed by * means other than device policy will also be removed, except for system CA certificates. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Use + * <code>null</code> if calling from a delegated certificate installer. */ public void uninstallAllUserCaCerts(ComponentName admin) { if (mService != null) { @@ -2026,7 +2134,8 @@ public class DevicePolicyManager { /** * Returns whether this certificate is installed as a trusted CA. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Use + * <code>null</code> if calling from a delegated certificate installer. * @param certBuffer encoded form of the certificate to look up. */ public boolean hasCaCertInstalled(ComponentName admin, byte[] certBuffer) { @@ -2083,6 +2192,50 @@ public class DevicePolicyManager { } /** + * Called by a profile owner or device owner to grant access to privileged certificate + * manipulation APIs to a third-party CA certificate installer app. Granted APIs include + * {@link #getInstalledCaCerts}, {@link #hasCaCertInstalled}, {@link #installCaCert}, + * {@link #uninstallCaCert} and {@link #uninstallAllUserCaCerts}. + * <p> + * Delegated certificate installer is a per-user state. The delegated access is persistent until + * it is later cleared by calling this method with a null value or uninstallling the certificate + * installer. + * + * @param who Which {@link DeviceAdminReceiver} this request is associated with. + * @param installerPackage The package name of the certificate installer which will be given + * access. If <code>null</code> is given the current package will be cleared. + */ + public void setCertInstallerPackage(ComponentName who, String installerPackage) + throws SecurityException { + if (mService != null) { + try { + mService.setCertInstallerPackage(who, installerPackage); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Called by a profile owner or device owner to retrieve the certificate installer for the + * current user. null if none is set. + * + * @param who Which {@link DeviceAdminReceiver} this request is associated with. + * @return The package name of the current delegated certificate installer. <code>null</code> + * if none is set. + */ + public String getCertInstallerPackage(ComponentName who) throws SecurityException { + if (mService != null) { + try { + return mService.getCertInstallerPackage(who); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return null; + } + + /** * Called by an application that is administering the device to disable all cameras * on the device, for this user. After setting this, no applications running as this user * will be able to access any cameras on the device. diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index a1f1d92..0a0d77d 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -60,4 +60,13 @@ public abstract class DevicePolicyManagerInternal { */ public abstract void addOnCrossProfileWidgetProvidersChangeListener( OnCrossProfileWidgetProvidersChangeListener listener); + + /** + * Checks if an app with given uid is an active device admin of its user and has the policy + * specified. + * @param uid App uid. + * @param reqPolicy Required policy, for policies see {@link DevicePolicyManager}. + * @return true if the uid is an active admin with the given policy. + */ + public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index f69cf36..9ca52e5 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -132,6 +132,9 @@ interface IDevicePolicyManager { boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer, String alias); void choosePrivateKeyAlias(int uid, in String host, int port, in String url, in String alias, IBinder aliasCallback); + void setCertInstallerPackage(in ComponentName who, String installerPackage); + String getCertInstallerPackage(in ComponentName who); + void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java new file mode 100644 index 0000000..af7c053 --- /dev/null +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -0,0 +1,233 @@ +/** + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package android.app.usage; + +import android.app.usage.NetworkUsageStats.Bucket; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkIdentity; +import android.net.NetworkTemplate; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +/** + * Provides access to network usage history and statistics. Usage data is collected in + * discrete bins of time called 'Buckets'. See {@link NetworkUsageStats.Bucket} for details. + * <p /> + * Queries can define a time interval in the form of start and end timestamps (Long.MIN_VALUE and + * Long.MAX_VALUE can be used to simulate open ended intervals). All queries (except + * {@link #querySummaryForDevice}) collect only network usage of apps belonging to the same user + * as the client. In addition tethering usage, usage by removed users and apps, and usage by system + * is also included in the results. + * <h3> + * Summary queries + * </h3> + * These queries aggregate network usage across the whole interval. Therefore there will be only one + * bucket for a particular key and state combination. In case of the user-wide and device-wide + * summaries a single bucket containing the totalised network usage is returned. + * <h3> + * History queries + * </h3> + * These queries do not aggregate over time but do aggregate over state. Therefore there can be + * multiple buckets for a particular key but all Bucket's state is going to be + * {@link NetworkUsageStats.Bucket#STATE_ALL}. + * <p /> + * <b>NOTE:</b> This API requires the permission + * {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, which is a system-level permission and + * will not be granted to third-party apps. However, declaring the permission implies intention to + * use the API and the user of the device can grant permission through the Settings application. + * Profile owner apps are automatically granted permission to query data on the profile they manage + * (that is, for any query except {@link #querySummaryForDevice}). Device owner apps likewise get + * access to usage data of the primary user. + */ +public class NetworkStatsManager { + private final static String TAG = "NetworkStatsManager"; + + private final Context mContext; + + /** + * {@hide} + */ + public NetworkStatsManager(Context context) { + mContext = context; + } + /** + * Query network usage statistics summaries. Result is summarised data usage for the whole + * device. Result is a single Bucket aggregated over time, state and uid. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Bucket object or null if permissions are insufficient or error happened during + * statistics collection. + */ + public Bucket querySummaryForDevice(int networkType, String subscriberId, + long startTime, long endTime) throws SecurityException, RemoteException { + NetworkTemplate template = createTemplate(networkType, subscriberId); + if (template == null) { + return null; + } + + Bucket bucket = null; + NetworkUsageStats stats = new NetworkUsageStats(mContext, template, startTime, endTime); + bucket = stats.getDeviceSummaryForNetwork(startTime, endTime); + + stats.close(); + return bucket; + } + + /** + * Query network usage statistics summaries. Result is summarised data usage for all uids + * belonging to calling user. Result is a single Bucket aggregated over time, state and uid. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Bucket object or null if permissions are insufficient or error happened during + * statistics collection. + */ + public Bucket querySummaryForUser(int networkType, String subscriberId, long startTime, + long endTime) throws SecurityException, RemoteException { + NetworkTemplate template = createTemplate(networkType, subscriberId); + if (template == null) { + return null; + } + + NetworkUsageStats stats; + stats = new NetworkUsageStats(mContext, template, startTime, endTime); + stats.startSummaryEnumeration(startTime, endTime); + + stats.close(); + return stats.getSummaryAggregate(); + } + + /** + * Query network usage statistics summaries. Result filtered to include only uids belonging to + * calling user. Result is aggregated over time, hence all buckets will have the same start and + * end timestamps. Not aggregated over state or uid. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Statistics object or null if permissions are insufficient or error happened during + * statistics collection. + */ + public NetworkUsageStats querySummary(int networkType, String subscriberId, long startTime, + long endTime) throws SecurityException, RemoteException { + NetworkTemplate template = createTemplate(networkType, subscriberId); + if (template == null) { + return null; + } + + NetworkUsageStats result; + result = new NetworkUsageStats(mContext, template, startTime, endTime); + result.startSummaryEnumeration(startTime, endTime); + + return result; + } + + /** + * Query network usage statistics details. Only usable for uids belonging to calling user. + * Result is aggregated over state but not aggregated over time. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param uid UID of app + * @return Statistics object or null if permissions are insufficient or error happened during + * statistics collection. + */ + public NetworkUsageStats queryDetailsForUid(int networkType, String subscriberId, + long startTime, long endTime, int uid) throws SecurityException, RemoteException { + NetworkTemplate template = createTemplate(networkType, subscriberId); + if (template == null) { + return null; + } + + NetworkUsageStats result; + result = new NetworkUsageStats(mContext, template, startTime, endTime); + result.startHistoryEnumeration(uid); + + return result; + } + + /** + * Query network usage statistics details. Result filtered to include only uids belonging to + * calling user. Result is aggregated over state but not aggregated over time or uid. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Statistics object or null if permissions are insufficient or error happened during + * statistics collection. + */ + public NetworkUsageStats queryDetails(int networkType, String subscriberId, long startTime, + long endTime) throws SecurityException, RemoteException { + NetworkTemplate template = createTemplate(networkType, subscriberId); + if (template == null) { + return null; + } + NetworkUsageStats result; + result = new NetworkUsageStats(mContext, template, startTime, endTime); + result.startUserUidEnumeration(); + return result; + } + + private static NetworkTemplate createTemplate(int networkType, String subscriberId) { + NetworkTemplate template = null; + switch (networkType) { + case ConnectivityManager.TYPE_MOBILE: { + template = NetworkTemplate.buildTemplateMobileAll(subscriberId); + } break; + case ConnectivityManager.TYPE_WIFI: { + template = NetworkTemplate.buildTemplateWifiWildcard(); + } break; + default: { + Log.w(TAG, "Cannot create template for network type " + networkType + + ", subscriberId '" + NetworkIdentity.scrubSubscriberId(subscriberId) + + "'."); + } + } + return template; + } +} diff --git a/core/java/android/app/usage/NetworkUsageStats.java b/core/java/android/app/usage/NetworkUsageStats.java new file mode 100644 index 0000000..990d231 --- /dev/null +++ b/core/java/android/app/usage/NetworkUsageStats.java @@ -0,0 +1,479 @@ +/** + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package android.app.usage; + +import android.content.Context; +import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import dalvik.system.CloseGuard; + +/** + * Class providing enumeration over buckets of network usage statistics. NetworkUsageStats objects + * are returned as results to various queries in {@link NetworkStatsManager}. + */ +public final class NetworkUsageStats implements AutoCloseable { + private final static String TAG = "NetworkUsageStats"; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + + /** + * Start timestamp of stats collected + */ + private final long mStartTimeStamp; + + /** + * End timestamp of stats collected + */ + private final long mEndTimeStamp; + + + /** + * Non-null array indicates the query enumerates over uids. + */ + private int[] mUids; + + /** + * Index of the current uid in mUids when doing uid enumeration or a single uid value, + * depending on query type. + */ + private int mUidOrUidIndex; + + /** + * The session while the query requires it, null if all the stats have been collected or close() + * has been called. + */ + private INetworkStatsSession mSession; + private NetworkTemplate mTemplate; + + /** + * Results of a summary query. + */ + private NetworkStats mSummary = null; + + /** + * Results of detail queries. + */ + private NetworkStatsHistory mHistory = null; + + /** + * Where we are in enumerating over the current result. + */ + private int mEnumerationIndex = 0; + + /** + * Recycling entry objects to prevent heap fragmentation. + */ + private NetworkStats.Entry mRecycledSummaryEntry = null; + private NetworkStatsHistory.Entry mRecycledHistoryEntry = null; + + /** @hide */ + NetworkUsageStats(Context context, NetworkTemplate template, long startTimestamp, + long endTimestamp) throws RemoteException, SecurityException { + final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + // Open network stats session + mSession = statsService.openSessionForUsageStats(context.getOpPackageName()); + mCloseGuard.open("close"); + mTemplate = template; + mStartTimeStamp = startTimestamp; + mEndTimeStamp = endTimestamp; + } + + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); + } + } + + // -------------------------BEGINNING OF PUBLIC API----------------------------------- + + /** + * Buckets are the smallest elements of a query result. As some dimensions of a result may be + * aggregated (e.g. time or state) some values may be equal across all buckets. + */ + public static class Bucket { + /** + * Combined usage across all other states. + */ + public static final int STATE_ALL = -1; + + /** + * Usage not accounted in any other states. + */ + public static final int STATE_DEFAULT = 0x1; + + /** + * Foreground usage. + */ + public static final int STATE_FOREGROUND = 0x2; + + /** + * Special UID value for removed apps. + */ + public static final int UID_REMOVED = -4; + + /** + * Special UID value for data usage by tethering. + */ + public static final int UID_TETHERING = -5; + + private int mUid; + private int mState; + private long mBeginTimeStamp; + private long mEndTimeStamp; + private long mRxBytes; + private long mRxPackets; + private long mTxBytes; + private long mTxPackets; + + private static int convertState(int networkStatsSet) { + switch (networkStatsSet) { + case NetworkStats.SET_ALL : return STATE_ALL; + case NetworkStats.SET_DEFAULT : return STATE_DEFAULT; + case NetworkStats.SET_FOREGROUND : return STATE_FOREGROUND; + } + return 0; + } + + private static int convertUid(int uid) { + switch (uid) { + case TrafficStats.UID_REMOVED: return UID_REMOVED; + case TrafficStats.UID_TETHERING: return UID_TETHERING; + } + return uid; + } + + public Bucket() { + } + + /** + * Key of the bucket. Usually an app uid or one of the following special values:<p /> + * <ul> + * <li>{@link #UID_REMOVED}</li> + * <li>{@link #UID_TETHERING}</li> + * <li>{@link android.os.Process#SYSTEM_UID}</li> + * </ul> + * @return Bucket key. + */ + public int getUid() { + return mUid; + } + + /** + * Usage state. One of the following values:<p/> + * <ul> + * <li>{@link #STATE_ALL}</li> + * <li>{@link #STATE_DEFAULT}</li> + * <li>{@link #STATE_FOREGROUND}</li> + * </ul> + * @return Usage state. + */ + public int getState() { + return mState; + } + + /** + * Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Start of interval. + */ + public long getStartTimeStamp() { + return mBeginTimeStamp; + } + + /** + * End timestamp of the bucket's time interval. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return End of interval. + */ + public long getEndTimeStamp() { + return mEndTimeStamp; + } + + /** + * Number of bytes received during the bucket's time interval. Statistics are measured at + * the network layer, so they include both TCP and UDP usage. + * @return Number of bytes. + */ + public long getRxBytes() { + return mRxBytes; + } + + /** + * Number of bytes transmitted during the bucket's time interval. Statistics are measured at + * the network layer, so they include both TCP and UDP usage. + * @return Number of bytes. + */ + public long getTxBytes() { + return mTxBytes; + } + + /** + * Number of packets received during the bucket's time interval. Statistics are measured at + * the network layer, so they include both TCP and UDP usage. + * @return Number of packets. + */ + public long getRxPackets() { + return mRxPackets; + } + + /** + * Number of packets transmitted during the bucket's time interval. Statistics are measured + * at the network layer, so they include both TCP and UDP usage. + * @return Number of packets. + */ + public long getTxPackets() { + return mTxPackets; + } + } + + /** + * Fills the recycled bucket with data of the next bin in the enumeration. + * @param bucketOut Bucket to be filled with data. + * @return true if successfully filled the bucket, false otherwise. + */ + public boolean getNextBucket(Bucket bucketOut) { + if (mSummary != null) { + return getNextSummaryBucket(bucketOut); + } else { + return getNextHistoryBucket(bucketOut); + } + } + + /** + * Check if it is possible to ask for a next bucket in the enumeration. + * @return true if there is at least one more bucket. + */ + public boolean hasNextBucket() { + if (mSummary != null) { + return mEnumerationIndex < mSummary.size(); + } else if (mHistory != null) { + return mEnumerationIndex < mHistory.size() + || hasNextUid(); + } + return false; + } + + /** + * Closes the enumeration. Call this method before this object gets out of scope. + */ + @Override + public void close() { + if (mSession != null) { + try { + mSession.close(); + } catch (RemoteException e) { + Log.w(TAG, e); + // Otherwise, meh + } + } + mSession = null; + if (mCloseGuard != null) { + mCloseGuard.close(); + } + } + + // -------------------------END OF PUBLIC API----------------------------------- + + /** + * Collects device summary results into a Bucket. + * @param startTime + * @param endTime + * @throws RemoteException + */ + Bucket getDeviceSummaryForNetwork(long startTime, long endTime) throws RemoteException { + mSummary = mSession.getDeviceSummaryForNetwork(mTemplate, startTime, endTime); + + // Setting enumeration index beyond end to avoid accidental enumeration over data that does + // not belong to the calling user. + mEnumerationIndex = mSummary.size(); + + return getSummaryAggregate(); + } + + /** + * Collects summary results and sets summary enumeration mode. + * @param startTime + * @param endTime + * @throws RemoteException + */ + void startSummaryEnumeration(long startTime, long endTime) throws RemoteException { + mSummary = mSession.getSummaryForAllUid(mTemplate, startTime, endTime, false); + + mEnumerationIndex = 0; + } + + /** + * Collects history results for uid and resets history enumeration index. + */ + void startHistoryEnumeration(int uid) { + mHistory = null; + try { + mHistory = mSession.getHistoryForUid(mTemplate, uid, NetworkStats.SET_ALL, + NetworkStats.TAG_NONE, NetworkStatsHistory.FIELD_ALL); + setSingleUid(uid); + } catch (RemoteException e) { + Log.w(TAG, e); + // Leaving mHistory null + } + mEnumerationIndex = 0; + } + + /** + * Starts uid enumeration for current user. + * @throws RemoteException + */ + void startUserUidEnumeration() throws RemoteException { + setUidEnumeration(mSession.getRelevantUids()); + stepHistory(); + } + + /** + * Steps to next uid in enumeration and collects history for that. + */ + private void stepHistory(){ + if (hasNextUid()) { + stepUid(); + mHistory = null; + try { + mHistory = mSession.getHistoryForUid(mTemplate, getUid(), NetworkStats.SET_ALL, + NetworkStats.TAG_NONE, NetworkStatsHistory.FIELD_ALL); + } catch (RemoteException e) { + Log.w(TAG, e); + // Leaving mHistory null + } + mEnumerationIndex = 0; + } + } + + private void fillBucketFromSummaryEntry(Bucket bucketOut) { + bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid); + bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set); + bucketOut.mBeginTimeStamp = mStartTimeStamp; + bucketOut.mEndTimeStamp = mEndTimeStamp; + bucketOut.mRxBytes = mRecycledSummaryEntry.rxBytes; + bucketOut.mRxPackets = mRecycledSummaryEntry.rxPackets; + bucketOut.mTxBytes = mRecycledSummaryEntry.txBytes; + bucketOut.mTxPackets = mRecycledSummaryEntry.txPackets; + } + + /** + * Getting the next item in summary enumeration. + * @param bucketOut Next item will be set here. + * @return true if a next item could be set. + */ + private boolean getNextSummaryBucket(Bucket bucketOut) { + if (bucketOut != null && mEnumerationIndex < mSummary.size()) { + mRecycledSummaryEntry = mSummary.getValues(mEnumerationIndex++, mRecycledSummaryEntry); + fillBucketFromSummaryEntry(bucketOut); + return true; + } + return false; + } + + Bucket getSummaryAggregate() { + if (mSummary == null) { + return null; + } + Bucket bucket = new Bucket(); + if (mRecycledSummaryEntry == null) { + mRecycledSummaryEntry = new NetworkStats.Entry(); + } + mSummary.getTotal(mRecycledSummaryEntry); + fillBucketFromSummaryEntry(bucket); + return bucket; + } + /** + * Getting the next item in a history enumeration. + * @param bucketOut Next item will be set here. + * @return true if a next item could be set. + */ + private boolean getNextHistoryBucket(Bucket bucketOut) { + if (bucketOut != null && mHistory != null) { + if (mEnumerationIndex < mHistory.size()) { + mRecycledHistoryEntry = mHistory.getValues(mEnumerationIndex++, + mRecycledHistoryEntry); + bucketOut.mUid = Bucket.convertUid(getUid()); + bucketOut.mState = Bucket.STATE_ALL; + bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart; + bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart + + mRecycledHistoryEntry.bucketDuration; + bucketOut.mRxBytes = mRecycledHistoryEntry.rxBytes; + bucketOut.mRxPackets = mRecycledHistoryEntry.rxPackets; + bucketOut.mTxBytes = mRecycledHistoryEntry.txBytes; + bucketOut.mTxPackets = mRecycledHistoryEntry.txPackets; + return true; + } else if (hasNextUid()) { + stepHistory(); + return getNextHistoryBucket(bucketOut); + } + } + return false; + } + + // ------------------ UID LOGIC------------------------ + + private boolean isUidEnumeration() { + return mUids != null; + } + + private boolean hasNextUid() { + return isUidEnumeration() && (mUidOrUidIndex + 1) < mUids.length; + } + + private int getUid() { + // Check if uid enumeration. + if (isUidEnumeration()) { + if (mUidOrUidIndex < 0 || mUidOrUidIndex >= mUids.length) { + throw new IndexOutOfBoundsException( + "Index=" + mUidOrUidIndex + " mUids.length=" + mUids.length); + } + return mUids[mUidOrUidIndex]; + } + // Single uid mode. + return mUidOrUidIndex; + } + + private void setSingleUid(int uid) { + mUidOrUidIndex = uid; + } + + private void setUidEnumeration(int[] uids) { + mUids = uids; + mUidOrUidIndex = -1; + } + + private void stepUid() { + if (mUids != null) { + ++mUidOrUidIndex; + } + } +} diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java index 7eae439..0106686 100644 --- a/core/java/android/bluetooth/le/ScanSettings.java +++ b/core/java/android/bluetooth/le/ScanSettings.java @@ -25,6 +25,13 @@ import android.os.Parcelable; * parameters for the scan. */ public final class ScanSettings implements Parcelable { + + /** + * A special Bluetooth LE scan mode. Applications using this scan mode will passively listen for + * other scan results without starting BLE scans themselves. + */ + public static final int SCAN_MODE_OPPORTUNISTIC = -1; + /** * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the * least power. @@ -177,7 +184,7 @@ public final class ScanSettings implements Parcelable { * @throws IllegalArgumentException If the {@code scanMode} is invalid. */ public Builder setScanMode(int scanMode) { - if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) { + if (scanMode < SCAN_MODE_OPPORTUNISTIC || scanMode > SCAN_MODE_LOW_LATENCY) { throw new IllegalArgumentException("invalid scan mode " + scanMode); } mScanMode = scanMode; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 80b5e0b..e9d4e59 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2149,7 +2149,7 @@ public abstract class Context { CONNECTIVITY_SERVICE, //@hide: UPDATE_LOCK_SERVICE, //@hide: NETWORKMANAGEMENT_SERVICE, - //@hide: NETWORK_STATS_SERVICE, + NETWORK_STATS_SERVICE, //@hide: NETWORK_POLICY_SERVICE, WIFI_SERVICE, WIFI_PASSPOINT_SERVICE, @@ -2259,6 +2259,9 @@ public abstract class Context { * <dd> A {@link android.os.BatteryManager} for managing battery state * <dt> {@link #JOB_SCHEDULER_SERVICE} ("taskmanager") * <dd> A {@link android.app.job.JobScheduler} for managing scheduled tasks + * <dt> {@link #NETWORK_STATS_SERVICE} ("netstats") + * <dd> A {@link android.app.usage.NetworkStatsManager NetworkStatsManager} for querying network + * usage statistics. * </dl> * * <p>Note: System services obtained via this API may be closely associated with @@ -2316,6 +2319,8 @@ public abstract class Context { * @see android.os.BatteryManager * @see #JOB_SCHEDULER_SERVICE * @see android.app.job.JobScheduler + * @see #NETWORK_STATS_SERVICE + * @see android.app.usage.NetworkStatsManager */ public abstract Object getSystemService(@ServiceName @NonNull String name); @@ -2334,7 +2339,8 @@ public abstract class Context { * {@link android.telephony.TelephonyManager}, {@link android.telephony.SubscriptionManager}, * {@link android.view.inputmethod.InputMethodManager}, * {@link android.app.UiModeManager}, {@link android.app.DownloadManager}, - * {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler}. + * {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler}, + * {@link android.app.usage.NetworkStatsManager}. * </p><p> * Note: System services obtained via this API may be closely associated with * the Context in which they are obtained from. In general, do not share the @@ -2563,7 +2569,13 @@ public abstract class Context { */ public static final String NETWORKMANAGEMENT_SERVICE = "network_management"; - /** {@hide} */ + /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.app.usage.NetworkStatsManager} for querying network usage stats. + * + * @see #getSystemService + * @see android.app.usage.NetworkStatsManager + */ public static final String NETWORK_STATS_SERVICE = "netstats"; /** {@hide} */ public static final String NETWORK_POLICY_SERVICE = "netpolicy"; @@ -2819,7 +2831,7 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@link android.bluetooth.BluetoothAdapter} for using Bluetooth. + * {@link android.bluetooth.BluetoothManager} for using Bluetooth. * * @see #getSystemService */ @@ -3109,6 +3121,20 @@ public abstract class Context { public abstract int checkCallingOrSelfPermission(@NonNull String permission); /** + * Determine whether <em>you</em> have been granted a particular permission. + * + * @param permission The name of the permission being checked. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if you have the + * permission, or {@link PackageManager#PERMISSION_DENIED} if not. + * + * @see PackageManager#checkPermission(String, String) + * @see #checkCallingPermission(String) + */ + @PackageManager.PermissionResult + public abstract int checkSelfPermission(@NonNull String permission); + + /** * If the given permission is not allowed for a particular process * and user ID running in the system, throw a {@link SecurityException}. * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 6e8b7c1..8c5a87c 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -602,6 +602,11 @@ public class ContextWrapper extends Context { } @Override + public int checkSelfPermission(String permission) { + return mBase.checkSelfPermission(permission); + } + + @Override public void enforcePermission( String permission, int pid, int uid, String message) { mBase.enforcePermission(permission, pid, uid, message); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index f685475..030b770 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1502,6 +1502,66 @@ public class Intent implements Parcelable, Cloneable { */ public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION"; + /** + * Activity action: Launch UI to manage the permissions of an app. + * <p> + * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose permissions + * will be managed by the launched UI. + * </p> + * <p> + * Output: Nothing. + * </p> + * + * @see #EXTRA_PACKAGE_NAME + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_APP_PERMISSIONS = + "android.intent.action.MANAGE_APP_PERMISSIONS"; + + /** + * Intent extra: An app package name. + * <p> + * Type: String + * </p>S + * + * @hide + */ + @SystemApi + public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME"; + + /** + * Activity action: Launch UI to manage which apps have a given permission. + * <p> + * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission access + * to which will be managed by the launched UI. + * </p> + * <p> + * Output: Nothing. + * </p> + * + * @see #EXTRA_PERMISSION_NAME + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_PERMISSION_APPS = + "android.intent.action.MANAGE_PERMISSION_APPS"; + + /** + * Intent extra: The name of a permission. + * <p> + * Type: String + * </p> + * + * @hide + */ + @SystemApi + public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent broadcast actions (see action variable). @@ -1519,6 +1579,10 @@ public class Intent implements Parcelable, Cloneable { * </p><p> * See {@link android.os.PowerManager#isInteractive} for details. * </p> + * You <em>cannot</em> receive this through components declared in + * manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. * * <p class="note">This is a protected intent that can only be sent * by the system. @@ -1539,6 +1603,10 @@ public class Intent implements Parcelable, Cloneable { * </p><p> * See {@link android.os.PowerManager#isInteractive} for details. * </p> + * You <em>cannot</em> receive this through components declared in + * manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. * * <p class="note">This is a protected intent that can only be sent * by the system. @@ -1576,7 +1644,7 @@ public class Intent implements Parcelable, Cloneable { /** * Broadcast Action: The current time has changed. Sent every - * minute. You can <em>not</em> receive this through components declared + * minute. You <em>cannot</em> receive this through components declared * in manifests, only by explicitly registering for it with * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) * Context.registerReceiver()}. @@ -1922,7 +1990,7 @@ public class Intent implements Parcelable, Cloneable { * appropriately. * * <p class="note"> - * You can <em>not</em> receive this through components declared + * You <em>cannot</em> receive this through components declared * in manifests, only by explicitly registering for it with * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) * Context.registerReceiver()}. @@ -1949,7 +2017,7 @@ public class Intent implements Parcelable, Cloneable { * contents of the Intent. * * <p class="note"> - * You can <em>not</em> receive this through components declared + * You <em>cannot</em> receive this through components declared * in manifests, only by explicitly registering for it with * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) * Context.registerReceiver()}. See {@link #ACTION_BATTERY_LOW}, diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java index 1d5ed8a..46c2c6e 100644 --- a/core/java/android/content/UndoManager.java +++ b/core/java/android/content/UndoManager.java @@ -119,15 +119,13 @@ public class UndoManager { } /** - * Flatten the current undo state into a Parcelable object, which can later be restored - * with {@link #restoreInstanceState(android.os.Parcelable)}. + * Flatten the current undo state into a Parcel object, which can later be restored + * with {@link #restoreInstanceState(android.os.Parcel, java.lang.ClassLoader)}. */ - public Parcelable saveInstanceState() { + public void saveInstanceState(Parcel p) { if (mUpdateCount > 0) { throw new IllegalStateException("Can't save state while updating"); } - ParcelableParcel pp = new ParcelableParcel(getClass().getClassLoader()); - Parcel p = pp.getParcel(); mStateSeq++; if (mStateSeq <= 0) { mStateSeq = 0; @@ -151,7 +149,6 @@ public class UndoManager { mRedos.get(i).writeToParcel(p); } p.writeInt(0); - return pp; } void saveOwner(UndoOwner owner, Parcel out) { @@ -168,26 +165,24 @@ public class UndoManager { } /** - * Restore an undo state previously created with {@link #saveInstanceState()}. This will - * restore the UndoManager's state to almost exactly what it was at the point it had + * Restore an undo state previously created with {@link #saveInstanceState(Parcel)}. This + * will restore the UndoManager's state to almost exactly what it was at the point it had * been previously saved; the only information not restored is the data object * associated with each {@link UndoOwner}, which requires separate calls to * {@link #getOwner(String, Object)} to re-associate the owner with its data. */ - public void restoreInstanceState(Parcelable state) { + public void restoreInstanceState(Parcel p, ClassLoader loader) { if (mUpdateCount > 0) { throw new IllegalStateException("Can't save state while updating"); } forgetUndos(null, -1); forgetRedos(null, -1); - ParcelableParcel pp = (ParcelableParcel)state; - Parcel p = pp.getParcel(); mHistorySize = p.readInt(); mStateOwners = new UndoOwner[p.readInt()]; int stype; while ((stype=p.readInt()) != 0) { - UndoState ustate = new UndoState(this, p, pp.getClassLoader()); + UndoState ustate = new UndoState(this, p, loader); if (stype == 1) { mUndos.add(0, ustate); } else { @@ -311,12 +306,15 @@ public class UndoManager { } int removed = 0; - for (int i=0; i<mUndos.size() && removed < count; i++) { + int i = 0; + while (i < mUndos.size() && removed < count) { UndoState state = mUndos.get(i); if (count > 0 && matchOwners(state, owners)) { state.destroy(); mUndos.remove(i); removed++; + } else { + i++; } } @@ -329,12 +327,15 @@ public class UndoManager { } int removed = 0; - for (int i=0; i<mRedos.size() && removed < count; i++) { + int i = 0; + while (i < mRedos.size() && removed < count) { UndoState state = mRedos.get(i); if (count > 0 && matchOwners(state, owners)) { state.destroy(); mRedos.remove(i); removed++; + } else { + i++; } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index e1a2aa9..2496e45 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -334,6 +334,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_FULL_BACKUP_ONLY = 1<<26; /** + * Value for {@link #flags}: {@code true} if the application may use cleartext network traffic + * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP + * without STARTTLS or TLS). If {@code false}, the app declares that it does not intend to use + * cleartext network traffic, in which case platform components (e.g., HTTP stacks, + * {@code WebView}, {@code MediaPlayer}) will refuse app's requests to use cleartext traffic. + * Third-party libraries are encouraged to honor this flag as well. + */ + public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27; + + /** + * When set installer extracts native libs from .apk files. + */ + public static final int FLAG_EXTRACT_NATIVE_LIBS = 1<<28; + + /** * Value for {@link #flags}: true if code from this application will need to be * loaded into other applications' processes. On devices that support multiple * instruction sets, this implies the code might be loaded into a process that's @@ -362,7 +377,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_LARGE_HEAP}, {@link #FLAG_STOPPED}, * {@link #FLAG_SUPPORTS_RTL}, {@link #FLAG_INSTALLED}, * {@link #FLAG_IS_DATA_ONLY}, {@link #FLAG_IS_GAME}, - * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_MULTIARCH}. + * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_USES_CLEARTEXT_TRAFFIC}, + * {@link #FLAG_MULTIARCH}. */ public int flags = 0; @@ -598,6 +614,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public int installLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; + /** + * True when the application's rendering should be hardware accelerated. + */ + public boolean hardwareAccelerated; + public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); if (className != null) { @@ -633,10 +654,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } pw.println(prefix + "dataDir=" + dataDir); if (sharedLibraryFiles != null) { - pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles); + pw.println(prefix + "sharedLibraryFiles=" + Arrays.toString(sharedLibraryFiles)); } pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion + " versionCode=" + versionCode); + pw.println(prefix + "hardwareAccelerated=" + hardwareAccelerated); if (manageSpaceActivityName != null) { pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName); } @@ -722,6 +744,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { descriptionRes = orig.descriptionRes; uiOptions = orig.uiOptions; backupAgentName = orig.backupAgentName; + hardwareAccelerated = orig.hardwareAccelerated; } @@ -773,6 +796,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString(backupAgentName); dest.writeInt(descriptionRes); dest.writeInt(uiOptions); + dest.writeInt(hardwareAccelerated ? 1 : 0); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -823,6 +847,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { backupAgentName = source.readString(); descriptionRes = source.readInt(); uiOptions = source.readInt(); + hardwareAccelerated = source.readInt() != 0; } /** diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 3e5d362..c6d97f1 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -46,32 +46,34 @@ import android.content.pm.UserInfo; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.net.Uri; +import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.content.IntentSender; +import com.android.internal.os.IResultReceiver; /** * See {@link PackageManager} for documentation on most of the APIs * here. - * + * * {@hide} */ interface IPackageManager { boolean isPackageAvailable(String packageName, int userId); PackageInfo getPackageInfo(String packageName, int flags, int userId); int getPackageUid(String packageName, int userId); - int[] getPackageGids(String packageName); - + int[] getPackageGids(String packageName, int userId); + String[] currentToCanonicalPackageNames(in String[] names); String[] canonicalToCurrentPackageNames(in String[] names); PermissionInfo getPermissionInfo(String name, int flags); - + List<PermissionInfo> queryPermissionsByGroup(String group, int flags); - + PermissionGroupInfo getPermissionGroupInfo(String name, int flags); - + List<PermissionGroupInfo> getAllPermissionGroups(int flags); - + ApplicationInfo getApplicationInfo(String packageName, int flags ,int userId); ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId); @@ -85,28 +87,28 @@ interface IPackageManager { ProviderInfo getProviderInfo(in ComponentName className, int flags, int userId); - int checkPermission(String permName, String pkgName); - + int checkPermission(String permName, String pkgName, int userId); + int checkUidPermission(String permName, int uid); - + boolean addPermission(in PermissionInfo info); - + void removePermission(String name); - void grantPermission(String packageName, String permissionName); + boolean grantPermission(String packageName, String permissionName, int userId); - void revokePermission(String packageName, String permissionName); + boolean revokePermission(String packageName, String permissionName, int userId); boolean isProtectedBroadcast(String actionName); - + int checkSignatures(String pkg1, String pkg2); - + int checkUidSignatures(int uid1, int uid2); - + String[] getPackagesForUid(int uid); - + String getNameForUid(int uid); - + int getUidForSharedUser(String sharedUserName); int getFlagsForUid(int uid); @@ -121,7 +123,7 @@ interface IPackageManager { boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId); - List<ResolveInfo> queryIntentActivities(in Intent intent, + List<ResolveInfo> queryIntentActivities(in Intent intent, String resolvedType, int flags, int userId); List<ResolveInfo> queryIntentActivityOptions( @@ -168,7 +170,7 @@ interface IPackageManager { /** * Retrieve all applications that are marked as persistent. - * + * * @return A List<applicationInfo> containing one entry for each persistent * application. */ @@ -178,7 +180,7 @@ interface IPackageManager { /** * Retrieve sync information for all content providers. - * + * * @param outNames Filled in with a list of the root names of the content * providers that can sync. * @param outInfo Filled in with a list of the ProviderInfo for each diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 9223269..9e6c6b5 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -167,8 +167,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_PERMISSIONS} was set. Each value matches * the corresponding entry in {@link #requestedPermissions}, and will have - * the flags {@link #REQUESTED_PERMISSION_REQUIRED} and - * {@link #REQUESTED_PERMISSION_GRANTED} set as appropriate. + * the flag {@link #REQUESTED_PERMISSION_GRANTED} set as appropriate. */ public int[] requestedPermissionsFlags; @@ -176,6 +175,8 @@ public class PackageInfo implements Parcelable { * Flag for {@link #requestedPermissionsFlags}: the requested permission * is required for the application to run; the user can not optionally * disable it. Currently all permissions are required. + * + * @removed We do not support required permissions. */ public static final int REQUESTED_PERMISSION_REQUIRED = 1<<0; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 0365689..59a16da 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -44,6 +44,7 @@ import android.os.Environment; import android.os.RemoteException; import android.os.UserHandle; import android.util.AndroidException; +import com.android.internal.util.ArrayUtils; import java.io.File; import java.lang.annotation.Retention; @@ -377,6 +378,16 @@ public abstract class PackageManager { public static final int INSTALL_ALLOW_DOWNGRADE = 0x00000080; /** + * Flag parameter for {@link #installPackage} to indicate that all runtime + * permissions should be granted to the package. If {@link #INSTALL_ALL_USERS} + * is set the runtime permissions will be granted to all users, otherwise + * only to the owner. + * + * @hide + */ + public static final int INSTALL_GRANT_RUNTIME_PERMISSIONS = 0x00000100; + + /** * Flag parameter for * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate * that you don't want to kill the app containing the component. Be careful when you set this @@ -1663,21 +1674,46 @@ public abstract class PackageManager { = "android.content.pm.extra.VERIFICATION_VERSION_CODE"; /** - * The action used to request that the user approve a permission request - * from the application. + * The action used to request that the user approve a grant permissions + * request from the application. * * @hide */ - public static final String ACTION_REQUEST_PERMISSION - = "android.content.pm.action.REQUEST_PERMISSION"; + @SystemApi + public static final String ACTION_REQUEST_PERMISSIONS = + "android.content.pm.action.REQUEST_PERMISSIONS"; /** - * Extra field name for the list of permissions, which the user must approve. + * The component name handling runtime permission grants. * * @hide */ - public static final String EXTRA_REQUEST_PERMISSION_PERMISSION_LIST - = "android.content.pm.extra.PERMISSION_LIST"; + public static final String GRANT_PERMISSIONS_PACKAGE_NAME = + "com.android.packageinstaller"; + + /** + * The names of the requested permissions. + * <p> + * <strong>Type:</strong> String[] + * </p> + * + * @hide + */ + @SystemApi + public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = + "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; + + /** + * The results from the permissions request. + * <p> + * <strong>Type:</strong> int[] of #PermissionResult + * </p> + * + * @hide + */ + @SystemApi + public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS + = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; /** * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of @@ -2184,51 +2220,70 @@ public abstract class PackageManager { public abstract void removePermission(String name); /** - * Returns an {@link Intent} suitable for passing to {@code startActivityForResult} - * which prompts the user to grant {@code permissions} to this application. - * @hide + * Grant a runtime permission to an application which the application does not + * already have. The permission must have been requested by the application. + * If the application is not allowed to hold the permission, a {@link + * java.lang.SecurityException} is thrown. + * <p> + * <strong>Note: </strong>Using this API requires holding + * android.permission.GRANT_REVOKE_PERMISSIONS and if the user id is + * not the current user android.permission.INTERACT_ACROSS_USERS_FULL. + * </p> + * + * @param packageName The package to which to grant the permission. + * @param permissionName The permission name to grant. + * @param user The user for which to grant the permission. * - * @throws NullPointerException if {@code permissions} is {@code null}. - * @throws IllegalArgumentException if {@code permissions} contains {@code null}. + * @see #revokePermission(String, String, android.os.UserHandle) + * + * @hide */ - public Intent buildPermissionRequestIntent(String... permissions) { - if (permissions == null) { - throw new NullPointerException("permissions cannot be null"); - } - for (String permission : permissions) { - if (permission == null) { - throw new IllegalArgumentException("permissions cannot contain null"); - } - } - - Intent i = new Intent(ACTION_REQUEST_PERMISSION); - i.putExtra(EXTRA_REQUEST_PERMISSION_PERMISSION_LIST, permissions); - i.setPackage("com.android.packageinstaller"); - return i; - } + @SystemApi + public abstract void grantPermission(@NonNull String packageName, + @NonNull String permissionName, @NonNull UserHandle user); /** - * Grant a permission to an application which the application does not - * already have. The permission must have been requested by the application, - * but as an optional permission. If the application is not allowed to - * hold the permission, a SecurityException is thrown. - * @hide + * Revoke a runtime permission that was previously granted by {@link + * #grantPermission(String, String, android.os.UserHandle)}. The permission + * must have been requested by and granted to the application. If the + * application is not allowed to hold the permission, a {@link + * java.lang.SecurityException} is thrown. + * <p> + * <strong>Note: </strong>Using this API requires holding + * android.permission.GRANT_REVOKE_PERMISSIONS and if the user id is + * not the current user android.permission.INTERACT_ACROSS_USERS_FULL. + * </p> + * + * @param packageName The package from which to revoke the permission. + * @param permissionName The permission name to revoke. + * @param user The user for which to revoke the permission. * - * @param packageName The name of the package that the permission will be - * granted to. - * @param permissionName The name of the permission. + * @see #grantPermission(String, String, android.os.UserHandle) + * + * @hide */ - public abstract void grantPermission(String packageName, String permissionName); + @SystemApi + public abstract void revokePermission(@NonNull String packageName, + @NonNull String permissionName, @NonNull UserHandle user); /** - * Revoke a permission that was previously granted by {@link #grantPermission}. - * @hide + * Returns an {@link android.content.Intent} suitable for passing to + * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)} + * which prompts the user to grant permissions to this application. + * + * @throws NullPointerException if {@code permissions} is {@code null} or empty. * - * @param packageName The name of the package that the permission will be - * granted to. - * @param permissionName The name of the permission. + * @hide */ - public abstract void revokePermission(String packageName, String permissionName); + public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) { + if (ArrayUtils.isEmpty(permissions)) { + throw new NullPointerException("permission cannot be null or empty"); + } + Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); + intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions); + intent.setPackage(GRANT_PERMISSIONS_PACKAGE_NAME); + return intent; + } /** * Compare the signatures of two packages to determine if the same @@ -3692,11 +3747,11 @@ public abstract class PackageManager { * {@link #addPreferredActivity}, that are * currently registered with the system. * - * @param outFilters A list in which to place the filters of all of the - * preferred activities, or null for none. - * @param outActivities A list in which to place the component names of - * all of the preferred activities, or null for none. - * @param packageName An option package in which you would like to limit + * @param outFilters A required list in which to place the filters of all of the + * preferred activities. + * @param outActivities A required list in which to place the component names of + * all of the preferred activities. + * @param packageName An optional package in which you would like to limit * the list. If null, all activities will be returned; if non-null, only * those activities in the given package are returned. * @@ -3704,8 +3759,8 @@ public abstract class PackageManager { * (the number of distinct IntentFilter records, not the number of unique * activity components) that were found. */ - public abstract int getPreferredActivities(List<IntentFilter> outFilters, - List<ComponentName> outActivities, String packageName); + public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters, + @NonNull List<ComponentName> outActivities, String packageName); /** * Ask for the set of available 'home' activities and the current explicit diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 1140756..212cf6d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -269,6 +269,7 @@ public class PackageParser { public final boolean coreApp; public final boolean multiArch; + public final boolean extractNativeLibs; public PackageLite(String codePath, ApkLite baseApk, String[] splitNames, String[] splitCodePaths, int[] splitRevisionCodes) { @@ -284,6 +285,7 @@ public class PackageParser { this.splitRevisionCodes = splitRevisionCodes; this.coreApp = baseApk.coreApp; this.multiArch = baseApk.multiArch; + this.extractNativeLibs = baseApk.extractNativeLibs; } public List<String> getAllCodePaths() { @@ -310,10 +312,12 @@ public class PackageParser { public final Signature[] signatures; public final boolean coreApp; public final boolean multiArch; + public final boolean extractNativeLibs; public ApkLite(String codePath, String packageName, String splitName, int versionCode, int revisionCode, int installLocation, List<VerifierInfo> verifiers, - Signature[] signatures, boolean coreApp, boolean multiArch) { + Signature[] signatures, boolean coreApp, boolean multiArch, + boolean extractNativeLibs) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; @@ -324,6 +328,7 @@ public class PackageParser { this.signatures = signatures; this.coreApp = coreApp; this.multiArch = multiArch; + this.extractNativeLibs = extractNativeLibs; } } @@ -371,16 +376,6 @@ public class PackageParser { return path.endsWith(".apk"); } - /* - public static PackageInfo generatePackageInfo(PackageParser.Package p, - int gids[], int flags, long firstInstallTime, long lastUpdateTime, - HashSet<String> grantedPermissions) { - PackageUserState state = new PackageUserState(); - return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, - grantedPermissions, state, UserHandle.getCallingUserId()); - } - */ - /** * Generate and return the {@link PackageInfo} for a parsed package. * @@ -389,7 +384,7 @@ public class PackageParser { */ public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, - ArraySet<String> grantedPermissions, PackageUserState state) { + Set<String> grantedPermissions, PackageUserState state) { return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, UserHandle.getCallingUserId()); @@ -410,7 +405,7 @@ public class PackageParser { public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, - ArraySet<String> grantedPermissions, PackageUserState state, int userId) { + Set<String> grantedPermissions, PackageUserState state, int userId) { if (!checkUseInstalledOrHidden(flags, state)) { return null; @@ -569,9 +564,8 @@ public class PackageParser { for (int i=0; i<N; i++) { final String perm = p.requestedPermissions.get(i); pi.requestedPermissions[i] = perm; - if (p.requestedPermissionsRequired.get(i)) { - pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED; - } + // The notion of required permissions is deprecated but for compatibility. + pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED; if (grantedPermissions != null && grantedPermissions.contains(perm)) { pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED; } @@ -1270,6 +1264,7 @@ public class PackageParser { int revisionCode = 0; boolean coreApp = false; boolean multiArch = false; + boolean extractNativeLibs = true; for (int i = 0; i < attrs.getAttributeCount(); i++) { final String attr = attrs.getAttributeName(i); @@ -1308,14 +1303,17 @@ public class PackageParser { final String attr = attrs.getAttributeName(i); if ("multiArch".equals(attr)) { multiArch = attrs.getAttributeBooleanValue(i, false); - break; + } + if ("extractNativeLibs".equals(attr)) { + extractNativeLibs = attrs.getAttributeBooleanValue(i, true); } } } } return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, - revisionCode, installLocation, verifiers, signatures, coreApp, multiArch); + revisionCode, installLocation, verifiers, signatures, coreApp, multiArch, + extractNativeLibs); } /** @@ -1812,7 +1810,6 @@ public class PackageParser { } implicitPerms.append(npi.name); pkg.requestedPermissions.add(npi.name); - pkg.requestedPermissionsRequired.add(Boolean.TRUE); } } if (implicitPerms != null) { @@ -1831,7 +1828,6 @@ public class PackageParser { final String perm = spi.newPerms[in]; if (!pkg.requestedPermissions.contains(perm)) { pkg.requestedPermissions.add(perm); - pkg.requestedPermissionsRequired.add(Boolean.TRUE); } } } @@ -1865,17 +1861,6 @@ public class PackageParser { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES; } - /* - * b/8528162: Ignore the <uses-permission android:required> attribute if - * targetSdkVersion < JELLY_BEAN_MR2. There are lots of apps in the wild - * which are improperly using this attribute, even though it never worked. - */ - if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) { - for (int i = 0; i < pkg.requestedPermissionsRequired.size(); i++) { - pkg.requestedPermissionsRequired.set(i, Boolean.TRUE); - } - } - return pkg; } @@ -1911,11 +1896,6 @@ public class PackageParser { // that may change. String name = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestUsesPermission_name); -/* - boolean required = sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestUsesPermission_required, true); -*/ - boolean required = true; // Optional <uses-permission> not supported int maxSdkVersion = 0; TypedValue val = sa.peekValue( @@ -1933,13 +1913,9 @@ public class PackageParser { int index = pkg.requestedPermissions.indexOf(name); if (index == -1) { pkg.requestedPermissions.add(name.intern()); - pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE); } else { - if (pkg.requestedPermissionsRequired.get(index) != required) { - outError[0] = "conflicting <uses-permission> entries"; - mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; - return false; - } + Slog.w(TAG, "Ignoring duplicate uses-permission: " + name + " in package: " + + pkg.packageName + " at: " + parser.getPositionDescription()); } } } @@ -2519,6 +2495,7 @@ public class PackageParser { owner.baseHardwareAccelerated = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated, owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH); + ai.hardwareAccelerated = owner.baseHardwareAccelerated; if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_hasCode, @@ -2551,6 +2528,12 @@ public class PackageParser { } if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_usesCleartextTraffic, + true)) { + ai.flags |= ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC; + } + + if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_supportsRtl, false /* default is no RTL support*/)) { ai.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL; @@ -2562,6 +2545,12 @@ public class PackageParser { ai.flags |= ApplicationInfo.FLAG_MULTIARCH; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_extractNativeLibs, + true)) { + ai.flags |= ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS; + } + String str; str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestApplication_permission, 0); @@ -3109,8 +3098,7 @@ public class PackageParser { } a.info.resizeable = sa.getBoolean( - R.styleable.AndroidManifestActivity_resizeableActivity, - owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.MNC); + R.styleable.AndroidManifestActivity_resizeableActivity, false); if (a.info.resizeable) { // Fixed screen orientation isn't supported with resizeable activities. a.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -4211,7 +4199,6 @@ public class PackageParser { public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0); public final ArrayList<String> requestedPermissions = new ArrayList<String>(); - public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>(); public ArrayList<String> protectedBroadcasts; diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 841b09d..7d8dff3 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -464,6 +464,33 @@ public class ColorStateList implements Parcelable { return mColors; } + /** + * Returns whether the specified state is referenced in any of the state + * specs contained within this ColorStateList. + * <p> + * Any reference, either positive or negative {ex. ~R.attr.state_enabled}, + * will cause this method to return {@code true}. Wildcards are not counted + * as references. + * + * @param state the state to search for + * @return {@code true} if the state if referenced, {@code false} otherwise + * @hide Use only as directed. For internal use only. + */ + public boolean hasState(int state) { + final int[][] stateSpecs = mStateSpecs; + final int specCount = stateSpecs.length; + for (int specIndex = 0; specIndex < specCount; specIndex++) { + final int[] states = stateSpecs[specIndex]; + final int stateCount = states.length; + for (int stateIndex = 0; stateIndex < stateCount; stateIndex++) { + if (states[stateIndex] == state || states[stateIndex] == ~state) { + return true; + } + } + } + return false; + } + @Override public String toString() { return "ColorStateList{" + diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 5dc9ef9..44018ff 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -111,12 +111,12 @@ public class Resources { // single-threaded, and after that these are immutable. private static final LongSparseArray<ConstantState>[] sPreloadedDrawables; private static final LongSparseArray<ConstantState> sPreloadedColorDrawables - = new LongSparseArray<ConstantState>(); + = new LongSparseArray<>(); private static final LongSparseArray<ColorStateListFactory> sPreloadedColorStateLists - = new LongSparseArray<ColorStateListFactory>(); + = new LongSparseArray<>(); // Pool of TypedArrays targeted to this Resources object. - final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<TypedArray>(5); + final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5); // Used by BridgeResources in layoutlib static Resources mSystem = null; @@ -128,21 +128,19 @@ public class Resources { private final Object mAccessLock = new Object(); private final Configuration mTmpConfig = new Configuration(); private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache = - new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); + new ArrayMap<>(); private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache = - new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); + new ArrayMap<>(); private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache = - new ConfigurationBoundResourceCache<ColorStateList>(this); + new ConfigurationBoundResourceCache<>(this); private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = - new ConfigurationBoundResourceCache<Animator>(this); + new ConfigurationBoundResourceCache<>(this); private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache = - new ConfigurationBoundResourceCache<StateListAnimator>(this); + new ConfigurationBoundResourceCache<>(this); private TypedValue mTmpValue = new TypedValue(); private boolean mPreloading; - private TypedArray mCachedStyledAttributes = null; - private int mLastCachedXmlBlockIndex = -1; private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 }; private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4]; @@ -157,8 +155,8 @@ public class Resources { static { sPreloadedDrawables = new LongSparseArray[2]; - sPreloadedDrawables[0] = new LongSparseArray<ConstantState>(); - sPreloadedDrawables[1] = new LongSparseArray<ConstantState>(); + sPreloadedDrawables[0] = new LongSparseArray<>(); + sPreloadedDrawables[1] = new LongSparseArray<>(); } /** @@ -927,6 +925,7 @@ public class Resources { * @deprecated Use {@link #getColor(int, Theme)} instead. */ @ColorInt + @Deprecated public int getColor(@ColorRes int id) throws NotFoundException { return getColor(id, null); } @@ -996,6 +995,7 @@ public class Resources { * @deprecated Use {@link #getColorStateList(int, Theme)} instead. */ @Nullable + @Deprecated public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException { final ColorStateList csl = getColorStateList(id, null); if (csl != null && csl.canApplyTheme()) { @@ -1049,8 +1049,6 @@ public class Resources { return res; } - - /** * Return a boolean associated with a particular resource ID. This can be * used with any integral resource value, and will return true if it is @@ -1876,7 +1874,7 @@ public class Resources { // the framework. mCompatibilityInfo.applyToDisplayMetrics(mMetrics); - int configChanges = calcConfigChanges(config); + final int configChanges = calcConfigChanges(config); if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); mConfiguration.setLayoutDirection(mConfiguration.locale); @@ -1891,7 +1889,8 @@ public class Resources { if (mConfiguration.locale != null) { locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag()); } - int width, height; + + final int width, height; if (mMetrics.widthPixels >= mMetrics.heightPixels) { width = mMetrics.widthPixels; height = mMetrics.heightPixels; @@ -1901,12 +1900,15 @@ public class Resources { //noinspection SuspiciousNameCombination height = mMetrics.widthPixels; } - int keyboardHidden = mConfiguration.keyboardHidden; - if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO - && mConfiguration.hardKeyboardHidden - == Configuration.HARDKEYBOARDHIDDEN_YES) { + + final int keyboardHidden; + if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO + && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; + } else { + keyboardHidden = mConfiguration.keyboardHidden; } + mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, locale, mConfiguration.orientation, mConfiguration.touchscreen, @@ -2508,10 +2510,10 @@ public class Resources { // Clean out the caches before we add more. This shouldn't // happen very often. pruneCaches(caches); - themedCache = new LongSparseArray<WeakReference<ConstantState>>(1); + themedCache = new LongSparseArray<>(1); caches.put(themeKey, themedCache); } - themedCache.put(key, new WeakReference<ConstantState>(cs)); + themedCache.put(key, new WeakReference<>(cs)); } } } @@ -2830,15 +2832,6 @@ public class Resources { + Integer.toHexString(id)); } - /*package*/ void recycleCachedStyledAttributes(TypedArray attrs) { - synchronized (mAccessLock) { - final TypedArray cached = mCachedStyledAttributes; - if (cached == null || cached.mData.length < attrs.mData.length) { - mCachedStyledAttributes = attrs; - } - } - } - /** * Obtains styled attributes from the theme, if available, or unstyled * resources if the theme is null. diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java index 9652db7..5cfc41f 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -328,7 +328,7 @@ final class StringBlock { String name = color.substring(1); int colorRes = res.getIdentifier(name, "color", "android"); if (colorRes != 0) { - ColorStateList colors = res.getColorStateList(colorRes); + ColorStateList colors = res.getColorStateList(colorRes, null); if (foreground) { return new TextAppearanceSpan(null, 0, 0, colors, null); } else { diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl index 9aede01..d5dfaf6 100644 --- a/core/java/android/hardware/ICameraService.aidl +++ b/core/java/android/hardware/ICameraService.aidl @@ -18,8 +18,6 @@ package android.hardware; import android.hardware.ICamera; import android.hardware.ICameraClient; -import android.hardware.IProCameraUser; -import android.hardware.IProCameraCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.impl.CameraMetadataNative; @@ -45,12 +43,6 @@ interface ICameraService // Container for an ICamera object out BinderHolder device); - int connectPro(IProCameraCallbacks callbacks, int cameraId, - String clientPackageName, - int clientUid, - // Container for an IProCameraUser object - out BinderHolder device); - int connectDevice(ICameraDeviceCallbacks callbacks, int cameraId, String clientPackageName, int clientUid, diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index a6c3ea4..88fa339 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -54,11 +54,13 @@ public class SystemSensorManager extends SensorManager { // Looper associated with the context in which this instance was created. private final Looper mMainLooper; private final int mTargetSdkLevel; + private final String mPackageName; /** {@hide} */ public SystemSensorManager(Context context, Looper mainLooper) { mMainLooper = mainLooper; mTargetSdkLevel = context.getApplicationInfo().targetSdkVersion; + mPackageName = context.getPackageName(); synchronized(sSensorModuleLock) { if (!sSensorModuleInitialized) { sSensorModuleInitialized = true; @@ -117,14 +119,14 @@ public class SystemSensorManager extends SensorManager { if (queue == null) { Looper looper = (handler != null) ? handler.getLooper() : mMainLooper; queue = new SensorEventQueue(listener, looper, this); - if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags)) { + if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) { queue.dispose(); return false; } mSensorListeners.put(listener, queue); return true; } else { - return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags); + return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs); } } } @@ -165,14 +167,14 @@ public class SystemSensorManager extends SensorManager { TriggerEventQueue queue = mTriggerListeners.get(listener); if (queue == null) { queue = new TriggerEventQueue(listener, mMainLooper, this); - if (!queue.addSensor(sensor, 0, 0, 0)) { + if (!queue.addSensor(sensor, 0, 0)) { queue.dispose(); return false; } mTriggerListeners.put(listener, queue); return true; } else { - return queue.addSensor(sensor, 0, 0, 0); + return queue.addSensor(sensor, 0, 0); } } } @@ -223,9 +225,9 @@ public class SystemSensorManager extends SensorManager { */ private static abstract class BaseEventQueue { private native long nativeInitBaseEventQueue(BaseEventQueue eventQ, MessageQueue msgQ, - float[] scratch); + float[] scratch, String packageName); private static native int nativeEnableSensor(long eventQ, int handle, int rateUs, - int maxBatchReportLatencyUs, int reservedFlags); + int maxBatchReportLatencyUs); private static native int nativeDisableSensor(long eventQ, int handle); private static native void nativeDestroySensorEventQueue(long eventQ); private static native int nativeFlushSensor(long eventQ); @@ -238,7 +240,8 @@ public class SystemSensorManager extends SensorManager { protected final SystemSensorManager mManager; BaseEventQueue(Looper looper, SystemSensorManager manager) { - nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch); + nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch, + manager.mPackageName); mCloseGuard.open("dispose"); mManager = manager; } @@ -248,7 +251,7 @@ public class SystemSensorManager extends SensorManager { } public boolean addSensor( - Sensor sensor, int delayUs, int maxBatchReportLatencyUs, int reservedFlags) { + Sensor sensor, int delayUs, int maxBatchReportLatencyUs) { // Check if already present. int handle = sensor.getHandle(); if (mActiveSensors.get(handle)) return false; @@ -256,10 +259,10 @@ public class SystemSensorManager extends SensorManager { // Get ready to receive events before calling enable. mActiveSensors.put(handle, true); addSensorEvent(sensor); - if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags) != 0) { + if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) { // Try continuous mode if batching fails. if (maxBatchReportLatencyUs == 0 || - maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0, 0) != 0) { + maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) { removeSensor(sensor, false); return false; } @@ -328,11 +331,11 @@ public class SystemSensorManager extends SensorManager { } private int enableSensor( - Sensor sensor, int rateUs, int maxBatchReportLatencyUs, int reservedFlags) { + Sensor sensor, int rateUs, int maxBatchReportLatencyUs) { if (nSensorEventQueue == 0) throw new NullPointerException(); if (sensor == null) throw new NullPointerException(); return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs, - maxBatchReportLatencyUs, reservedFlags); + maxBatchReportLatencyUs); } private int disableSensor(Sensor sensor) { diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index faa782a..b513379 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -1013,10 +1013,10 @@ public final class CameraManager { if (DEBUG) { Log.v(TAG, String.format( - "Device status was previously available (%d), " + - " and is now again available (%d)" + + "Device status was previously available (%b), " + + " and is now again available (%b)" + "so no new client visible update will be sent", - isAvailable(status), isAvailable(status))); + isAvailable(oldStatus), isAvailable(status))); } return; } diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl index d286d38..01f2396 100644 --- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl +++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl @@ -16,7 +16,7 @@ package android.hardware.camera2; -import android.view.Surface; +import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.CaptureRequest; @@ -66,7 +66,7 @@ interface ICameraDeviceUser int deleteStream(int streamId); // non-negative value is the stream ID. negative value is status_t - int createStream(in Surface surface); + int createStream(in OutputConfiguration outputConfiguration); int createDefaultRequest(int templateId, out CameraMetadataNative request); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index c5267cb..38f8e39 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -79,7 +79,8 @@ public class CameraDeviceImpl extends CameraDevice { private int mRepeatingRequestId = REQUEST_ID_NONE; private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>(); // Map stream IDs to Surfaces - private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>(); + private final SparseArray<OutputConfiguration> mConfiguredOutputs = + new SparseArray<OutputConfiguration>(); private final String mCameraId; private final CameraCharacteristics mCharacteristics; @@ -315,7 +316,11 @@ public class CameraDeviceImpl extends CameraDevice { public void configureOutputs(List<Surface> outputs) throws CameraAccessException { // Leave this here for backwards compatibility with older code using this directly - configureOutputsChecked(outputs); + ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>(outputs.size()); + for (Surface s : outputs) { + outputConfigs.add(new OutputConfiguration(s)); + } + configureOutputsChecked(outputConfigs); } /** @@ -334,28 +339,30 @@ public class CameraDeviceImpl extends CameraDevice { * * @throws CameraAccessException if there were any unexpected problems during configuration */ - public boolean configureOutputsChecked(List<Surface> outputs) throws CameraAccessException { + public boolean configureOutputsChecked(List<OutputConfiguration> outputs) + throws CameraAccessException { // Treat a null input the same an empty list if (outputs == null) { - outputs = new ArrayList<Surface>(); + outputs = new ArrayList<OutputConfiguration>(); } boolean success = false; synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); - - HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create - List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete + // Streams to create + HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs); + // Streams to delete + List<Integer> deleteList = new ArrayList<Integer>(); // Determine which streams need to be created, which to be deleted for (int i = 0; i < mConfiguredOutputs.size(); ++i) { int streamId = mConfiguredOutputs.keyAt(i); - Surface s = mConfiguredOutputs.valueAt(i); + OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i); - if (!outputs.contains(s)) { + if (!outputs.contains(outConfig)) { deleteList.add(streamId); } else { - addSet.remove(s); // Don't create a stream previously created + addSet.remove(outConfig); // Don't create a stream previously created } } @@ -373,9 +380,11 @@ public class CameraDeviceImpl extends CameraDevice { } // Add all new streams - for (Surface s : addSet) { - int streamId = mRemoteDevice.createStream(s); - mConfiguredOutputs.put(streamId, s); + for (OutputConfiguration outConfig : outputs) { + if (addSet.contains(outConfig)) { + int streamId = mRemoteDevice.createStream(outConfig); + mConfiguredOutputs.put(streamId, outConfig); + } } try { @@ -444,12 +453,9 @@ public class CameraDeviceImpl extends CameraDevice { // TODO: dont block for this boolean configureSuccess = true; CameraAccessException pendingException = null; - List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size()); - for (OutputConfiguration config : outputConfigurations) { - outSurfaces.add(config.getSurface()); - } try { - configureSuccess = configureOutputsChecked(outSurfaces); // and then block until IDLE + // configure outputs and then block until IDLE + configureSuccess = configureOutputsChecked(outputConfigurations); } catch (CameraAccessException e) { configureSuccess = false; pendingException = e; @@ -458,6 +464,10 @@ public class CameraDeviceImpl extends CameraDevice { } } + List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size()); + for (OutputConfiguration config : outputConfigurations) { + outSurfaces.add(config.getSurface()); + } // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. CameraCaptureSessionImpl newSession = new CameraCaptureSessionImpl(mNextSessionId++, diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 26cd498..70f3463 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -26,6 +26,7 @@ import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.utils.LongParcelable; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.os.ConditionVariable; @@ -504,7 +505,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override - public int createStream(Surface surface) { + public int createStream(OutputConfiguration outputConfiguration) { if (DEBUG) { Log.d(TAG, "createStream called."); } @@ -518,8 +519,12 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { Log.e(TAG, "Cannot create stream, beginConfigure hasn't been called yet."); return CameraBinderDecorator.INVALID_OPERATION; } + if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) { + Log.e(TAG, "Cannot create stream, stream rotation is not supported."); + return CameraBinderDecorator.INVALID_OPERATION; + } int id = ++mSurfaceIdCounter; - mSurfaces.put(id, surface); + mSurfaces.put(id, outputConfiguration.getSurface()); return id; } } diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index 367a078..b5a019d 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -292,6 +292,10 @@ public class LegacyCameraDevice implements AutoCloseable { Log.e(TAG, "configureOutputs - null outputs are not allowed"); return BAD_VALUE; } + if (!output.isValid()) { + Log.e(TAG, "configureOutputs - invalid output surfaces are not allowed"); + return BAD_VALUE; + } StreamConfigurationMap streamConfigurations = mStaticCharacteristics. get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); @@ -522,7 +526,7 @@ public class LegacyCameraDevice implements AutoCloseable { * @return the width and height of the surface * * @throws NullPointerException if the {@code surface} was {@code null} - * @throws IllegalStateException if the {@code surface} was invalid + * @throws BufferQueueAbandonedException if the {@code surface} was invalid */ public static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException { checkNotNull(surface); diff --git a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java index 7e0c01b..4b7cfbf 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java +++ b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java @@ -60,7 +60,7 @@ public class LegacyExceptionUtils { case CameraBinderDecorator.NO_ERROR: { return CameraBinderDecorator.NO_ERROR; } - case CameraBinderDecorator.ENODEV: { + case CameraBinderDecorator.BAD_VALUE: { throw new BufferQueueAbandonedException(); } } diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index ff74c59..691798f 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -498,6 +498,10 @@ public class RequestThreadManager { return; } for(Surface s : surfaces) { + if (s == null || !s.isValid()) { + Log.w(TAG, "Jpeg surface is invalid, skipping..."); + continue; + } try { LegacyCameraDevice.setSurfaceFormat(s, LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB); } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) { diff --git a/core/java/android/hardware/IProCameraCallbacks.aidl b/core/java/android/hardware/camera2/params/OutputConfiguration.aidl index a09b452..0921cd8 100644 --- a/core/java/android/hardware/IProCameraCallbacks.aidl +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,7 @@ * limitations under the License. */ -package android.hardware; +package android.hardware.camera2.params; /** @hide */ -interface IProCameraCallbacks -{ - /** - * Keep up-to-date with frameworks/av/include/camera/IProCameraCallbacks.h - */ - // TODO: consider implementing this. -} +parcelable OutputConfiguration; diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 47c784e..0a4ed39 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -18,19 +18,22 @@ package android.hardware.camera2.params; import android.hardware.camera2.CameraDevice; +import android.util.Log; import android.view.Surface; +import android.os.Parcel; +import android.os.Parcelable; import static com.android.internal.util.Preconditions.*; /** - * Immutable class for describing camera output, which contains a {@link Surface} and its specific + * A class for describing camera output, which contains a {@link Surface} and its specific * configuration for creating capture session. * * @see CameraDevice#createCaptureSession * * @hide */ -public final class OutputConfiguration { +public final class OutputConfiguration implements Parcelable { /** * Rotation constant: 0 degree rotation (no rotation) @@ -93,6 +96,18 @@ public final class OutputConfiguration { } /** + * Create an OutputConfiguration from Parcel. + */ + private OutputConfiguration(Parcel source) { + int rotation = source.readInt(); + Surface surface = Surface.CREATOR.createFromParcel(source); + checkNotNull(surface, "Surface must not be null"); + checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); + mSurface = surface; + mRotation = rotation; + } + + /** * Get the {@link Surface} associated with this {@link OutputConfiguration}. * * @return the {@link Surface} associated with this {@link OutputConfiguration}. @@ -111,6 +126,40 @@ public final class OutputConfiguration { return mRotation; } + public static final Parcelable.Creator<OutputConfiguration> CREATOR = + new Parcelable.Creator<OutputConfiguration>() { + @Override + public OutputConfiguration createFromParcel(Parcel source) { + try { + OutputConfiguration outputConfiguration = new OutputConfiguration(source); + return outputConfiguration; + } catch (Exception e) { + Log.e(TAG, "Exception creating OutputConfiguration from parcel", e); + return null; + } + } + + @Override + public OutputConfiguration[] newArray(int size) { + return new OutputConfiguration[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (dest == null) { + throw new IllegalArgumentException("dest must not be null"); + } + dest.writeInt(mRotation); + mSurface.writeToParcel(dest, flags); + } + + private static final String TAG = "OutputConfiguration"; private final Surface mSurface; private final int mRotation; } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index b077e06..12e1963 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -472,7 +472,8 @@ public final class DisplayManager { /** * Creates a virtual display. * - * @see #createVirtualDisplay(String, int, int, int, Surface, int, VirtualDisplay.Callback) + * @see #createVirtualDisplay(String, int, int, int, Surface, int, + * VirtualDisplay.Callback, Handler) */ public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface, int flags) { diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java index 263d6b1..874b0c6 100644 --- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java +++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java @@ -32,6 +32,9 @@ import android.util.Log; public final class HdmiPlaybackClient extends HdmiClient { private static final String TAG = "HdmiPlaybackClient"; + // Logical address of TV. The secondary TV is not handled. + private static final int ADDR_TV = 0; + /** * Listener used by the client to get the result of one touch play operation. */ @@ -103,6 +106,17 @@ public final class HdmiPlaybackClient extends HdmiClient { } } + /** + * Sends a <Standby> command to TV. + */ + public void sendStandby() { + try { + mService.sendStandby(getDeviceType(), HdmiDeviceInfo.idForCecDevice(ADDR_TV)); + } catch (RemoteException e) { + Log.e(TAG, "sendStandby threw exception ", e); + } + } + private IHdmiControlCallback getCallbackWrapper(final OneTouchPlayCallback callback) { return new IHdmiControlCallback.Stub() { @Override diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java index cef17dd..a94c1da 100644 --- a/core/java/android/hardware/hdmi/HdmiTvClient.java +++ b/core/java/android/hardware/hdmi/HdmiTvClient.java @@ -211,6 +211,19 @@ public final class HdmiTvClient extends HdmiClient { } } + /** + * Sends a <Standby> command to other device. + * + * @param deviceId device id to send the command to + */ + public void sendStandby(int deviceId) { + try { + mService.sendStandby(getDeviceType(), deviceId); + } catch (RemoteException e) { + Log.e(TAG, "sendStandby threw exception ", e); + } + } + private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) { return new IHdmiRecordListener.Stub() { @Override diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java index 6e5d064..4696b2a 100644 --- a/core/java/android/hardware/location/GeofenceHardwareImpl.java +++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java @@ -436,7 +436,7 @@ public final class GeofenceHardwareImpl { int monitoringType, int sourcesUsed) { if(location == null) { - Log.e(TAG, String.format("Invalid Geofence Transition: location=%p", location)); + Log.e(TAG, String.format("Invalid Geofence Transition: location=null")); return; } if(DEBUG) { diff --git a/core/java/android/net/BaseDhcpStateMachine.java b/core/java/android/net/BaseDhcpStateMachine.java new file mode 100644 index 0000000..a25847d --- /dev/null +++ b/core/java/android/net/BaseDhcpStateMachine.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import com.android.internal.util.StateMachine; + +/** + * Interface that must be implemented by DHCP state machines. + * + * This is an abstract class instead of a Java interface so that callers can just declare an object + * of this type and be able to call all the methods defined by either StateMachine or this class. + * + * @hide + */ +public abstract class BaseDhcpStateMachine extends StateMachine { + protected BaseDhcpStateMachine(String tag) { + super(tag); + } + public abstract void registerForPreDhcpNotification(); + public abstract void doQuit(); +} diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 34a0727..a0e2bf8 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2337,7 +2337,7 @@ public class ConnectivityManager { * successfully finding a network for the applications request. Retrieve it with * {@link android.content.Intent#getParcelableExtra(String)}. * <p> - * Note that if you intend to invoke (@link #setProcessDefaultNetwork(Network)) or + * Note that if you intend to invoke {@link #setProcessDefaultNetwork} or * {@link Network#openConnection(java.net.URL)} then you must get a * ConnectivityManager instance before doing so. */ diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index 5151a04..1b8adc8 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -47,7 +47,7 @@ import android.util.Log; * * @hide */ -public class DhcpStateMachine extends StateMachine { +public class DhcpStateMachine extends BaseDhcpStateMachine { private static final String TAG = "DhcpStateMachine"; private static final boolean DBG = false; @@ -161,6 +161,7 @@ public class DhcpStateMachine extends StateMachine { * This is used by Wifi at this time for the purpose of doing BT-Wifi coex * handling during Dhcp */ + @Override public void registerForPreDhcpNotification() { mRegisteredForPreDhcpNotification = true; } @@ -170,6 +171,7 @@ public class DhcpStateMachine extends StateMachine { * * @hide */ + @Override public void doQuit() { quit(); } diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 1129c9e..7e92de2 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -54,7 +54,8 @@ interface INetworkPolicyManager { void setRestrictBackground(boolean restrictBackground); boolean getRestrictBackground(); + void setDeviceIdleMode(boolean enabled); + NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state); boolean isNetworkMetered(in NetworkState state); - } diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 2c3881c..6436e42 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -27,6 +27,14 @@ interface INetworkStatsService { /** Start a statistics query session. */ INetworkStatsSession openSession(); + /** Start a statistics query session. If calling package is profile or device owner then it is + * granted automatic access if apiLevel is NetworkStatsManager.API_LEVEL_DPC_ALLOWED. If + * apiLevel is at least NetworkStatsManager.API_LEVEL_REQUIRES_PACKAGE_USAGE_STATS then + * PACKAGE_USAGE_STATS permission is always checked. If PACKAGE_USAGE_STATS is not granted + * READ_NETWORK_USAGE_STATS is checked for. + */ + INetworkStatsSession openSessionForUsageStats(String callingPackage); + /** Return network layer usage total for traffic that matches template. */ long getNetworkTotalBytes(in NetworkTemplate template, long start, long end); diff --git a/core/java/android/net/INetworkStatsSession.aidl b/core/java/android/net/INetworkStatsSession.aidl index 1596fa2..7bcb043 100644 --- a/core/java/android/net/INetworkStatsSession.aidl +++ b/core/java/android/net/INetworkStatsSession.aidl @@ -23,6 +23,9 @@ import android.net.NetworkTemplate; /** {@hide} */ interface INetworkStatsSession { + /** Return device aggregated network layer usage summary for traffic that matches template. */ + NetworkStats getDeviceSummaryForNetwork(in NetworkTemplate template, long start, long end); + /** Return network layer usage summary for traffic that matches template. */ NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end); /** Return historical network layer stats for traffic that matches template. */ @@ -33,6 +36,9 @@ interface INetworkStatsSession { /** Return historical network layer stats for specific UID traffic that matches template. */ NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields); + /** Return array of uids that have stats and are accessible to the calling user */ + int[] getRelevantUids(); + void close(); } diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index a7f9c5b..8c8bfab 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -148,9 +148,9 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int NET_CAPABILITY_TRUSTED = 14; - /* + /** * Indicates that this network is not a VPN. This capability is set by default and should be - * explicitly cleared when creating VPN networks. + * explicitly cleared for VPN networks. */ public static final int NET_CAPABILITY_NOT_VPN = 15; diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 8003afb..02fbe73 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -16,9 +16,11 @@ package android.net; +import java.io.FileDescriptor; import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.SocketException; import java.net.UnknownHostException; import java.util.Collection; import java.util.Locale; @@ -139,6 +141,11 @@ public class NetworkUtils { public native static String getDhcpError(); /** + * Attaches a socket filter that accepts DHCP packets to the given socket. + */ + public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException; + + /** * Binds the current process to the network designated by {@code netId}. All sockets created * in the future (and not explicitly bound via a bound {@link SocketFactory} (see * {@link Network#getSocketFactory}) will be bound to this network. Note that if this @@ -171,6 +178,15 @@ public class NetworkUtils { public native static int bindSocketToNetwork(int socketfd, int netId); /** + * Protect {@code fd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + public static boolean protectFromVpn(FileDescriptor fd) { + return protectFromVpn(fd.getInt$()); + } + + /** * Protect {@code socketfd} from VPN connections. After protecting, data sent through * this socket will go directly to the underlying network, so its traffic will not be * forwarded through the VPN. @@ -230,6 +246,25 @@ public class NetworkUtils { } /** + * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous. + * @param netmask as a {@code Inet4Address}. + * @return the network prefix length + * @throws IllegalArgumentException the specified netmask was not contiguous. + * @hide + */ + public static int netmaskToPrefixLength(Inet4Address netmask) { + // inetAddressToInt returns an int in *network* byte order. + int i = Integer.reverseBytes(inetAddressToInt(netmask)); + int prefixLength = Integer.bitCount(i); + int trailingZeros = Integer.numberOfTrailingZeros(i); + if (trailingZeros != 32 - prefixLength) { + throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i)); + } + return prefixLength; + } + + + /** * Create an InetAddress from a string where the string must be a standard * representation of a V4 or V6 address. Avoids doing a DNS lookup on failure * but it will throw an IllegalArgumentException in that case. @@ -309,6 +344,22 @@ public class NetworkUtils { } /** + * Returns the implicit netmask of an IPv4 address, as was the custom before 1993. + */ + public static int getImplicitNetmask(Inet4Address address) { + int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value. + if (firstByte < 128) { + return 8; + } else if (firstByte < 192) { + return 16; + } else if (firstByte < 224) { + return 24; + } else { + return 32; // Will likely not end well for other reasons. + } + } + + /** * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64". * @hide */ diff --git a/core/java/android/net/dhcp/DhcpAckPacket.java b/core/java/android/net/dhcp/DhcpAckPacket.java deleted file mode 100644 index 7b8be9c..0000000 --- a/core/java/android/net/dhcp/DhcpAckPacket.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import java.net.InetAddress; -import java.net.Inet4Address; -import java.nio.ByteBuffer; - -/** - * This class implements the DHCP-ACK packet. - */ -class DhcpAckPacket extends DhcpPacket { - - /** - * The address of the server which sent this packet. - */ - private final InetAddress mSrcIp; - - DhcpAckPacket(int transId, boolean broadcast, InetAddress serverAddress, - InetAddress clientIp, byte[] clientMac) { - super(transId, Inet4Address.ANY, clientIp, serverAddress, - Inet4Address.ANY, clientMac, broadcast); - mBroadcast = broadcast; - mSrcIp = serverAddress; - } - - public String toString() { - String s = super.toString(); - String dnsServers = " DNS servers: "; - - for (InetAddress dnsServer: mDnsServers) { - dnsServers += dnsServer.toString() + " "; - } - - return s + " ACK: your new IP " + mYourIp + - ", netmask " + mSubnetMask + - ", gateway " + mGateway + dnsServers + - ", lease time " + mLeaseTime; - } - - /** - * Fills in a packet with the requested ACK parameters. - */ - public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { - ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - InetAddress destIp = mBroadcast ? Inet4Address.ALL : mYourIp; - InetAddress srcIp = mBroadcast ? Inet4Address.ANY : mSrcIp; - - fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, - DHCP_BOOTREPLY, mBroadcast); - result.flip(); - return result; - } - - /** - * Adds the optional parameters to the client-generated ACK packet. - */ - void finishPacket(ByteBuffer buffer) { - addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_ACK); - addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); - addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime); - - // the client should renew at 1/2 the lease-expiry interval - if (mLeaseTime != null) { - addTlv(buffer, DHCP_RENEWAL_TIME, - Integer.valueOf(mLeaseTime.intValue() / 2)); - } - - addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask); - addTlv(buffer, DHCP_ROUTER, mGateway); - addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName); - addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress); - addTlv(buffer, DHCP_DNS_SERVER, mDnsServers); - addTlvEnd(buffer); - } - - /** - * Un-boxes an Integer, returning 0 if a null reference is supplied. - */ - private static final int getInt(Integer v) { - if (v == null) { - return 0; - } else { - return v.intValue(); - } - } - - /** - * Notifies the specified state machine of the ACK packet parameters. - */ - public void doNextOp(DhcpStateMachine machine) { - machine.onAckReceived(mYourIp, mSubnetMask, mGateway, mDnsServers, - mServerIdentifier, getInt(mLeaseTime)); - } -} diff --git a/core/java/android/net/dhcp/DhcpDeclinePacket.java b/core/java/android/net/dhcp/DhcpDeclinePacket.java deleted file mode 100644 index 7646eb4..0000000 --- a/core/java/android/net/dhcp/DhcpDeclinePacket.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import java.net.InetAddress; -import java.nio.ByteBuffer; - -/** - * This class implements the DHCP-DECLINE packet. - */ -class DhcpDeclinePacket extends DhcpPacket { - /** - * Generates a DECLINE packet with the specified parameters. - */ - DhcpDeclinePacket(int transId, InetAddress clientIp, InetAddress yourIp, - InetAddress nextIp, InetAddress relayIp, - byte[] clientMac) { - super(transId, clientIp, yourIp, nextIp, relayIp, clientMac, false); - } - - public String toString() { - String s = super.toString(); - return s + " DECLINE"; - } - - /** - * Fills in a packet with the requested DECLINE attributes. - */ - public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { - ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - - fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result, - DHCP_BOOTREQUEST, false); - result.flip(); - return result; - } - - /** - * Adds optional parameters to the DECLINE packet. - */ - void finishPacket(ByteBuffer buffer) { - // None needed - } - - /** - * Informs the state machine of the arrival of a DECLINE packet. - */ - public void doNextOp(DhcpStateMachine machine) { - machine.onDeclineReceived(mClientMac, mRequestedIp); - } -} diff --git a/core/java/android/net/dhcp/DhcpDiscoverPacket.java b/core/java/android/net/dhcp/DhcpDiscoverPacket.java deleted file mode 100644 index 0e2d39b..0000000 --- a/core/java/android/net/dhcp/DhcpDiscoverPacket.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import java.net.InetAddress; -import java.net.Inet4Address; -import java.nio.ByteBuffer; - -/** - * This class implements the DHCP-DISCOVER packet. - */ -class DhcpDiscoverPacket extends DhcpPacket { - /** - * Generates a DISCOVER packet with the specified parameters. - */ - DhcpDiscoverPacket(int transId, byte[] clientMac, boolean broadcast) { - super(transId, Inet4Address.ANY, Inet4Address.ANY, Inet4Address.ANY, - Inet4Address.ANY, clientMac, broadcast); - } - - public String toString() { - String s = super.toString(); - return s + " DISCOVER " + - (mBroadcast ? "broadcast " : "unicast "); - } - - /** - * Fills in a packet with the requested DISCOVER parameters. - */ - public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { - ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - InetAddress destIp = Inet4Address.ALL; - - fillInPacket(encap, Inet4Address.ALL, Inet4Address.ANY, destUdp, srcUdp, - result, DHCP_BOOTREQUEST, true); - result.flip(); - return result; - } - - /** - * Adds optional parameters to a DISCOVER packet. - */ - void finishPacket(ByteBuffer buffer) { - addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DISCOVER); - addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); - addTlvEnd(buffer); - } - - /** - * Informs the state machine of the arrival of a DISCOVER packet. - */ - public void doNextOp(DhcpStateMachine machine) { - // currently omitted: host name - machine.onDiscoverReceived(mBroadcast, mTransId, mClientMac, - mRequestedParams); - } -} diff --git a/core/java/android/net/dhcp/DhcpInformPacket.java b/core/java/android/net/dhcp/DhcpInformPacket.java deleted file mode 100644 index da73216..0000000 --- a/core/java/android/net/dhcp/DhcpInformPacket.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import java.net.InetAddress; -import java.nio.ByteBuffer; - -/** - * This class implements the (unused) DHCP-INFORM packet. - */ -class DhcpInformPacket extends DhcpPacket { - /** - * Generates an INFORM packet with the specified parameters. - */ - DhcpInformPacket(int transId, InetAddress clientIp, InetAddress yourIp, - InetAddress nextIp, InetAddress relayIp, - byte[] clientMac) { - super(transId, clientIp, yourIp, nextIp, relayIp, clientMac, false); - } - - public String toString() { - String s = super.toString(); - return s + " INFORM"; - } - - /** - * Builds an INFORM packet. - */ - public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { - ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - - fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result, - DHCP_BOOTREQUEST, false); - result.flip(); - return result; - } - - /** - * Adds additional parameters to the INFORM packet. - */ - void finishPacket(ByteBuffer buffer) { - byte[] clientId = new byte[7]; - - clientId[0] = CLIENT_ID_ETHER; - System.arraycopy(mClientMac, 0, clientId, 1, 6); - - addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST); - addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); - addTlvEnd(buffer); - } - - /** - * Informs the state machine of the arrival of an INFORM packet. Not - * used currently. - */ - public void doNextOp(DhcpStateMachine machine) { - InetAddress clientRequest = - mRequestedIp == null ? mClientIp : mRequestedIp; - machine.onInformReceived(mTransId, mClientMac, clientRequest, - mRequestedParams); - } -} diff --git a/core/java/android/net/dhcp/DhcpNakPacket.java b/core/java/android/net/dhcp/DhcpNakPacket.java deleted file mode 100644 index 1f340ad..0000000 --- a/core/java/android/net/dhcp/DhcpNakPacket.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import java.net.InetAddress; -import java.net.Inet4Address; -import java.nio.ByteBuffer; - -/** - * This class implements the DHCP-NAK packet. - */ -class DhcpNakPacket extends DhcpPacket { - /** - * Generates a NAK packet with the specified parameters. - */ - DhcpNakPacket(int transId, InetAddress clientIp, InetAddress yourIp, - InetAddress nextIp, InetAddress relayIp, - byte[] clientMac) { - super(transId, Inet4Address.ANY, Inet4Address.ANY, nextIp, relayIp, - clientMac, false); - } - - public String toString() { - String s = super.toString(); - return s + " NAK, reason " + (mMessage == null ? "(none)" : mMessage); - } - - /** - * Fills in a packet with the requested NAK attributes. - */ - public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { - ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - InetAddress destIp = mClientIp; - InetAddress srcIp = mYourIp; - - fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, - DHCP_BOOTREPLY, mBroadcast); - result.flip(); - return result; - } - - /** - * Adds the optional parameters to the client-generated NAK packet. - */ - void finishPacket(ByteBuffer buffer) { - addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_NAK); - addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); - addTlv(buffer, DHCP_MESSAGE, mMessage); - addTlvEnd(buffer); - } - - /** - * Notifies the specified state machine of the newly-arrived NAK packet. - */ - public void doNextOp(DhcpStateMachine machine) { - machine.onNakReceived(); - } -} diff --git a/core/java/android/net/dhcp/DhcpOfferPacket.java b/core/java/android/net/dhcp/DhcpOfferPacket.java deleted file mode 100644 index f1c30e1..0000000 --- a/core/java/android/net/dhcp/DhcpOfferPacket.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import java.net.InetAddress; -import java.net.Inet4Address; -import java.nio.ByteBuffer; - -/** - * This class implements the DHCP-OFFER packet. - */ -class DhcpOfferPacket extends DhcpPacket { - /** - * The IP address of the server which sent this packet. - */ - private final InetAddress mSrcIp; - - /** - * Generates a OFFER packet with the specified parameters. - */ - DhcpOfferPacket(int transId, boolean broadcast, InetAddress serverAddress, - InetAddress clientIp, byte[] clientMac) { - super(transId, Inet4Address.ANY, clientIp, Inet4Address.ANY, - Inet4Address.ANY, clientMac, broadcast); - mSrcIp = serverAddress; - } - - public String toString() { - String s = super.toString(); - String dnsServers = ", DNS servers: "; - - if (mDnsServers != null) { - for (InetAddress dnsServer: mDnsServers) { - dnsServers += dnsServer + " "; - } - } - - return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask + - dnsServers + ", gateway " + mGateway + - " lease time " + mLeaseTime + ", domain " + mDomainName; - } - - /** - * Fills in a packet with the specified OFFER attributes. - */ - public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { - ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - InetAddress destIp = mBroadcast ? Inet4Address.ALL : mYourIp; - InetAddress srcIp = mBroadcast ? Inet4Address.ANY : mSrcIp; - - fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, - DHCP_BOOTREPLY, mBroadcast); - result.flip(); - return result; - } - - /** - * Adds the optional parameters to the server-generated OFFER packet. - */ - void finishPacket(ByteBuffer buffer) { - addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_OFFER); - addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); - addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime); - - // the client should renew at 1/2 the lease-expiry interval - if (mLeaseTime != null) { - addTlv(buffer, DHCP_RENEWAL_TIME, - Integer.valueOf(mLeaseTime.intValue() / 2)); - } - - addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask); - addTlv(buffer, DHCP_ROUTER, mGateway); - addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName); - addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress); - addTlv(buffer, DHCP_DNS_SERVER, mDnsServers); - addTlvEnd(buffer); - } - - /** - * Notifies the state machine of the OFFER packet parameters. - */ - public void doNextOp(DhcpStateMachine machine) { - machine.onOfferReceived(mBroadcast, mTransId, mClientMac, mYourIp, - mServerIdentifier); - } -} diff --git a/core/java/android/net/dhcp/DhcpPacket.java b/core/java/android/net/dhcp/DhcpPacket.java deleted file mode 100644 index c7c25f0..0000000 --- a/core/java/android/net/dhcp/DhcpPacket.java +++ /dev/null @@ -1,895 +0,0 @@ -package android.net.dhcp; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.nio.ShortBuffer; - -import java.util.ArrayList; -import java.util.List; - -/** - * Defines basic data and operations needed to build and use packets for the - * DHCP protocol. Subclasses create the specific packets used at each - * stage of the negotiation. - */ -abstract class DhcpPacket { - protected static final String TAG = "DhcpPacket"; - - /** - * Packet encapsulations. - */ - public static final int ENCAP_L2 = 0; // EthernetII header included - public static final int ENCAP_L3 = 1; // IP/UDP header included - public static final int ENCAP_BOOTP = 2; // BOOTP contents only - - /** - * IP layer definitions. - */ - private static final byte IP_TYPE_UDP = (byte) 0x11; - - /** - * IP: Version 4, Header Length 20 bytes - */ - private static final byte IP_VERSION_HEADER_LEN = (byte) 0x45; - - /** - * IP: Flags 0, Fragment Offset 0, Don't Fragment - */ - private static final short IP_FLAGS_OFFSET = (short) 0x4000; - - /** - * IP: TOS - */ - private static final byte IP_TOS_LOWDELAY = (byte) 0x10; - - /** - * IP: TTL -- use default 64 from RFC1340 - */ - private static final byte IP_TTL = (byte) 0x40; - - /** - * The client DHCP port. - */ - static final short DHCP_CLIENT = (short) 68; - - /** - * The server DHCP port. - */ - static final short DHCP_SERVER = (short) 67; - - /** - * The message op code indicating a request from a client. - */ - protected static final byte DHCP_BOOTREQUEST = (byte) 1; - - /** - * The message op code indicating a response from the server. - */ - protected static final byte DHCP_BOOTREPLY = (byte) 2; - - /** - * The code type used to identify an Ethernet MAC address in the - * Client-ID field. - */ - protected static final byte CLIENT_ID_ETHER = (byte) 1; - - /** - * The maximum length of a packet that can be constructed. - */ - protected static final int MAX_LENGTH = 1500; - - /** - * DHCP Optional Type: DHCP Subnet Mask - */ - protected static final byte DHCP_SUBNET_MASK = 1; - protected InetAddress mSubnetMask; - - /** - * DHCP Optional Type: DHCP Router - */ - protected static final byte DHCP_ROUTER = 3; - protected InetAddress mGateway; - - /** - * DHCP Optional Type: DHCP DNS Server - */ - protected static final byte DHCP_DNS_SERVER = 6; - protected List<InetAddress> mDnsServers; - - /** - * DHCP Optional Type: DHCP Host Name - */ - protected static final byte DHCP_HOST_NAME = 12; - protected String mHostName; - - /** - * DHCP Optional Type: DHCP DOMAIN NAME - */ - protected static final byte DHCP_DOMAIN_NAME = 15; - protected String mDomainName; - - /** - * DHCP Optional Type: DHCP BROADCAST ADDRESS - */ - protected static final byte DHCP_BROADCAST_ADDRESS = 28; - protected InetAddress mBroadcastAddress; - - /** - * DHCP Optional Type: DHCP Requested IP Address - */ - protected static final byte DHCP_REQUESTED_IP = 50; - protected InetAddress mRequestedIp; - - /** - * DHCP Optional Type: DHCP Lease Time - */ - protected static final byte DHCP_LEASE_TIME = 51; - protected Integer mLeaseTime; - - /** - * DHCP Optional Type: DHCP Message Type - */ - protected static final byte DHCP_MESSAGE_TYPE = 53; - // the actual type values - protected static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1; - protected static final byte DHCP_MESSAGE_TYPE_OFFER = 2; - protected static final byte DHCP_MESSAGE_TYPE_REQUEST = 3; - protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4; - protected static final byte DHCP_MESSAGE_TYPE_ACK = 5; - protected static final byte DHCP_MESSAGE_TYPE_NAK = 6; - protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8; - - /** - * DHCP Optional Type: DHCP Server Identifier - */ - protected static final byte DHCP_SERVER_IDENTIFIER = 54; - protected InetAddress mServerIdentifier; - - /** - * DHCP Optional Type: DHCP Parameter List - */ - protected static final byte DHCP_PARAMETER_LIST = 55; - protected byte[] mRequestedParams; - - /** - * DHCP Optional Type: DHCP MESSAGE - */ - protected static final byte DHCP_MESSAGE = 56; - protected String mMessage; - - /** - * DHCP Optional Type: DHCP Renewal Time Value - */ - protected static final byte DHCP_RENEWAL_TIME = 58; - - /** - * DHCP Optional Type: Vendor Class Identifier - */ - protected static final byte DHCP_VENDOR_CLASS_ID = 60; - - /** - * DHCP Optional Type: DHCP Client Identifier - */ - protected static final byte DHCP_CLIENT_IDENTIFIER = 61; - - /** - * The transaction identifier used in this particular DHCP negotiation - */ - protected final int mTransId; - - /** - * The IP address of the client host. This address is typically - * proposed by the client (from an earlier DHCP negotiation) or - * supplied by the server. - */ - protected final InetAddress mClientIp; - protected final InetAddress mYourIp; - private final InetAddress mNextIp; - private final InetAddress mRelayIp; - - /** - * Does the client request a broadcast response? - */ - protected boolean mBroadcast; - - /** - * The six-octet MAC of the client. - */ - protected final byte[] mClientMac; - - /** - * Asks the packet object to signal the next operation in the DHCP - * protocol. The available actions are methods defined in the - * DhcpStateMachine interface. - */ - public abstract void doNextOp(DhcpStateMachine stateMachine); - - /** - * Asks the packet object to create a ByteBuffer serialization of - * the packet for transmission. - */ - public abstract ByteBuffer buildPacket(int encap, short destUdp, - short srcUdp); - - /** - * Allows the concrete class to fill in packet-type-specific details, - * typically optional parameters at the end of the packet. - */ - abstract void finishPacket(ByteBuffer buffer); - - protected DhcpPacket(int transId, InetAddress clientIp, InetAddress yourIp, - InetAddress nextIp, InetAddress relayIp, - byte[] clientMac, boolean broadcast) { - mTransId = transId; - mClientIp = clientIp; - mYourIp = yourIp; - mNextIp = nextIp; - mRelayIp = relayIp; - mClientMac = clientMac; - mBroadcast = broadcast; - } - - /** - * Returns the transaction ID. - */ - public int getTransactionId() { - return mTransId; - } - - /** - * Creates a new L3 packet (including IP header) containing the - * DHCP udp packet. This method relies upon the delegated method - * finishPacket() to insert the per-packet contents. - */ - protected void fillInPacket(int encap, InetAddress destIp, - InetAddress srcIp, short destUdp, short srcUdp, ByteBuffer buf, - byte requestCode, boolean broadcast) { - byte[] destIpArray = destIp.getAddress(); - byte[] srcIpArray = srcIp.getAddress(); - int ipLengthOffset = 0; - int ipChecksumOffset = 0; - int endIpHeader = 0; - int udpHeaderOffset = 0; - int udpLengthOffset = 0; - int udpChecksumOffset = 0; - - buf.clear(); - buf.order(ByteOrder.BIG_ENDIAN); - - // if a full IP packet needs to be generated, put the IP & UDP - // headers in place, and pre-populate with artificial values - // needed to seed the IP checksum. - if (encap == ENCAP_L3) { - // fake IP header, used in the IP-header checksum - buf.put(IP_VERSION_HEADER_LEN); - buf.put(IP_TOS_LOWDELAY); // tos: IPTOS_LOWDELAY - ipLengthOffset = buf.position(); - buf.putShort((short)0); // length - buf.putShort((short)0); // id - buf.putShort(IP_FLAGS_OFFSET); // ip offset: don't fragment - buf.put(IP_TTL); // TTL: use default 64 from RFC1340 - buf.put(IP_TYPE_UDP); - ipChecksumOffset = buf.position(); - buf.putShort((short) 0); // checksum - - buf.put(srcIpArray); - buf.put(destIpArray); - endIpHeader = buf.position(); - - // UDP header - udpHeaderOffset = buf.position(); - buf.putShort(srcUdp); - buf.putShort(destUdp); - udpLengthOffset = buf.position(); - buf.putShort((short) 0); // length - udpChecksumOffset = buf.position(); - buf.putShort((short) 0); // UDP checksum -- initially zero - } - - // DHCP payload - buf.put(requestCode); - buf.put((byte) 1); // Hardware Type: Ethernet - buf.put((byte) mClientMac.length); // Hardware Address Length - buf.put((byte) 0); // Hop Count - buf.putInt(mTransId); // Transaction ID - buf.putShort((short) 0); // Elapsed Seconds - - if (broadcast) { - buf.putShort((short) 0x8000); // Flags - } else { - buf.putShort((short) 0x0000); // Flags - } - - buf.put(mClientIp.getAddress()); - buf.put(mYourIp.getAddress()); - buf.put(mNextIp.getAddress()); - buf.put(mRelayIp.getAddress()); - buf.put(mClientMac); - buf.position(buf.position() + - (16 - mClientMac.length) // pad addr to 16 bytes - + 64 // empty server host name (64 bytes) - + 128); // empty boot file name (128 bytes) - buf.putInt(0x63825363); // magic number - finishPacket(buf); - - // round up to an even number of octets - if ((buf.position() & 1) == 1) { - buf.put((byte) 0); - } - - // If an IP packet is being built, the IP & UDP checksums must be - // computed. - if (encap == ENCAP_L3) { - // fix UDP header: insert length - short udpLen = (short)(buf.position() - udpHeaderOffset); - buf.putShort(udpLengthOffset, udpLen); - // fix UDP header: checksum - // checksum for UDP at udpChecksumOffset - int udpSeed = 0; - - // apply IPv4 pseudo-header. Read IP address src and destination - // values from the IP header and accumulate checksum. - udpSeed += intAbs(buf.getShort(ipChecksumOffset + 2)); - udpSeed += intAbs(buf.getShort(ipChecksumOffset + 4)); - udpSeed += intAbs(buf.getShort(ipChecksumOffset + 6)); - udpSeed += intAbs(buf.getShort(ipChecksumOffset + 8)); - - // accumulate extra data for the pseudo-header - udpSeed += IP_TYPE_UDP; - udpSeed += udpLen; - // and compute UDP checksum - buf.putShort(udpChecksumOffset, (short) checksum(buf, udpSeed, - udpHeaderOffset, - buf.position())); - // fix IP header: insert length - buf.putShort(ipLengthOffset, (short)buf.position()); - // fixup IP-header checksum - buf.putShort(ipChecksumOffset, - (short) checksum(buf, 0, 0, endIpHeader)); - } - } - - /** - * Converts a signed short value to an unsigned int value. Needed - * because Java does not have unsigned types. - */ - private int intAbs(short v) { - if (v < 0) { - int r = v + 65536; - return r; - } else { - return(v); - } - } - - /** - * Performs an IP checksum (used in IP header and across UDP - * payload) on the specified portion of a ByteBuffer. The seed - * allows the checksum to commence with a specified value. - */ - private int checksum(ByteBuffer buf, int seed, int start, int end) { - int sum = seed; - int bufPosition = buf.position(); - - // set position of original ByteBuffer, so that the ShortBuffer - // will be correctly initialized - buf.position(start); - ShortBuffer shortBuf = buf.asShortBuffer(); - - // re-set ByteBuffer position - buf.position(bufPosition); - - short[] shortArray = new short[(end - start) / 2]; - shortBuf.get(shortArray); - - for (short s : shortArray) { - sum += intAbs(s); - } - - start += shortArray.length * 2; - - // see if a singleton byte remains - if (end != start) { - short b = buf.get(start); - - // make it unsigned - if (b < 0) { - b += 256; - } - - sum += b * 256; - } - - sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF); - sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF); - int negated = ~sum; - return intAbs((short) negated); - } - - /** - * Adds an optional parameter containing a single byte value. - */ - protected void addTlv(ByteBuffer buf, byte type, byte value) { - buf.put(type); - buf.put((byte) 1); - buf.put(value); - } - - /** - * Adds an optional parameter containing an array of bytes. - */ - protected void addTlv(ByteBuffer buf, byte type, byte[] payload) { - if (payload != null) { - buf.put(type); - buf.put((byte) payload.length); - buf.put(payload); - } - } - - /** - * Adds an optional parameter containing an IP address. - */ - protected void addTlv(ByteBuffer buf, byte type, InetAddress addr) { - if (addr != null) { - addTlv(buf, type, addr.getAddress()); - } - } - - /** - * Adds an optional parameter containing a list of IP addresses. - */ - protected void addTlv(ByteBuffer buf, byte type, List<InetAddress> addrs) { - if (addrs != null && addrs.size() > 0) { - buf.put(type); - buf.put((byte)(4 * addrs.size())); - - for (InetAddress addr : addrs) { - buf.put(addr.getAddress()); - } - } - } - - /** - * Adds an optional parameter containing a simple integer - */ - protected void addTlv(ByteBuffer buf, byte type, Integer value) { - if (value != null) { - buf.put(type); - buf.put((byte) 4); - buf.putInt(value.intValue()); - } - } - - /** - * Adds an optional parameter containing and ASCII string. - */ - protected void addTlv(ByteBuffer buf, byte type, String str) { - if (str != null) { - buf.put(type); - buf.put((byte) str.length()); - - for (int i = 0; i < str.length(); i++) { - buf.put((byte) str.charAt(i)); - } - } - } - - /** - * Adds the special end-of-optional-parameters indicator. - */ - protected void addTlvEnd(ByteBuffer buf) { - buf.put((byte) 0xFF); - } - - /** - * Converts a MAC from an array of octets to an ASCII string. - */ - public static String macToString(byte[] mac) { - String macAddr = ""; - - for (int i = 0; i < mac.length; i++) { - String hexString = "0" + Integer.toHexString(mac[i]); - - // substring operation grabs the last 2 digits: this - // allows signed bytes to be converted correctly. - macAddr += hexString.substring(hexString.length() - 2); - - if (i != (mac.length - 1)) { - macAddr += ":"; - } - } - - return macAddr; - } - - public String toString() { - String macAddr = macToString(mClientMac); - - return macAddr; - } - - /** - * Reads a four-octet value from a ByteBuffer and construct - * an IPv4 address from that value. - */ - private static InetAddress readIpAddress(ByteBuffer packet) { - InetAddress result = null; - byte[] ipAddr = new byte[4]; - packet.get(ipAddr); - - try { - result = InetAddress.getByAddress(ipAddr); - } catch (UnknownHostException ex) { - // ipAddr is numeric, so this should not be - // triggered. However, if it is, just nullify - result = null; - } - - return result; - } - - /** - * Reads a string of specified length from the buffer. - */ - private static String readAsciiString(ByteBuffer buf, int byteCount) { - byte[] bytes = new byte[byteCount]; - buf.get(bytes); - return new String(bytes, 0, bytes.length, StandardCharsets.US_ASCII); - } - - /** - * Creates a concrete DhcpPacket from the supplied ByteBuffer. The - * buffer may have an L2 encapsulation (which is the full EthernetII - * format starting with the source-address MAC) or an L3 encapsulation - * (which starts with the IP header). - * <br> - * A subset of the optional parameters are parsed and are stored - * in object fields. - */ - public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) - { - // bootp parameters - int transactionId; - InetAddress clientIp; - InetAddress yourIp; - InetAddress nextIp; - InetAddress relayIp; - byte[] clientMac; - List<InetAddress> dnsServers = new ArrayList<InetAddress>(); - InetAddress gateway = null; // aka router - Integer leaseTime = null; - InetAddress serverIdentifier = null; - InetAddress netMask = null; - String message = null; - String vendorId = null; - byte[] expectedParams = null; - String hostName = null; - String domainName = null; - InetAddress ipSrc = null; - InetAddress ipDst = null; - InetAddress bcAddr = null; - InetAddress requestedIp = null; - - // dhcp options - byte dhcpType = (byte) 0xFF; - - packet.order(ByteOrder.BIG_ENDIAN); - - // check to see if we need to parse L2, IP, and UDP encaps - if (pktType == ENCAP_L2) { - // System.out.println("buffer len " + packet.limit()); - byte[] l2dst = new byte[6]; - byte[] l2src = new byte[6]; - - packet.get(l2dst); - packet.get(l2src); - - short l2type = packet.getShort(); - - if (l2type != 0x0800) - return null; - } - - if ((pktType == ENCAP_L2) || (pktType == ENCAP_L3)) { - // assume l2type is 0x0800, i.e. IP - byte ipType = packet.get(); - // System.out.println("ipType is " + ipType); - byte ipDiffServicesField = packet.get(); - short ipTotalLength = packet.getShort(); - short ipIdentification = packet.getShort(); - byte ipFlags = packet.get(); - byte ipFragOffset = packet.get(); - byte ipTTL = packet.get(); - byte ipProto = packet.get(); - short ipChksm = packet.getShort(); - - ipSrc = readIpAddress(packet); - ipDst = readIpAddress(packet); - - if (ipProto != IP_TYPE_UDP) // UDP - return null; - - // assume UDP - short udpSrcPort = packet.getShort(); - short udpDstPort = packet.getShort(); - short udpLen = packet.getShort(); - short udpChkSum = packet.getShort(); - - if ((udpSrcPort != DHCP_SERVER) && (udpSrcPort != DHCP_CLIENT)) - return null; - } - - // assume bootp - byte type = packet.get(); - byte hwType = packet.get(); - byte addrLen = packet.get(); - byte hops = packet.get(); - transactionId = packet.getInt(); - short elapsed = packet.getShort(); - short bootpFlags = packet.getShort(); - boolean broadcast = (bootpFlags & 0x8000) != 0; - byte[] ipv4addr = new byte[4]; - - try { - packet.get(ipv4addr); - clientIp = InetAddress.getByAddress(ipv4addr); - packet.get(ipv4addr); - yourIp = InetAddress.getByAddress(ipv4addr); - packet.get(ipv4addr); - nextIp = InetAddress.getByAddress(ipv4addr); - packet.get(ipv4addr); - relayIp = InetAddress.getByAddress(ipv4addr); - } catch (UnknownHostException ex) { - return null; - } - - clientMac = new byte[addrLen]; - packet.get(clientMac); - - // skip over address padding (16 octets allocated) - packet.position(packet.position() + (16 - addrLen) - + 64 // skip server host name (64 chars) - + 128); // skip boot file name (128 chars) - - int dhcpMagicCookie = packet.getInt(); - - if (dhcpMagicCookie != 0x63825363) - return null; - - // parse options - boolean notFinishedOptions = true; - - while ((packet.position() < packet.limit()) && notFinishedOptions) { - byte optionType = packet.get(); - - if (optionType == (byte) 0xFF) { - notFinishedOptions = false; - } else { - byte optionLen = packet.get(); - int expectedLen = 0; - - switch(optionType) { - case DHCP_SUBNET_MASK: - netMask = readIpAddress(packet); - expectedLen = 4; - break; - case DHCP_ROUTER: - gateway = readIpAddress(packet); - expectedLen = 4; - break; - case DHCP_DNS_SERVER: - expectedLen = 0; - - for (expectedLen = 0; expectedLen < optionLen; - expectedLen += 4) { - dnsServers.add(readIpAddress(packet)); - } - break; - case DHCP_HOST_NAME: - expectedLen = optionLen; - hostName = readAsciiString(packet, optionLen); - break; - case DHCP_DOMAIN_NAME: - expectedLen = optionLen; - domainName = readAsciiString(packet, optionLen); - break; - case DHCP_BROADCAST_ADDRESS: - bcAddr = readIpAddress(packet); - expectedLen = 4; - break; - case DHCP_REQUESTED_IP: - requestedIp = readIpAddress(packet); - expectedLen = 4; - break; - case DHCP_LEASE_TIME: - leaseTime = Integer.valueOf(packet.getInt()); - expectedLen = 4; - break; - case DHCP_MESSAGE_TYPE: - dhcpType = packet.get(); - expectedLen = 1; - break; - case DHCP_SERVER_IDENTIFIER: - serverIdentifier = readIpAddress(packet); - expectedLen = 4; - break; - case DHCP_PARAMETER_LIST: - expectedParams = new byte[optionLen]; - packet.get(expectedParams); - expectedLen = optionLen; - break; - case DHCP_MESSAGE: - expectedLen = optionLen; - message = readAsciiString(packet, optionLen); - break; - case DHCP_VENDOR_CLASS_ID: - expectedLen = optionLen; - vendorId = readAsciiString(packet, optionLen); - break; - case DHCP_CLIENT_IDENTIFIER: { // Client identifier - byte[] id = new byte[optionLen]; - packet.get(id); - expectedLen = optionLen; - } break; - default: - // ignore any other parameters - for (int i = 0; i < optionLen; i++) { - expectedLen++; - byte throwaway = packet.get(); - } - } - - if (expectedLen != optionLen) { - return null; - } - } - } - - DhcpPacket newPacket; - - switch(dhcpType) { - case -1: return null; - case DHCP_MESSAGE_TYPE_DISCOVER: - newPacket = new DhcpDiscoverPacket( - transactionId, clientMac, broadcast); - break; - case DHCP_MESSAGE_TYPE_OFFER: - newPacket = new DhcpOfferPacket( - transactionId, broadcast, ipSrc, yourIp, clientMac); - break; - case DHCP_MESSAGE_TYPE_REQUEST: - newPacket = new DhcpRequestPacket( - transactionId, clientIp, clientMac, broadcast); - break; - case DHCP_MESSAGE_TYPE_DECLINE: - newPacket = new DhcpDeclinePacket( - transactionId, clientIp, yourIp, nextIp, relayIp, - clientMac); - break; - case DHCP_MESSAGE_TYPE_ACK: - newPacket = new DhcpAckPacket( - transactionId, broadcast, ipSrc, yourIp, clientMac); - break; - case DHCP_MESSAGE_TYPE_NAK: - newPacket = new DhcpNakPacket( - transactionId, clientIp, yourIp, nextIp, relayIp, - clientMac); - break; - case DHCP_MESSAGE_TYPE_INFORM: - newPacket = new DhcpInformPacket( - transactionId, clientIp, yourIp, nextIp, relayIp, - clientMac); - break; - default: - System.out.println("Unimplemented type: " + dhcpType); - return null; - } - - newPacket.mBroadcastAddress = bcAddr; - newPacket.mDnsServers = dnsServers; - newPacket.mDomainName = domainName; - newPacket.mGateway = gateway; - newPacket.mHostName = hostName; - newPacket.mLeaseTime = leaseTime; - newPacket.mMessage = message; - newPacket.mRequestedIp = requestedIp; - newPacket.mRequestedParams = expectedParams; - newPacket.mServerIdentifier = serverIdentifier; - newPacket.mSubnetMask = netMask; - return newPacket; - } - - /** - * Parse a packet from an array of bytes. - */ - public static DhcpPacket decodeFullPacket(byte[] packet, int pktType) - { - ByteBuffer buffer = ByteBuffer.wrap(packet).order(ByteOrder.BIG_ENDIAN); - return decodeFullPacket(buffer, pktType); - } - - /** - * Builds a DHCP-DISCOVER packet from the required specified - * parameters. - */ - public static ByteBuffer buildDiscoverPacket(int encap, int transactionId, - byte[] clientMac, boolean broadcast, byte[] expectedParams) { - DhcpPacket pkt = new DhcpDiscoverPacket( - transactionId, clientMac, broadcast); - pkt.mRequestedParams = expectedParams; - return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT); - } - - /** - * Builds a DHCP-OFFER packet from the required specified - * parameters. - */ - public static ByteBuffer buildOfferPacket(int encap, int transactionId, - boolean broadcast, InetAddress serverIpAddr, InetAddress clientIpAddr, - byte[] mac, Integer timeout, InetAddress netMask, InetAddress bcAddr, - InetAddress gateway, List<InetAddress> dnsServers, - InetAddress dhcpServerIdentifier, String domainName) { - DhcpPacket pkt = new DhcpOfferPacket( - transactionId, broadcast, serverIpAddr, clientIpAddr, mac); - pkt.mGateway = gateway; - pkt.mDnsServers = dnsServers; - pkt.mLeaseTime = timeout; - pkt.mDomainName = domainName; - pkt.mServerIdentifier = dhcpServerIdentifier; - pkt.mSubnetMask = netMask; - pkt.mBroadcastAddress = bcAddr; - return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER); - } - - /** - * Builds a DHCP-ACK packet from the required specified parameters. - */ - public static ByteBuffer buildAckPacket(int encap, int transactionId, - boolean broadcast, InetAddress serverIpAddr, InetAddress clientIpAddr, - byte[] mac, Integer timeout, InetAddress netMask, InetAddress bcAddr, - InetAddress gateway, List<InetAddress> dnsServers, - InetAddress dhcpServerIdentifier, String domainName) { - DhcpPacket pkt = new DhcpAckPacket( - transactionId, broadcast, serverIpAddr, clientIpAddr, mac); - pkt.mGateway = gateway; - pkt.mDnsServers = dnsServers; - pkt.mLeaseTime = timeout; - pkt.mDomainName = domainName; - pkt.mSubnetMask = netMask; - pkt.mServerIdentifier = dhcpServerIdentifier; - pkt.mBroadcastAddress = bcAddr; - return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER); - } - - /** - * Builds a DHCP-NAK packet from the required specified parameters. - */ - public static ByteBuffer buildNakPacket(int encap, int transactionId, - InetAddress serverIpAddr, InetAddress clientIpAddr, byte[] mac) { - DhcpPacket pkt = new DhcpNakPacket(transactionId, clientIpAddr, - serverIpAddr, serverIpAddr, serverIpAddr, mac); - pkt.mMessage = "requested address not available"; - pkt.mRequestedIp = clientIpAddr; - return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER); - } - - /** - * Builds a DHCP-REQUEST packet from the required specified parameters. - */ - public static ByteBuffer buildRequestPacket(int encap, - int transactionId, InetAddress clientIp, boolean broadcast, - byte[] clientMac, InetAddress requestedIpAddress, - InetAddress serverIdentifier, byte[] requestedParams, String hostName) { - DhcpPacket pkt = new DhcpRequestPacket(transactionId, clientIp, - clientMac, broadcast); - pkt.mRequestedIp = requestedIpAddress; - pkt.mServerIdentifier = serverIdentifier; - pkt.mHostName = hostName; - pkt.mRequestedParams = requestedParams; - ByteBuffer result = pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT); - return result; - } -} diff --git a/core/java/android/net/dhcp/DhcpRequestPacket.java b/core/java/android/net/dhcp/DhcpRequestPacket.java deleted file mode 100644 index cf32957..0000000 --- a/core/java/android/net/dhcp/DhcpRequestPacket.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import android.util.Log; - -import java.net.InetAddress; -import java.net.Inet4Address; -import java.nio.ByteBuffer; - -/** - * This class implements the DHCP-REQUEST packet. - */ -class DhcpRequestPacket extends DhcpPacket { - /** - * Generates a REQUEST packet with the specified parameters. - */ - DhcpRequestPacket(int transId, InetAddress clientIp, byte[] clientMac, - boolean broadcast) { - super(transId, clientIp, Inet4Address.ANY, Inet4Address.ANY, - Inet4Address.ANY, clientMac, broadcast); - } - - public String toString() { - String s = super.toString(); - return s + " REQUEST, desired IP " + mRequestedIp + " from host '" - + mHostName + "', param list length " - + (mRequestedParams == null ? 0 : mRequestedParams.length); - } - - /** - * Fills in a packet with the requested REQUEST attributes. - */ - public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { - ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - - fillInPacket(encap, Inet4Address.ALL, Inet4Address.ANY, destUdp, srcUdp, - result, DHCP_BOOTREQUEST, mBroadcast); - result.flip(); - return result; - } - - /** - * Adds the optional parameters to the client-generated REQUEST packet. - */ - void finishPacket(ByteBuffer buffer) { - byte[] clientId = new byte[7]; - - // assemble client identifier - clientId[0] = CLIENT_ID_ETHER; - System.arraycopy(mClientMac, 0, clientId, 1, 6); - - addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST); - addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); - addTlv(buffer, DHCP_REQUESTED_IP, mRequestedIp); - addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); - addTlv(buffer, DHCP_CLIENT_IDENTIFIER, clientId); - addTlvEnd(buffer); - } - - /** - * Notifies the specified state machine of the REQUEST packet parameters. - */ - public void doNextOp(DhcpStateMachine machine) { - InetAddress clientRequest = - mRequestedIp == null ? mClientIp : mRequestedIp; - Log.v(TAG, "requested IP is " + mRequestedIp + " and client IP is " + - mClientIp); - machine.onRequestReceived(mBroadcast, mTransId, mClientMac, - clientRequest, mRequestedParams, mHostName); - } -} diff --git a/core/java/android/net/dhcp/DhcpStateMachine.java b/core/java/android/net/dhcp/DhcpStateMachine.java deleted file mode 100644 index bc9a798..0000000 --- a/core/java/android/net/dhcp/DhcpStateMachine.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import java.net.InetAddress; -import java.util.List; - -/** - * This class defines the "next steps" which occur after a given DHCP - * packet has been received. - */ -interface DhcpStateMachine { - /** - * Signals that an offer packet has been received with the specified - * parameters. - */ - public void onOfferReceived(boolean broadcast, int transactionId, - byte[] myMac, InetAddress offeredIpAddress, - InetAddress serverIpAddress); - - /** - * Signals that a NAK packet has been received. - */ - public void onNakReceived(); - - /** - * Signals that the final ACK has been received from the server. - */ - public void onAckReceived(InetAddress myIpAddress, InetAddress myNetMask, - InetAddress myGateway, List<InetAddress> myDnsServers, - InetAddress myDhcpServer, int leaseTime); - - /** - * Signals that a client's DISCOVER packet has been received with the - * specified parameters. - */ - public void onDiscoverReceived(boolean broadcast, int transactionId, - byte[] clientMac, byte[] requestedParameterList); - - /** - * Signals that a client's REQUEST packet has been received with the - * specified parameters. - */ - public void onRequestReceived(boolean broadcast, int transactionId, - byte[] clientMac, InetAddress requestedIp, byte[] requestedParams, - String clientHostName); - - /** - * Signals that a client's INFORM packet has been received with the - * specified parameters. - */ - public void onInformReceived(int transactionId, byte[] clientMac, - InetAddress preassignedIp, byte[] requestedParams); - - /** - * Signals that a client's DECLINE packet has been received with the - * specified parameters. - */ - public void onDeclineReceived(byte[] clientMac, InetAddress declinedIp); -} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index d96a0e9..8b3ecae 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -29,8 +29,8 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.telephony.SignalStrength; import android.text.format.DateFormat; +import android.util.ArrayMap; import android.util.Printer; -import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TimeUtils; @@ -284,21 +284,21 @@ public abstract class BatteryStats implements Parcelable { * * @return a Map from Strings to Uid.Wakelock objects. */ - public abstract Map<String, ? extends Wakelock> getWakelockStats(); + public abstract ArrayMap<String, ? extends Wakelock> getWakelockStats(); /** * Returns a mapping containing sync statistics. * * @return a Map from Strings to Timer objects. */ - public abstract Map<String, ? extends Timer> getSyncStats(); + public abstract ArrayMap<String, ? extends Timer> getSyncStats(); /** * Returns a mapping containing scheduled job statistics. * * @return a Map from Strings to Timer objects. */ - public abstract Map<String, ? extends Timer> getJobStats(); + public abstract ArrayMap<String, ? extends Timer> getJobStats(); /** * The statistics associated with a particular wake lock. @@ -324,14 +324,14 @@ public abstract class BatteryStats implements Parcelable { * * @return a Map from Strings to Uid.Proc objects. */ - public abstract Map<String, ? extends Proc> getProcessStats(); + public abstract ArrayMap<String, ? extends Proc> getProcessStats(); /** * Returns a mapping containing package statistics. * * @return a Map from Strings to Uid.Pkg objects. */ - public abstract Map<String, ? extends Pkg> getPackageStats(); + public abstract ArrayMap<String, ? extends Pkg> getPackageStats(); /** * {@hide} @@ -502,17 +502,16 @@ public abstract class BatteryStats implements Parcelable { public static abstract class Pkg { /** - * Returns the number of times this package has done something that could wake up the - * device from sleep. - * - * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. + * Returns information about all wakeup alarms that have been triggered for this + * package. The mapping keys are tag names for the alarms, the counter contains + * the number of times the alarm was triggered while on battery. */ - public abstract int getWakeups(int which); + public abstract ArrayMap<String, ? extends Counter> getWakeupAlarmStats(); /** * Returns a mapping containing service statistics. */ - public abstract Map<String, ? extends Serv> getServiceStats(); + public abstract ArrayMap<String, ? extends Serv> getServiceStats(); /** * The statistics associated with a particular service. @@ -614,6 +613,9 @@ public abstract class BatteryStats implements Parcelable { if ((initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0) { out.append('p'); } + if ((initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0) { + out.append('i'); + } switch ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) { case Display.STATE_OFF: out.append('F'); break; case Display.STATE_ON: out.append('O'); break; @@ -623,6 +625,9 @@ public abstract class BatteryStats implements Parcelable { if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) != 0) { out.append('P'); } + if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0) { + out.append('I'); + } out.append('-'); appendHex(level, 4, out); out.append('-'); @@ -649,6 +654,9 @@ public abstract class BatteryStats implements Parcelable { case 'p': out |= (((long)STEP_LEVEL_MODE_POWER_SAVE) << STEP_LEVEL_INITIAL_MODE_SHIFT); break; + case 'i': out |= (((long)STEP_LEVEL_MODE_DEVICE_IDLE) + << STEP_LEVEL_INITIAL_MODE_SHIFT); + break; case 'F': out |= (((long)Display.STATE_OFF-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT); break; case 'O': out |= (((long)Display.STATE_ON-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT); @@ -661,6 +669,9 @@ public abstract class BatteryStats implements Parcelable { case 'P': out |= (((long)STEP_LEVEL_MODE_POWER_SAVE) << STEP_LEVEL_MODIFIED_MODE_SHIFT); break; + case 'I': out |= (((long)STEP_LEVEL_MODE_DEVICE_IDLE) + << STEP_LEVEL_MODIFIED_MODE_SHIFT); + break; } } i++; @@ -821,11 +832,18 @@ public abstract class BatteryStats implements Parcelable { } } + public static final class PackageChange { + public String mPackageName; + public boolean mUpdate; + public int mVersionCode; + } + public static final class DailyItem { public long mStartTime; public long mEndTime; public LevelStepTracker mDischargeSteps; public LevelStepTracker mChargeSteps; + public ArrayList<PackageChange> mPackageChanges; } public abstract DailyItem getDailyItemLocked(int daysAgo); @@ -1044,14 +1062,15 @@ public abstract class BatteryStats implements Parcelable { public static final int STATE2_WIFI_SIGNAL_STRENGTH_MASK = 0x7 << STATE2_WIFI_SIGNAL_STRENGTH_SHIFT; - public static final int STATE2_LOW_POWER_FLAG = 1<<31; + public static final int STATE2_POWER_SAVE_FLAG = 1<<31; public static final int STATE2_VIDEO_ON_FLAG = 1<<30; public static final int STATE2_WIFI_RUNNING_FLAG = 1<<29; public static final int STATE2_WIFI_ON_FLAG = 1<<28; public static final int STATE2_FLASHLIGHT_FLAG = 1<<27; + public static final int STATE2_DEVICE_IDLE_FLAG = 1<<26; public static final int MOST_INTERESTING_STATES2 = - STATE2_LOW_POWER_FLAG | STATE2_WIFI_ON_FLAG; + STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_FLAG; public int states2; @@ -1086,10 +1105,18 @@ public abstract class BatteryStats implements Parcelable { public static final int EVENT_USER_RUNNING = 0x0007; // Events for foreground user. public static final int EVENT_USER_FOREGROUND = 0x0008; - // Events for connectivity changed. + // Event for connectivity changed. public static final int EVENT_CONNECTIVITY_CHANGED = 0x0009; + // Event for significant motion taking us out of idle mode. + public static final int EVENT_SIGNIFICANT_MOTION = 0x000a; + // Event for becoming active taking us out of idle mode. + public static final int EVENT_ACTIVE = 0x000b; + // Event for a package being installed. + public static final int EVENT_PACKAGE_INSTALLED = 0x000c; + // Event for a package being uninstalled. + public static final int EVENT_PACKAGE_UNINSTALLED = 0x000d; // Number of event types. - public static final int EVENT_COUNT = 0x000a; + public static final int EVENT_COUNT = 0x000e; // Mask to extract out only the type part of the event. public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH); @@ -1325,7 +1352,7 @@ public abstract class BatteryStats implements Parcelable { int idx = code&HistoryItem.EVENT_TYPE_MASK; HashMap<String, SparseIntArray> active = mActiveEvents[idx]; if (active == null) { - active = new HashMap<String, SparseIntArray>(); + active = new HashMap<>(); mActiveEvents[idx] = active; } SparseIntArray uids = active.get(name); @@ -1486,19 +1513,51 @@ public abstract class BatteryStats implements Parcelable { long elapsedRealtimeUs, int which); /** - * Returns the time in microseconds that low power mode has been enabled while the device was + * Returns the time in microseconds that power save mode has been enabled while the device was * running on battery. * * {@hide} */ - public abstract long getLowPowerModeEnabledTime(long elapsedRealtimeUs, int which); + public abstract long getPowerSaveModeEnabledTime(long elapsedRealtimeUs, int which); + + /** + * Returns the number of times that power save mode was enabled. + * + * {@hide} + */ + public abstract int getPowerSaveModeEnabledCount(int which); + + /** + * Returns the time in microseconds that device has been in idle mode while + * running on battery. + * + * {@hide} + */ + public abstract long getDeviceIdleModeEnabledTime(long elapsedRealtimeUs, int which); + + /** + * Returns the number of times that the devie has gone in to idle mode. + * + * {@hide} + */ + public abstract int getDeviceIdleModeEnabledCount(int which); /** - * Returns the number of times that low power mode was enabled. + * Returns the time in microseconds that device has been in idling while on + * battery. This is broader than {@link #getDeviceIdleModeEnabledTime} -- it + * counts all of the time that we consider the device to be idle, whether or not + * it is currently in the actual device idle mode. * * {@hide} */ - public abstract int getLowPowerModeEnabledCount(int which); + public abstract long getDeviceIdlingTime(long elapsedRealtimeUs, int which); + + /** + * Returns the number of times that the devie has started idling. + * + * {@hide} + */ + public abstract int getDeviceIdlingCount(int which); /** * Returns the number of times that connectivity state changed. @@ -1692,11 +1751,12 @@ public abstract class BatteryStats implements Parcelable { public static final BitDescription[] HISTORY_STATE2_DESCRIPTIONS = new BitDescription[] { - new BitDescription(HistoryItem.STATE2_LOW_POWER_FLAG, "low_power", "lp"), + new BitDescription(HistoryItem.STATE2_POWER_SAVE_FLAG, "power_save", "ps"), new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"), new BitDescription(HistoryItem.STATE2_WIFI_RUNNING_FLAG, "wifi_running", "Wr"), new BitDescription(HistoryItem.STATE2_WIFI_ON_FLAG, "wifi", "W"), new BitDescription(HistoryItem.STATE2_FLASHLIGHT_FLAG, "flashlight", "fl"), + new BitDescription(HistoryItem.STATE2_DEVICE_IDLE_FLAG, "device_idle", "di"), new BitDescription(HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK, HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT, "wifi_signal_strength", "Wss", new String[] { "0", "1", "2", "3", "4" }, @@ -1707,11 +1767,13 @@ public abstract class BatteryStats implements Parcelable { }; public static final String[] HISTORY_EVENT_NAMES = new String[] { - "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn" + "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn", + "motion", "active", "pkginst", "pkgunin" }; public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] { - "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn" + "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn", + "Esm", "Eac", "Epi", "Epu" }; /** @@ -2043,45 +2105,44 @@ public abstract class BatteryStats implements Parcelable { // Step duration mode: power save is on. public static final int STEP_LEVEL_MODE_POWER_SAVE = 0x04; + // Step duration mode: device is currently in idle mode. + public static final int STEP_LEVEL_MODE_DEVICE_IDLE = 0x08; + public static final int[] STEP_LEVEL_MODES_OF_INTEREST = new int[] { STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE|STEP_LEVEL_MODE_DEVICE_IDLE, + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_DEVICE_IDLE, STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, - STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, - STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE|STEP_LEVEL_MODE_DEVICE_IDLE, + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_DEVICE_IDLE, }; public static final int[] STEP_LEVEL_MODE_VALUES = new int[] { (Display.STATE_OFF-1), (Display.STATE_OFF-1)|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_OFF-1)|STEP_LEVEL_MODE_DEVICE_IDLE, (Display.STATE_ON-1), (Display.STATE_ON-1)|STEP_LEVEL_MODE_POWER_SAVE, (Display.STATE_DOZE-1), (Display.STATE_DOZE-1)|STEP_LEVEL_MODE_POWER_SAVE, (Display.STATE_DOZE_SUSPEND-1), (Display.STATE_DOZE_SUSPEND-1)|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_DOZE_SUSPEND-1)|STEP_LEVEL_MODE_DEVICE_IDLE, }; public static final String[] STEP_LEVEL_MODE_LABELS = new String[] { "screen off", "screen off power save", + "screen off device idle", "screen on", "screen on power save", "screen doze", "screen doze power save", "screen doze-suspend", "screen doze-suspend power save", - }; - public static final String[] STEP_LEVEL_MODE_TAGS = new String[] { - "off", - "off-save", - "on", - "on-save", - "doze", - "doze-save", - "susp", - "susp-save", + "screen doze-suspend device idle", }; /** @@ -2114,6 +2175,8 @@ public abstract class BatteryStats implements Parcelable { */ public abstract LevelStepTracker getDailyChargeLevelStepTracker(); + public abstract ArrayList<PackageChange> getDailyPackageChanges(); + public abstract Map<String, ? extends Timer> getWakeupReasonStats(); public abstract Map<String, ? extends Timer> getKernelWakelockStats(); @@ -2310,19 +2373,21 @@ public abstract class BatteryStats implements Parcelable { final long totalUptime = computeUptime(rawUptime, which); final long screenOnTime = getScreenOnTime(rawRealtime, which); final long interactiveTime = getInteractiveTime(rawRealtime, which); - final long lowPowerModeEnabledTime = getLowPowerModeEnabledTime(rawRealtime, which); + final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which); + final long deviceIdleModeEnabledTime = getDeviceIdleModeEnabledTime(rawRealtime, which); + final long deviceIdlingTime = getDeviceIdlingTime(rawRealtime, which); final int connChanges = getNumConnectivityChange(which); final long phoneOnTime = getPhoneOnTime(rawRealtime, which); final long wifiOnTime = getWifiOnTime(rawRealtime, which); final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); final long bluetoothOnTime = getBluetoothOnTime(rawRealtime, which); - StringBuilder sb = new StringBuilder(128); + final StringBuilder sb = new StringBuilder(128); - SparseArray<? extends Uid> uidStats = getUidStats(); + final SparseArray<? extends Uid> uidStats = getUidStats(); final int NU = uidStats.size(); - String category = STAT_NAMES[which]; + final String category = STAT_NAMES[which]; // Dump "battery" stat dumpLine(pw, 0 /* uid */, category, BATTERY_DATA, @@ -2337,37 +2402,35 @@ public abstract class BatteryStats implements Parcelable { long partialWakeLockTimeTotal = 0; for (int iu = 0; iu < NU; iu++) { - Uid u = uidStats.valueAt(iu); - - Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); - if (wakelocks.size() > 0) { - for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent - : wakelocks.entrySet()) { - Uid.Wakelock wl = ent.getValue(); - - Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL); - if (fullWakeTimer != null) { - fullWakeLockTimeTotal += fullWakeTimer.getTotalTimeLocked(rawRealtime, - which); - } + final Uid u = uidStats.valueAt(iu); - Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL); - if (partialWakeTimer != null) { - partialWakeLockTimeTotal += partialWakeTimer.getTotalTimeLocked( - rawRealtime, which); - } + final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks + = u.getWakelockStats(); + for (int iw=wakelocks.size()-1; iw>=0; iw--) { + final Uid.Wakelock wl = wakelocks.valueAt(iw); + + final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL); + if (fullWakeTimer != null) { + fullWakeLockTimeTotal += fullWakeTimer.getTotalTimeLocked(rawRealtime, + which); + } + + final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL); + if (partialWakeTimer != null) { + partialWakeLockTimeTotal += partialWakeTimer.getTotalTimeLocked( + rawRealtime, which); } } } - long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); - long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); - long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); - long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); - long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); - long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); - long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); - long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); + final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); + final long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); + final long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); + final long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); + final long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); + final long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); + final long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); + final long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); // Dump network stats dumpLine(pw, 0 /* uid */, category, GLOBAL_NETWORK_DATA, @@ -2382,7 +2445,9 @@ public abstract class BatteryStats implements Parcelable { fullWakeLockTimeTotal / 1000, partialWakeLockTimeTotal / 1000, 0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which) / 1000, getMobileRadioActiveAdjustedTime(which) / 1000, interactiveTime / 1000, - lowPowerModeEnabledTime / 1000, connChanges); + powerSaveModeEnabledTime / 1000, connChanges, deviceIdleModeEnabledTime / 1000, + getDeviceIdleModeEnabledCount(which), deviceIdlingTime / 1000, + getDeviceIdlingCount(which)); // Dump screen brightness stats Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS]; @@ -2477,7 +2542,7 @@ public abstract class BatteryStats implements Parcelable { } if (reqUid < 0) { - Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats(); + final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats(); if (kernelWakelocks.size() > 0) { for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) { sb.setLength(0); @@ -2486,7 +2551,7 @@ public abstract class BatteryStats implements Parcelable { sb.toString()); } } - Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats(); + final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats(); if (wakeupReasons.size() > 0) { for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) { // Not doing the regular wake lock formatting to remain compatible @@ -2499,10 +2564,10 @@ public abstract class BatteryStats implements Parcelable { } } - BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly); + final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly); helper.create(this); helper.refreshStats(which, UserHandle.USER_ALL); - List<BatterySipper> sippers = helper.getUsageList(); + final List<BatterySipper> sippers = helper.getUsageList(); if (sippers != null && sippers.size() > 0) { dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA, BatteryStatsHelper.makemAh(helper.getPowerProfile().getBatteryCapacity()), @@ -2510,7 +2575,7 @@ public abstract class BatteryStats implements Parcelable { BatteryStatsHelper.makemAh(helper.getMinDrainedPower()), BatteryStatsHelper.makemAh(helper.getMaxDrainedPower())); for (int i=0; i<sippers.size(); i++) { - BatterySipper bs = sippers.get(i); + final BatterySipper bs = sippers.get(i); int uid = 0; String label; switch (bs.drainType) { @@ -2562,22 +2627,22 @@ public abstract class BatteryStats implements Parcelable { if (reqUid >= 0 && uid != reqUid) { continue; } - Uid u = uidStats.valueAt(iu); + final Uid u = uidStats.valueAt(iu); // Dump Network stats per uid, if any - long mobileBytesRx = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); - long mobileBytesTx = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); - long wifiBytesRx = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); - long wifiBytesTx = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); - long mobilePacketsRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); - long mobilePacketsTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); - long mobileActiveTime = u.getMobileRadioActiveTime(which); - int mobileActiveCount = u.getMobileRadioActiveCount(which); - long wifiPacketsRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); - long wifiPacketsTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); - long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which); - long wifiScanTime = u.getWifiScanTime(rawRealtime, which); - int wifiScanCount = u.getWifiScanCount(which); - long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); + final long mobileBytesRx = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); + final long mobileBytesTx = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); + final long wifiBytesRx = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); + final long wifiBytesTx = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); + final long mobilePacketsRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); + final long mobilePacketsTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); + final long mobileActiveTime = u.getMobileRadioActiveTime(which); + final int mobileActiveCount = u.getMobileRadioActiveCount(which); + final long wifiPacketsRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); + final long wifiPacketsTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); + final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which); + final long wifiScanTime = u.getWifiScanTime(rawRealtime, which); + final int wifiScanCount = u.getWifiScanCount(which); + final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); if (mobileBytesRx > 0 || mobileBytesTx > 0 || wifiBytesRx > 0 || wifiBytesTx > 0 || mobilePacketsRx > 0 || mobilePacketsTx > 0 || wifiPacketsRx > 0 @@ -2608,93 +2673,90 @@ public abstract class BatteryStats implements Parcelable { } } - Map<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats(); - if (wakelocks.size() > 0) { - for (Map.Entry<String, ? extends Uid.Wakelock> ent : wakelocks.entrySet()) { - Uid.Wakelock wl = ent.getValue(); - String linePrefix = ""; - sb.setLength(0); - linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL), - rawRealtime, "f", which, linePrefix); - linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), - rawRealtime, "p", which, linePrefix); - linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), - rawRealtime, "w", which, linePrefix); - - // Only log if we had at lease one wakelock... - if (sb.length() > 0) { - String name = ent.getKey(); - if (name.indexOf(',') >= 0) { - name = name.replace(',', '_'); - } - dumpLine(pw, uid, category, WAKELOCK_DATA, name, sb.toString()); + final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats(); + for (int iw=wakelocks.size()-1; iw>=0; iw--) { + final Uid.Wakelock wl = wakelocks.valueAt(iw); + String linePrefix = ""; + sb.setLength(0); + linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL), + rawRealtime, "f", which, linePrefix); + linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), + rawRealtime, "p", which, linePrefix); + linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), + rawRealtime, "w", which, linePrefix); + + // Only log if we had at lease one wakelock... + if (sb.length() > 0) { + String name = wakelocks.keyAt(iw); + if (name.indexOf(',') >= 0) { + name = name.replace(',', '_'); } + dumpLine(pw, uid, category, WAKELOCK_DATA, name, sb.toString()); } } - Map<String, ? extends Timer> syncs = u.getSyncStats(); - if (syncs.size() > 0) { - for (Map.Entry<String, ? extends Timer> ent : syncs.entrySet()) { - Timer timer = ent.getValue(); - // Convert from microseconds to milliseconds with rounding - long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; - int count = timer.getCountLocked(which); - if (totalTime != 0) { - dumpLine(pw, uid, category, SYNC_DATA, ent.getKey(), totalTime, count); - } + final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats(); + for (int isy=syncs.size()-1; isy>=0; isy--) { + final Timer timer = syncs.valueAt(isy); + // Convert from microseconds to milliseconds with rounding + final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; + final int count = timer.getCountLocked(which); + if (totalTime != 0) { + dumpLine(pw, uid, category, SYNC_DATA, syncs.keyAt(isy), totalTime, count); } } - Map<String, ? extends Timer> jobs = u.getJobStats(); - if (jobs.size() > 0) { - for (Map.Entry<String, ? extends Timer> ent : jobs.entrySet()) { - Timer timer = ent.getValue(); - // Convert from microseconds to milliseconds with rounding - long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; - int count = timer.getCountLocked(which); - if (totalTime != 0) { - dumpLine(pw, uid, category, JOB_DATA, ent.getKey(), totalTime, count); - } + final ArrayMap<String, ? extends Timer> jobs = u.getJobStats(); + for (int ij=jobs.size()-1; ij>=0; ij--) { + final Timer timer = jobs.valueAt(ij); + // Convert from microseconds to milliseconds with rounding + final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; + final int count = timer.getCountLocked(which); + if (totalTime != 0) { + dumpLine(pw, uid, category, JOB_DATA, jobs.keyAt(ij), totalTime, count); } } - SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats(); - int NSE = sensors.size(); + final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats(); + final int NSE = sensors.size(); for (int ise=0; ise<NSE; ise++) { - Uid.Sensor se = sensors.valueAt(ise); - int sensorNumber = sensors.keyAt(ise); - Timer timer = se.getSensorTime(); + final Uid.Sensor se = sensors.valueAt(ise); + final int sensorNumber = sensors.keyAt(ise); + final Timer timer = se.getSensorTime(); if (timer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; - int count = timer.getCountLocked(which); + final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) + / 1000; + final int count = timer.getCountLocked(which); if (totalTime != 0) { dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count); } } } - Timer vibTimer = u.getVibratorOnTimer(); + final Timer vibTimer = u.getVibratorOnTimer(); if (vibTimer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (vibTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; - int count = vibTimer.getCountLocked(which); + final long totalTime = (vibTimer.getTotalTimeLocked(rawRealtime, which) + 500) + / 1000; + final int count = vibTimer.getCountLocked(which); if (totalTime != 0) { dumpLine(pw, uid, category, VIBRATOR_DATA, totalTime, count); } } - Timer fgTimer = u.getForegroundActivityTimer(); + final Timer fgTimer = u.getForegroundActivityTimer(); if (fgTimer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (fgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; - int count = fgTimer.getCountLocked(which); + final long totalTime = (fgTimer.getTotalTimeLocked(rawRealtime, which) + 500) + / 1000; + final int count = fgTimer.getCountLocked(which); if (totalTime != 0) { dumpLine(pw, uid, category, FOREGROUND_DATA, totalTime, count); } } - Object[] stateTimes = new Object[Uid.NUM_PROCESS_STATE]; + final Object[] stateTimes = new Object[Uid.NUM_PROCESS_STATE]; long totalStateTime = 0; for (int ips=0; ips<Uid.NUM_PROCESS_STATE; ips++) { totalStateTime += u.getProcessStateTime(ips, rawRealtime, which); @@ -2704,50 +2766,48 @@ public abstract class BatteryStats implements Parcelable { dumpLine(pw, uid, category, STATE_TIME_DATA, stateTimes); } - Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); - if (processStats.size() > 0) { - for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent - : processStats.entrySet()) { - Uid.Proc ps = ent.getValue(); - - final long userMillis = ps.getUserTime(which); - final long systemMillis = ps.getSystemTime(which); - final long foregroundMillis = ps.getForegroundTime(which); - final int starts = ps.getStarts(which); - final int numCrashes = ps.getNumCrashes(which); - final int numAnrs = ps.getNumAnrs(which); - - if (userMillis != 0 || systemMillis != 0 || foregroundMillis != 0 - || starts != 0 || numAnrs != 0 || numCrashes != 0) { - dumpLine(pw, uid, category, PROCESS_DATA, ent.getKey(), userMillis, - systemMillis, foregroundMillis, starts, numAnrs, numCrashes); - } + final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats + = u.getProcessStats(); + for (int ipr=processStats.size()-1; ipr>=0; ipr--) { + final Uid.Proc ps = processStats.valueAt(ipr); + + final long userMillis = ps.getUserTime(which); + final long systemMillis = ps.getSystemTime(which); + final long foregroundMillis = ps.getForegroundTime(which); + final int starts = ps.getStarts(which); + final int numCrashes = ps.getNumCrashes(which); + final int numAnrs = ps.getNumAnrs(which); + + if (userMillis != 0 || systemMillis != 0 || foregroundMillis != 0 + || starts != 0 || numAnrs != 0 || numCrashes != 0) { + dumpLine(pw, uid, category, PROCESS_DATA, processStats.keyAt(ipr), userMillis, + systemMillis, foregroundMillis, starts, numAnrs, numCrashes); } } - Map<String, ? extends BatteryStats.Uid.Pkg> packageStats = u.getPackageStats(); - if (packageStats.size() > 0) { - for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg> ent - : packageStats.entrySet()) { - - Uid.Pkg ps = ent.getValue(); - int wakeups = ps.getWakeups(which); - Map<String, ? extends Uid.Pkg.Serv> serviceStats = ps.getServiceStats(); - for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg.Serv> sent - : serviceStats.entrySet()) { - BatteryStats.Uid.Pkg.Serv ss = sent.getValue(); - long startTime = ss.getStartTime(batteryUptime, which); - int starts = ss.getStarts(which); - int launches = ss.getLaunches(which); - if (startTime != 0 || starts != 0 || launches != 0) { - dumpLine(pw, uid, category, APK_DATA, - wakeups, // wakeup alarms - ent.getKey(), // Apk - sent.getKey(), // service - startTime / 1000, // time spent started, in ms - starts, - launches); - } + final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats + = u.getPackageStats(); + for (int ipkg=packageStats.size()-1; ipkg>=0; ipkg--) { + final Uid.Pkg ps = packageStats.valueAt(ipkg); + int wakeups = 0; + final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats(); + for (int iwa=alarms.size()-1; iwa>=0; iwa--) { + wakeups += alarms.valueAt(iwa).getCountLocked(which); + } + final ArrayMap<String, ? extends Uid.Pkg.Serv> serviceStats = ps.getServiceStats(); + for (int isvc=serviceStats.size()-1; isvc>=0; isvc--) { + final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc); + final long startTime = ss.getStartTime(batteryUptime, which); + final int starts = ss.getStarts(which); + final int launches = ss.getLaunches(which); + if (startTime != 0 || starts != 0 || launches != 0) { + dumpLine(pw, uid, category, APK_DATA, + wakeups, // wakeup alarms + packageStats.keyAt(ipkg), // Apk + serviceStats.keyAt(isvc), // service + startTime / 1000, // time spent started, in ms + starts, + launches); } } } @@ -2796,9 +2856,9 @@ public abstract class BatteryStats implements Parcelable { final long batteryTimeRemaining = computeBatteryTimeRemaining(rawRealtime); final long chargeTimeRemaining = computeChargeTimeRemaining(rawRealtime); - StringBuilder sb = new StringBuilder(128); + final StringBuilder sb = new StringBuilder(128); - SparseArray<? extends Uid> uidStats = getUidStats(); + final SparseArray<? extends Uid> uidStats = getUidStats(); final int NU = uidStats.size(); sb.setLength(0); @@ -2849,7 +2909,9 @@ public abstract class BatteryStats implements Parcelable { final long screenOnTime = getScreenOnTime(rawRealtime, which); final long interactiveTime = getInteractiveTime(rawRealtime, which); - final long lowPowerModeEnabledTime = getLowPowerModeEnabledTime(rawRealtime, which); + final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which); + final long deviceIdleModeEnabledTime = getDeviceIdleModeEnabledTime(rawRealtime, which); + final long deviceIdlingTime = getDeviceIdlingTime(rawRealtime, which); final long phoneOnTime = getPhoneOnTime(rawRealtime, which); final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); final long wifiOnTime = getWifiOnTime(rawRealtime, which); @@ -2884,24 +2946,46 @@ public abstract class BatteryStats implements Parcelable { } if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - if (lowPowerModeEnabledTime != 0) { + if (powerSaveModeEnabledTime != 0) { sb.setLength(0); sb.append(prefix); - sb.append(" Low power mode enabled: "); - formatTimeMs(sb, lowPowerModeEnabledTime / 1000); + sb.append(" Power save mode enabled: "); + formatTimeMs(sb, powerSaveModeEnabledTime / 1000); sb.append("("); - sb.append(formatRatioLocked(lowPowerModeEnabledTime, whichBatteryRealtime)); + sb.append(formatRatioLocked(powerSaveModeEnabledTime, whichBatteryRealtime)); sb.append(")"); pw.println(sb.toString()); } + if (deviceIdlingTime != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Device idling: "); + formatTimeMs(sb, deviceIdlingTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(deviceIdlingTime, whichBatteryRealtime)); + sb.append(") "); sb.append(getDeviceIdlingCount(which)); + sb.append("x"); + pw.println(sb.toString()); + } + if (deviceIdleModeEnabledTime != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Idle mode time: "); + formatTimeMs(sb, deviceIdleModeEnabledTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(deviceIdleModeEnabledTime, whichBatteryRealtime)); + sb.append(") "); sb.append(getDeviceIdleModeEnabledCount(which)); + sb.append("x"); + pw.println(sb.toString()); + } if (phoneOnTime != 0) { sb.setLength(0); sb.append(prefix); sb.append(" Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000); sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime)); - sb.append(") "); sb.append(getPhoneOnCount(which)); + sb.append(") "); sb.append(getPhoneOnCount(which)); sb.append("x"); } - int connChanges = getNumConnectivityChange(which); + final int connChanges = getNumConnectivityChange(which); if (connChanges != 0) { pw.print(prefix); pw.print(" Connectivity changes: "); pw.println(connChanges); @@ -2911,50 +2995,48 @@ public abstract class BatteryStats implements Parcelable { long fullWakeLockTimeTotalMicros = 0; long partialWakeLockTimeTotalMicros = 0; - final ArrayList<TimerEntry> timers = new ArrayList<TimerEntry>(); + final ArrayList<TimerEntry> timers = new ArrayList<>(); for (int iu = 0; iu < NU; iu++) { - Uid u = uidStats.valueAt(iu); - - Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); - if (wakelocks.size() > 0) { - for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent - : wakelocks.entrySet()) { - Uid.Wakelock wl = ent.getValue(); - - Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL); - if (fullWakeTimer != null) { - fullWakeLockTimeTotalMicros += fullWakeTimer.getTotalTimeLocked( - rawRealtime, which); - } + final Uid u = uidStats.valueAt(iu); - Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL); - if (partialWakeTimer != null) { - long totalTimeMicros = partialWakeTimer.getTotalTimeLocked( - rawRealtime, which); - if (totalTimeMicros > 0) { - if (reqUid < 0) { - // Only show the ordered list of all wake - // locks if the caller is not asking for data - // about a specific uid. - timers.add(new TimerEntry(ent.getKey(), u.getUid(), - partialWakeTimer, totalTimeMicros)); - } - partialWakeLockTimeTotalMicros += totalTimeMicros; + final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks + = u.getWakelockStats(); + for (int iw=wakelocks.size()-1; iw>=0; iw--) { + final Uid.Wakelock wl = wakelocks.valueAt(iw); + + final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL); + if (fullWakeTimer != null) { + fullWakeLockTimeTotalMicros += fullWakeTimer.getTotalTimeLocked( + rawRealtime, which); + } + + final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL); + if (partialWakeTimer != null) { + final long totalTimeMicros = partialWakeTimer.getTotalTimeLocked( + rawRealtime, which); + if (totalTimeMicros > 0) { + if (reqUid < 0) { + // Only show the ordered list of all wake + // locks if the caller is not asking for data + // about a specific uid. + timers.add(new TimerEntry(wakelocks.keyAt(iw), u.getUid(), + partialWakeTimer, totalTimeMicros)); } + partialWakeLockTimeTotalMicros += totalTimeMicros; } } } } - long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); - long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); - long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); - long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); - long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); - long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); - long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); - long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); + final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); + final long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); + final long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); + final long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); + final long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); + final long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); + final long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); + final long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); if (fullWakeLockTimeTotalMicros != 0) { sb.setLength(0); @@ -3151,9 +3233,9 @@ public abstract class BatteryStats implements Parcelable { if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - final long wifiIdleTimeMs = getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which); - final long wifiRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which); - final long wifiTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which); + final long wifiIdleTimeMs = getWifiControllerActivity(CONTROLLER_IDLE_TIME, which); + final long wifiRxTimeMs = getWifiControllerActivity(CONTROLLER_RX_TIME, which); + final long wifiTxTimeMs = getWifiControllerActivity(CONTROLLER_TX_TIME, which); final long wifiTotalTimeMs = wifiIdleTimeMs + wifiRxTimeMs + wifiTxTimeMs; sb.setLength(0); @@ -3276,7 +3358,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(); } - BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly); + final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly); helper.create(this); helper.refreshStats(which, UserHandle.USER_ALL); List<BatterySipper> sippers = helper.getUsageList(); @@ -3291,7 +3373,7 @@ public abstract class BatteryStats implements Parcelable { } pw.println(); for (int i=0; i<sippers.size(); i++) { - BatterySipper bs = sippers.get(i); + final BatterySipper bs = sippers.get(i); switch (bs.drainType) { case IDLE: pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.value); @@ -3348,7 +3430,7 @@ public abstract class BatteryStats implements Parcelable { pw.print(prefix); pw.println(" Per-app mobile ms per packet:"); long totalTime = 0; for (int i=0; i<sippers.size(); i++) { - BatterySipper bs = sippers.get(i); + final BatterySipper bs = sippers.get(i); sb.setLength(0); sb.append(prefix); sb.append(" Uid "); UserHandle.formatUid(sb, bs.uidObj.getUid()); @@ -3385,12 +3467,14 @@ public abstract class BatteryStats implements Parcelable { }; if (reqUid < 0) { - Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats(); + final Map<String, ? extends BatteryStats.Timer> kernelWakelocks + = getKernelWakelockStats(); if (kernelWakelocks.size() > 0) { - final ArrayList<TimerEntry> ktimers = new ArrayList<TimerEntry>(); - for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) { - BatteryStats.Timer timer = ent.getValue(); - long totalTimeMillis = computeWakeLock(timer, rawRealtime, which); + final ArrayList<TimerEntry> ktimers = new ArrayList<>(); + for (Map.Entry<String, ? extends BatteryStats.Timer> ent + : kernelWakelocks.entrySet()) { + final BatteryStats.Timer timer = ent.getValue(); + final long totalTimeMillis = computeWakeLock(timer, rawRealtime, which); if (totalTimeMillis > 0) { ktimers.add(new TimerEntry(ent.getKey(), 0, timer, totalTimeMillis)); } @@ -3399,7 +3483,7 @@ public abstract class BatteryStats implements Parcelable { Collections.sort(ktimers, timerComparator); pw.print(prefix); pw.println(" All kernel wake locks:"); for (int i=0; i<ktimers.size(); i++) { - TimerEntry timer = ktimers.get(i); + final TimerEntry timer = ktimers.get(i); String linePrefix = ": "; sb.setLength(0); sb.append(prefix); @@ -3435,12 +3519,12 @@ public abstract class BatteryStats implements Parcelable { pw.println(); } - Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats(); + final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats(); if (wakeupReasons.size() > 0) { pw.print(prefix); pw.println(" All wakeup reasons:"); - final ArrayList<TimerEntry> reasons = new ArrayList<TimerEntry>(); + final ArrayList<TimerEntry> reasons = new ArrayList<>(); for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) { - Timer timer = ent.getValue(); + final Timer timer = ent.getValue(); reasons.add(new TimerEntry(ent.getKey(), 0, timer, timer.getCountLocked(which))); } @@ -3466,7 +3550,7 @@ public abstract class BatteryStats implements Parcelable { continue; } - Uid u = uidStats.valueAt(iu); + final Uid u = uidStats.valueAt(iu); pw.print(prefix); pw.print(" "); @@ -3474,20 +3558,20 @@ public abstract class BatteryStats implements Parcelable { pw.println(":"); boolean uidActivity = false; - long mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); - long mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); - long wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); - long wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); - long mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); - long mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); - long uidMobileActiveTime = u.getMobileRadioActiveTime(which); - int uidMobileActiveCount = u.getMobileRadioActiveCount(which); - long wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); - long wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); - long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which); - long wifiScanTime = u.getWifiScanTime(rawRealtime, which); - int wifiScanCount = u.getWifiScanCount(which); - long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); + final long mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); + final long mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); + final long wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); + final long wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which); + final long mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which); + final long mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); + final long uidMobileActiveTime = u.getMobileRadioActiveTime(which); + final int uidMobileActiveCount = u.getMobileRadioActiveCount(which); + final long wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); + final long wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); + final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which); + final long wifiScanTime = u.getWifiScanTime(rawRealtime, which); + final int wifiScanCount = u.getWifiScanCount(which); + final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); if (mobileRxBytes > 0 || mobileTxBytes > 0 || mobileRxPackets > 0 || mobileTxPackets > 0) { @@ -3545,7 +3629,7 @@ public abstract class BatteryStats implements Parcelable { if (u.hasUserActivity()) { boolean hasData = false; for (int i=0; i<Uid.NUM_USER_ACTIVITY_TYPES; i++) { - int val = u.getUserActivityCount(i, which); + final int val = u.getUserActivityCount(i, which); if (val != 0) { if (!hasData) { sb.setLength(0); @@ -3564,125 +3648,121 @@ public abstract class BatteryStats implements Parcelable { } } - Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); - if (wakelocks.size() > 0) { - long totalFull = 0, totalPartial = 0, totalWindow = 0; - int count = 0; - for (Map.Entry<String, ? extends Uid.Wakelock> ent : wakelocks.entrySet()) { - Uid.Wakelock wl = ent.getValue(); - String linePrefix = ": "; + final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks + = u.getWakelockStats(); + long totalFullWakelock = 0, totalPartialWakelock = 0, totalWindowWakelock = 0; + int countWakelock = 0; + for (int iw=wakelocks.size()-1; iw>=0; iw--) { + final Uid.Wakelock wl = wakelocks.valueAt(iw); + String linePrefix = ": "; + sb.setLength(0); + sb.append(prefix); + sb.append(" Wake lock "); + sb.append(wakelocks.keyAt(iw)); + linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), rawRealtime, + "full", which, linePrefix); + linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), rawRealtime, + "partial", which, linePrefix); + linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), rawRealtime, + "window", which, linePrefix); + if (true || !linePrefix.equals(": ")) { + sb.append(" realtime"); + // Only print out wake locks that were held + pw.println(sb.toString()); + uidActivity = true; + countWakelock++; + } + totalFullWakelock += computeWakeLock(wl.getWakeTime(WAKE_TYPE_FULL), + rawRealtime, which); + totalPartialWakelock += computeWakeLock(wl.getWakeTime(WAKE_TYPE_PARTIAL), + rawRealtime, which); + totalWindowWakelock += computeWakeLock(wl.getWakeTime(WAKE_TYPE_WINDOW), + rawRealtime, which); + } + if (countWakelock > 1) { + if (totalFullWakelock != 0 || totalPartialWakelock != 0 + || totalWindowWakelock != 0) { sb.setLength(0); sb.append(prefix); - sb.append(" Wake lock "); - sb.append(ent.getKey()); - linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), rawRealtime, - "full", which, linePrefix); - linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), rawRealtime, - "partial", which, linePrefix); - linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), rawRealtime, - "window", which, linePrefix); - if (true || !linePrefix.equals(": ")) { - sb.append(" realtime"); - // Only print out wake locks that were held - pw.println(sb.toString()); - uidActivity = true; - count++; + sb.append(" TOTAL wake: "); + boolean needComma = false; + if (totalFullWakelock != 0) { + needComma = true; + formatTimeMs(sb, totalFullWakelock); + sb.append("full"); } - totalFull += computeWakeLock(wl.getWakeTime(WAKE_TYPE_FULL), - rawRealtime, which); - totalPartial += computeWakeLock(wl.getWakeTime(WAKE_TYPE_PARTIAL), - rawRealtime, which); - totalWindow += computeWakeLock(wl.getWakeTime(WAKE_TYPE_WINDOW), - rawRealtime, which); - } - if (count > 1) { - if (totalFull != 0 || totalPartial != 0 || totalWindow != 0) { - sb.setLength(0); - sb.append(prefix); - sb.append(" TOTAL wake: "); - boolean needComma = false; - if (totalFull != 0) { - needComma = true; - formatTimeMs(sb, totalFull); - sb.append("full"); - } - if (totalPartial != 0) { - if (needComma) { - sb.append(", "); - } - needComma = true; - formatTimeMs(sb, totalPartial); - sb.append("partial"); + if (totalPartialWakelock != 0) { + if (needComma) { + sb.append(", "); } - if (totalWindow != 0) { - if (needComma) { - sb.append(", "); - } - needComma = true; - formatTimeMs(sb, totalWindow); - sb.append("window"); + needComma = true; + formatTimeMs(sb, totalPartialWakelock); + sb.append("partial"); + } + if (totalWindowWakelock != 0) { + if (needComma) { + sb.append(", "); } - sb.append(" realtime"); - pw.println(sb.toString()); + needComma = true; + formatTimeMs(sb, totalWindowWakelock); + sb.append("window"); } + sb.append(" realtime"); + pw.println(sb.toString()); } } - Map<String, ? extends Timer> syncs = u.getSyncStats(); - if (syncs.size() > 0) { - for (Map.Entry<String, ? extends Timer> ent : syncs.entrySet()) { - Timer timer = ent.getValue(); - // Convert from microseconds to milliseconds with rounding - long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; - int count = timer.getCountLocked(which); - sb.setLength(0); - sb.append(prefix); - sb.append(" Sync "); - sb.append(ent.getKey()); - sb.append(": "); - if (totalTime != 0) { - formatTimeMs(sb, totalTime); - sb.append("realtime ("); - sb.append(count); - sb.append(" times)"); - } else { - sb.append("(not used)"); - } - pw.println(sb.toString()); - uidActivity = true; + final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats(); + for (int isy=syncs.size()-1; isy>=0; isy--) { + final Timer timer = syncs.valueAt(isy); + // Convert from microseconds to milliseconds with rounding + final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; + final int count = timer.getCountLocked(which); + sb.setLength(0); + sb.append(prefix); + sb.append(" Sync "); + sb.append(syncs.keyAt(isy)); + sb.append(": "); + if (totalTime != 0) { + formatTimeMs(sb, totalTime); + sb.append("realtime ("); + sb.append(count); + sb.append(" times)"); + } else { + sb.append("(not used)"); } + pw.println(sb.toString()); + uidActivity = true; } - Map<String, ? extends Timer> jobs = u.getJobStats(); - if (jobs.size() > 0) { - for (Map.Entry<String, ? extends Timer> ent : jobs.entrySet()) { - Timer timer = ent.getValue(); - // Convert from microseconds to milliseconds with rounding - long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; - int count = timer.getCountLocked(which); - sb.setLength(0); - sb.append(prefix); - sb.append(" Job "); - sb.append(ent.getKey()); - sb.append(": "); - if (totalTime != 0) { - formatTimeMs(sb, totalTime); - sb.append("realtime ("); - sb.append(count); - sb.append(" times)"); - } else { - sb.append("(not used)"); - } - pw.println(sb.toString()); - uidActivity = true; + final ArrayMap<String, ? extends Timer> jobs = u.getJobStats(); + for (int ij=jobs.size()-1; ij>=0; ij--) { + final Timer timer = jobs.valueAt(ij); + // Convert from microseconds to milliseconds with rounding + final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; + final int count = timer.getCountLocked(which); + sb.setLength(0); + sb.append(prefix); + sb.append(" Job "); + sb.append(jobs.keyAt(ij)); + sb.append(": "); + if (totalTime != 0) { + formatTimeMs(sb, totalTime); + sb.append("realtime ("); + sb.append(count); + sb.append(" times)"); + } else { + sb.append("(not used)"); } + pw.println(sb.toString()); + uidActivity = true; } - SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats(); - int NSE = sensors.size(); + final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats(); + final int NSE = sensors.size(); for (int ise=0; ise<NSE; ise++) { - Uid.Sensor se = sensors.valueAt(ise); - int sensorNumber = sensors.keyAt(ise); + final Uid.Sensor se = sensors.valueAt(ise); + final int sensorNumber = sensors.keyAt(ise); sb.setLength(0); sb.append(prefix); sb.append(" Sensor "); @@ -3694,12 +3774,12 @@ public abstract class BatteryStats implements Parcelable { } sb.append(": "); - Timer timer = se.getSensorTime(); + final Timer timer = se.getSensorTime(); if (timer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (timer.getTotalTimeLocked( + final long totalTime = (timer.getTotalTimeLocked( rawRealtime, which) + 500) / 1000; - int count = timer.getCountLocked(which); + final int count = timer.getCountLocked(which); //timer.logState(); if (totalTime != 0) { formatTimeMs(sb, totalTime); @@ -3717,12 +3797,12 @@ public abstract class BatteryStats implements Parcelable { uidActivity = true; } - Timer vibTimer = u.getVibratorOnTimer(); + final Timer vibTimer = u.getVibratorOnTimer(); if (vibTimer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (vibTimer.getTotalTimeLocked( + final long totalTime = (vibTimer.getTotalTimeLocked( rawRealtime, which) + 500) / 1000; - int count = vibTimer.getCountLocked(which); + final int count = vibTimer.getCountLocked(which); //timer.logState(); if (totalTime != 0) { sb.setLength(0); @@ -3737,11 +3817,12 @@ public abstract class BatteryStats implements Parcelable { } } - Timer fgTimer = u.getForegroundActivityTimer(); + final Timer fgTimer = u.getForegroundActivityTimer(); if (fgTimer != null) { // Convert from microseconds to milliseconds with rounding - long totalTime = (fgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; - int count = fgTimer.getCountLocked(which); + final long totalTime = (fgTimer.getTotalTimeLocked(rawRealtime, which) + 500) + / 1000; + final int count = fgTimer.getCountLocked(which); if (totalTime != 0) { sb.setLength(0); sb.append(prefix); @@ -3771,125 +3852,121 @@ public abstract class BatteryStats implements Parcelable { } } - Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); - if (processStats.size() > 0) { - for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent - : processStats.entrySet()) { - Uid.Proc ps = ent.getValue(); - long userTime; - long systemTime; - long foregroundTime; - int starts; - int numExcessive; - - userTime = ps.getUserTime(which); - systemTime = ps.getSystemTime(which); - foregroundTime = ps.getForegroundTime(which); - starts = ps.getStarts(which); - final int numCrashes = ps.getNumCrashes(which); - final int numAnrs = ps.getNumAnrs(which); - numExcessive = which == STATS_SINCE_CHARGED - ? ps.countExcessivePowers() : 0; - - if (userTime != 0 || systemTime != 0 || foregroundTime != 0 || starts != 0 - || numExcessive != 0 || numCrashes != 0 || numAnrs != 0) { - sb.setLength(0); - sb.append(prefix); sb.append(" Proc "); - sb.append(ent.getKey()); sb.append(":\n"); - sb.append(prefix); sb.append(" CPU: "); - formatTimeMs(sb, userTime); sb.append("usr + "); - formatTimeMs(sb, systemTime); sb.append("krn ; "); - formatTimeMs(sb, foregroundTime); sb.append("fg"); - if (starts != 0 || numCrashes != 0 || numAnrs != 0) { - sb.append("\n"); sb.append(prefix); sb.append(" "); - boolean hasOne = false; - if (starts != 0) { - hasOne = true; - sb.append(starts); sb.append(" starts"); - } - if (numCrashes != 0) { - if (hasOne) { - sb.append(", "); - } - hasOne = true; - sb.append(numCrashes); sb.append(" crashes"); - } - if (numAnrs != 0) { - if (hasOne) { - sb.append(", "); - } - sb.append(numAnrs); sb.append(" anrs"); + final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats + = u.getProcessStats(); + for (int ipr=processStats.size()-1; ipr>=0; ipr--) { + final Uid.Proc ps = processStats.valueAt(ipr); + long userTime; + long systemTime; + long foregroundTime; + int starts; + int numExcessive; + + userTime = ps.getUserTime(which); + systemTime = ps.getSystemTime(which); + foregroundTime = ps.getForegroundTime(which); + starts = ps.getStarts(which); + final int numCrashes = ps.getNumCrashes(which); + final int numAnrs = ps.getNumAnrs(which); + numExcessive = which == STATS_SINCE_CHARGED + ? ps.countExcessivePowers() : 0; + + if (userTime != 0 || systemTime != 0 || foregroundTime != 0 || starts != 0 + || numExcessive != 0 || numCrashes != 0 || numAnrs != 0) { + sb.setLength(0); + sb.append(prefix); sb.append(" Proc "); + sb.append(processStats.keyAt(ipr)); sb.append(":\n"); + sb.append(prefix); sb.append(" CPU: "); + formatTimeMs(sb, userTime); sb.append("usr + "); + formatTimeMs(sb, systemTime); sb.append("krn ; "); + formatTimeMs(sb, foregroundTime); sb.append("fg"); + if (starts != 0 || numCrashes != 0 || numAnrs != 0) { + sb.append("\n"); sb.append(prefix); sb.append(" "); + boolean hasOne = false; + if (starts != 0) { + hasOne = true; + sb.append(starts); sb.append(" starts"); + } + if (numCrashes != 0) { + if (hasOne) { + sb.append(", "); } + hasOne = true; + sb.append(numCrashes); sb.append(" crashes"); } - pw.println(sb.toString()); - for (int e=0; e<numExcessive; e++) { - Uid.Proc.ExcessivePower ew = ps.getExcessivePower(e); - if (ew != null) { - pw.print(prefix); pw.print(" * Killed for "); - if (ew.type == Uid.Proc.ExcessivePower.TYPE_WAKE) { - pw.print("wake lock"); - } else if (ew.type == Uid.Proc.ExcessivePower.TYPE_CPU) { - pw.print("cpu"); - } else { - pw.print("unknown"); - } - pw.print(" use: "); - TimeUtils.formatDuration(ew.usedTime, pw); - pw.print(" over "); - TimeUtils.formatDuration(ew.overTime, pw); - if (ew.overTime != 0) { - pw.print(" ("); - pw.print((ew.usedTime*100)/ew.overTime); - pw.println("%)"); - } + if (numAnrs != 0) { + if (hasOne) { + sb.append(", "); } + sb.append(numAnrs); sb.append(" anrs"); + } + } + pw.println(sb.toString()); + for (int e=0; e<numExcessive; e++) { + Uid.Proc.ExcessivePower ew = ps.getExcessivePower(e); + if (ew != null) { + pw.print(prefix); pw.print(" * Killed for "); + if (ew.type == Uid.Proc.ExcessivePower.TYPE_WAKE) { + pw.print("wake lock"); + } else if (ew.type == Uid.Proc.ExcessivePower.TYPE_CPU) { + pw.print("cpu"); + } else { + pw.print("unknown"); + } + pw.print(" use: "); + TimeUtils.formatDuration(ew.usedTime, pw); + pw.print(" over "); + TimeUtils.formatDuration(ew.overTime, pw); + if (ew.overTime != 0) { + pw.print(" ("); + pw.print((ew.usedTime*100)/ew.overTime); + pw.println("%)"); + } } - uidActivity = true; } + uidActivity = true; } } - Map<String, ? extends BatteryStats.Uid.Pkg> packageStats = u.getPackageStats(); - if (packageStats.size() > 0) { - for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg> ent - : packageStats.entrySet()) { - pw.print(prefix); pw.print(" Apk "); pw.print(ent.getKey()); pw.println(":"); - boolean apkActivity = false; - Uid.Pkg ps = ent.getValue(); - int wakeups = ps.getWakeups(which); - if (wakeups != 0) { - pw.print(prefix); pw.print(" "); - pw.print(wakeups); pw.println(" wakeup alarms"); + final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats + = u.getPackageStats(); + for (int ipkg=packageStats.size()-1; ipkg>=0; ipkg--) { + pw.print(prefix); pw.print(" Apk "); pw.print(packageStats.keyAt(ipkg)); + pw.println(":"); + boolean apkActivity = false; + final Uid.Pkg ps = packageStats.valueAt(ipkg); + final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats(); + for (int iwa=alarms.size()-1; iwa>=0; iwa--) { + pw.print(prefix); pw.print(" Wakeup alarm "); + pw.print(alarms.keyAt(iwa)); pw.print(": "); + pw.print(alarms.valueAt(iwa).getCountLocked(which)); + pw.println(" times"); + apkActivity = true; + } + final ArrayMap<String, ? extends Uid.Pkg.Serv> serviceStats = ps.getServiceStats(); + for (int isvc=serviceStats.size()-1; isvc>=0; isvc--) { + final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc); + final long startTime = ss.getStartTime(batteryUptime, which); + final int starts = ss.getStarts(which); + final int launches = ss.getLaunches(which); + if (startTime != 0 || starts != 0 || launches != 0) { + sb.setLength(0); + sb.append(prefix); sb.append(" Service "); + sb.append(serviceStats.keyAt(isvc)); sb.append(":\n"); + sb.append(prefix); sb.append(" Created for: "); + formatTimeMs(sb, startTime / 1000); + sb.append("uptime\n"); + sb.append(prefix); sb.append(" Starts: "); + sb.append(starts); + sb.append(", launches: "); sb.append(launches); + pw.println(sb.toString()); apkActivity = true; } - Map<String, ? extends Uid.Pkg.Serv> serviceStats = ps.getServiceStats(); - if (serviceStats.size() > 0) { - for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg.Serv> sent - : serviceStats.entrySet()) { - BatteryStats.Uid.Pkg.Serv ss = sent.getValue(); - long startTime = ss.getStartTime(batteryUptime, which); - int starts = ss.getStarts(which); - int launches = ss.getLaunches(which); - if (startTime != 0 || starts != 0 || launches != 0) { - sb.setLength(0); - sb.append(prefix); sb.append(" Service "); - sb.append(sent.getKey()); sb.append(":\n"); - sb.append(prefix); sb.append(" Created for: "); - formatTimeMs(sb, startTime / 1000); - sb.append("uptime\n"); - sb.append(prefix); sb.append(" Starts: "); - sb.append(starts); - sb.append(", launches: "); sb.append(launches); - pw.println(sb.toString()); - apkActivity = true; - } - } - } - if (!apkActivity) { - pw.print(prefix); pw.println(" (nothing executed)"); - } - uidActivity = true; } + if (!apkActivity) { + pw.print(prefix); pw.println(" (nothing executed)"); + } + uidActivity = true; } if (!uidActivity) { pw.print(prefix); pw.println(" (nothing executed)"); @@ -4363,6 +4440,11 @@ public abstract class BatteryStats implements Parcelable { } else { lineArgs[3] = ""; } + if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) == 0) { + lineArgs[3] = (initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 ? "i+" : "i-"; + } else { + lineArgs[3] = ""; + } dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs); } else { pw.print(prefix); @@ -4387,6 +4469,12 @@ public abstract class BatteryStats implements Parcelable { ? "power-save-on" : "power-save-off"); haveModes = true; } + if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) == 0) { + pw.print(haveModes ? ", " : " ("); + pw.print((initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 + ? "device-idle-on" : "device-idle-off"); + haveModes = true; + } if (haveModes) { pw.print(")"); } @@ -4396,7 +4484,6 @@ public abstract class BatteryStats implements Parcelable { return true; } - public static final int DUMP_UNPLUGGED_ONLY = 1<<0; public static final int DUMP_CHARGED_ONLY = 1<<1; public static final int DUMP_DAILY_ONLY = 1<<2; public static final int DUMP_HISTORY_ONLY = 1<<3; @@ -4518,6 +4605,23 @@ public abstract class BatteryStats implements Parcelable { } } + private void dumpDailyPackageChanges(PrintWriter pw, String prefix, + ArrayList<PackageChange> changes) { + if (changes == null) { + return; + } + pw.print(prefix); pw.println("Package changes:"); + for (int i=0; i<changes.size(); i++) { + PackageChange pc = changes.get(i); + if (pc.mUpdate) { + pw.print(prefix); pw.print(" Update "); pw.print(pc.mPackageName); + pw.print(" vers="); pw.println(pc.mVersionCode); + } else { + pw.print(prefix); pw.print(" Uninstall "); pw.println(pc.mPackageName); + } + } + } + /** * Dumps a human-readable summary of the battery statistics to the given PrintWriter. * @@ -4528,7 +4632,7 @@ public abstract class BatteryStats implements Parcelable { prepareForDumpLocked(); final boolean filtering = (flags - & (DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0; + & (DUMP_HISTORY_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0; if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) { final long historyTotalSize = getHistoryTotalSize(); @@ -4572,7 +4676,7 @@ public abstract class BatteryStats implements Parcelable { } } - if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) { + if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) { return; } @@ -4648,8 +4752,9 @@ public abstract class BatteryStats implements Parcelable { int[] outInt = new int[1]; LevelStepTracker dsteps = getDailyDischargeLevelStepTracker(); LevelStepTracker csteps = getDailyChargeLevelStepTracker(); - if (dsteps.mNumStepDurations > 0 || csteps.mNumStepDurations > 0) { - if ((flags&DUMP_DAILY_ONLY) != 0) { + ArrayList<PackageChange> pkgc = getDailyPackageChanges(); + if (dsteps.mNumStepDurations > 0 || csteps.mNumStepDurations > 0 || pkgc != null) { + if ((flags&DUMP_DAILY_ONLY) != 0 || !filtering) { if (dumpDurationSteps(pw, " ", " Current daily discharge step durations:", dsteps, false)) { dumpDailyLevelStepSummary(pw, " ", "Discharge", dsteps, @@ -4660,6 +4765,7 @@ public abstract class BatteryStats implements Parcelable { dumpDailyLevelStepSummary(pw, " ", "Charge", csteps, sb, outInt); } + dumpDailyPackageChanges(pw, " ", pkgc); } else { pw.println(" Current daily steps:"); dumpDailyLevelStepSummary(pw, " ", "Discharge", dsteps, @@ -4680,7 +4786,7 @@ public abstract class BatteryStats implements Parcelable { pw.print(" to "); pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", dit.mEndTime).toString()); pw.println(":"); - if ((flags&DUMP_DAILY_ONLY) != 0) { + if ((flags&DUMP_DAILY_ONLY) != 0 || !filtering) { if (dumpDurationSteps(pw, " ", " Discharge step durations:", dit.mDischargeSteps, false)) { dumpDailyLevelStepSummary(pw, " ", "Discharge", dit.mDischargeSteps, @@ -4691,6 +4797,7 @@ public abstract class BatteryStats implements Parcelable { dumpDailyLevelStepSummary(pw, " ", "Charge", dit.mChargeSteps, sb, outInt); } + dumpDailyPackageChanges(pw, " ", dit.mPackageChanges); } else { dumpDailyLevelStepSummary(pw, " ", "Discharge", dit.mDischargeSteps, sb, outInt); @@ -4708,11 +4815,6 @@ public abstract class BatteryStats implements Parcelable { (flags&DUMP_DEVICE_WIFI_ONLY) != 0); pw.println(); } - if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) { - pw.println("Statistics since last unplugged:"); - dumpLocked(context, pw, "", STATS_SINCE_UNPLUGGED, reqUid, - (flags&DUMP_DEVICE_WIFI_ONLY) != 0); - } } @SuppressWarnings("unused") @@ -4721,12 +4823,12 @@ public abstract class BatteryStats implements Parcelable { prepareForDumpLocked(); dumpLine(pw, 0 /* uid */, "i" /* category */, VERSION_DATA, - "12", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion()); + "13", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion()); long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); final boolean filtering = (flags & - (DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0; + (DUMP_HISTORY_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0; if ((flags&DUMP_INCLUDE_HISTORY) != 0 || (flags&DUMP_HISTORY_ONLY) != 0) { if (startIteratingHistoryLocked()) { @@ -4752,7 +4854,7 @@ public abstract class BatteryStats implements Parcelable { } } - if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) { + if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) { return; } @@ -4802,9 +4904,5 @@ public abstract class BatteryStats implements Parcelable { dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1, (flags&DUMP_DEVICE_WIFI_ONLY) != 0); } - if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) { - dumpCheckinLocked(context, pw, STATS_SINCE_UNPLUGGED, -1, - (flags&DUMP_DEVICE_WIFI_ONLY) != 0); - } } } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 16dac7d..804d3d0 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -43,6 +43,7 @@ interface IPowerManager boolean isInteractive(); boolean isPowerSaveMode(); boolean setPowerSaveMode(boolean mode); + boolean isDeviceIdleMode(); void reboot(boolean confirm, String reason, boolean wait); void shutdown(boolean confirm, boolean wait); @@ -50,6 +51,7 @@ interface IPowerManager void setStayOnSetting(int val); void boostScreenBrightness(long time); + boolean isScreenBrightnessBoosted(); // temporarily overrides the screen brightness settings to allow the user to // see the effect of a settings change without applying it immediately diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index e303f61..01c9a21 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -364,6 +364,12 @@ public final class PowerManager { public static final int GO_TO_SLEEP_REASON_HDMI = 5; /** + * Go to sleep reason code: Going to sleep due to the sleep button being pressed. + * @hide + */ + public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6; + + /** * Go to sleep flag: Skip dozing state and directly go to full sleep. * @hide */ @@ -706,6 +712,22 @@ public final class PowerManager { } /** + * Returns whether the screen brightness is currently boosted to maximum, caused by a call + * to {@link #boostScreenBrightness(long)}. + * @return {@code True} if the screen brightness is currently boosted. {@code False} otherwise. + * + * @hide + */ + @SystemApi + public boolean isScreenBrightnessBoosted() { + try { + return mService.isScreenBrightnessBoosted(); + } catch (RemoteException e) { + return false; + } + } + + /** * Sets the brightness of the backlights (screen, keyboard, button). * <p> * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. @@ -850,6 +872,23 @@ public final class PowerManager { } /** + * Returns true if the device is currently in idle mode. This happens when a device + * has been sitting unused and unmoving for a sufficiently long period of time, so that + * it decides to go into a lower power-use state. This may involve things like turning + * off network access to apps. You can monitor for changes to this state with + * {@link #ACTION_DEVICE_IDLE_MODE_CHANGED}. + * + * @return Returns true if currently in low power mode, else false. + */ + public boolean isDeviceIdleMode() { + try { + return mService.isDeviceIdleMode(); + } catch (RemoteException e) { + return false; + } + } + + /** * Turn off the device. * * @param confirm If true, shows a shutdown confirmation dialog. @@ -873,6 +912,14 @@ public final class PowerManager { = "android.os.action.POWER_SAVE_MODE_CHANGED"; /** + * Intent that is broadcast when the state of {@link #isDeviceIdleMode()} changes. + * This broadcast is only sent to registered receivers. + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_IDLE_MODE_CHANGED + = "android.os.action.DEVICE_IDLE_MODE_CHANGED"; + + /** * Intent that is broadcast when the state of {@link #isPowerSaveMode()} is about to change. * This broadcast is only sent to registered receivers. * @@ -886,6 +933,16 @@ public final class PowerManager { public static final String EXTRA_POWER_SAVE_MODE = "mode"; /** + * Intent that is broadcast when the state of {@link #isScreenBrightnessBoosted()} has changed. + * This broadcast is only sent to registered receivers. + * + * @hide + **/ + @SystemApi + public static final String ACTION_SCREEN_BRIGHTNESS_BOOST_CHANGED + = "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED"; + + /** * A wake lock is a mechanism to indicate that your application needs * to have the device stay on. * <p> diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 6f31768..00ab262 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -117,7 +117,7 @@ public abstract class PowerManagerInternal { /** * Used by the dream manager to override certain properties while dozing. * - * @param screenState The overridden screen state, or {@link Display.STATE_UNKNOWN} + * @param screenState The overridden screen state, or {@link Display#STATE_UNKNOWN} * to disable the override. * @param screenBrightness The overridden screen brightness, or * {@link PowerManager#BRIGHTNESS_DEFAULT} to disable the override. @@ -132,4 +132,6 @@ public abstract class PowerManagerInternal { public interface LowPowerModeListener { public void onLowPowerModeChanged(boolean enabled); } + + public abstract void setDeviceIdleMode(boolean enabled); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 4834f97..0de9c70 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -631,6 +631,9 @@ public class Process { if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) { argsForZygote.add("--enable-checkjni"); } + if ((debugFlags & Zygote.DEBUG_ENABLE_JIT) != 0) { + argsForZygote.add("--enable-jit"); + } if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) { argsForZygote.add("--enable-assert"); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 706e0d0..3601a1c 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -400,6 +400,18 @@ public class UserManager { public static final String DISALLOW_WALLPAPER = "no_wallpaper"; /** + * Specifies if the user is not allowed to reboot the device into safe boot mode. + * This can only be set by device owners and profile owners on the primary user. + * The default value is <code>false</code>. + * + * <p/>Key for user restrictions. + * <p/>Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_SAFE_BOOT = "no_safe_boot"; + + /** * Application restriction key that is used to indicate the pending arrival * of real restrictions for the app. * diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 116110e..6209c2a 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -888,6 +888,21 @@ public interface IMountService extends IInterface { } return; } + + @Override + public void waitForAsecScan() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_waitForAsecScan, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return; + } } private static final String DESCRIPTOR = "IMountService"; @@ -978,6 +993,8 @@ public interface IMountService extends IInterface { static final int TRANSACTION_runMaintenance = IBinder.FIRST_CALL_TRANSACTION + 42; + static final int TRANSACTION_waitForAsecScan = IBinder.FIRST_CALL_TRANSACTION + 43; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1396,6 +1413,12 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_waitForAsecScan: { + data.enforceInterface(DESCRIPTOR); + waitForAsecScan(); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1680,4 +1703,6 @@ public interface IMountService extends IInterface { * @throws RemoteException */ public void runMaintenance() throws RemoteException; + + public void waitForAsecScan() throws RemoteException; } diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index ccf2cfa..f32e8cf 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -706,8 +706,10 @@ public class Preference implements Comparable<Preference> { * @param iconResId The icon as a resource ID. */ public void setIcon(@DrawableRes int iconResId) { - mIconResId = iconResId; - setIcon(mContext.getDrawable(iconResId)); + if (mIconResId != iconResId) { + mIconResId = iconResId; + setIcon(mContext.getDrawable(iconResId)); + } } /** diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java index e4e753e..44e6410 100644 --- a/core/java/android/print/PrintDocumentInfo.java +++ b/core/java/android/print/PrintDocumentInfo.java @@ -212,7 +212,7 @@ public final class PrintDocumentInfo implements Parcelable { result = prime * result + mContentType; result = prime * result + mPageCount; result = prime * result + (int) mDataSize; - result = prime * result + (int) mDataSize >> 32; + result = prime * result + (int) (mDataSize >> 32); return result; } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 91a19dc..e4a6f07 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1516,8 +1516,14 @@ public final class ContactsContract { /** * Build a {@link #CONTENT_LOOKUP_URI} lookup {@link Uri} using the * given {@link ContactsContract.Contacts#_ID} and {@link #LOOKUP_KEY}. + * <p> + * Returns null if unable to construct a valid lookup URI from the + * provided parameters. */ public static Uri getLookupUri(long contactId, String lookupKey) { + if (TextUtils.isEmpty(lookupKey)) { + return null; + } return ContentUris.withAppendedId(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId); } @@ -4803,6 +4809,14 @@ public final class ContactsContract { Uri.withAppendedPath(AUTHORITY_URI, "raw_contact_entities"); /** + * The content:// style URI for this table in corp profile + * + * @hide + */ + public static final Uri CORP_CONTENT_URI = + Uri.withAppendedPath(AUTHORITY_URI, "raw_contact_entities_corp"); + + /** * The content:// style URI for this table, specific to the user's profile. */ public static final Uri PROFILE_CONTENT_URI = @@ -4994,14 +5008,16 @@ public final class ContactsContract { "phone_lookup"); /** - * URI used for the "enterprise caller-id". + * <p>URI used for the "enterprise caller-id".</p> * + * <p> * It supports the same semantics as {@link #CONTENT_FILTER_URI} and returns the same * columns. If the device has no corp profile that is linked to the current profile, it * behaves in the exact same way as {@link #CONTENT_FILTER_URI}. If there is a corp profile * linked to the current profile, it first queries against the personal contact database, * and if no matching contacts are found there, then queries against the * corp contacts database. + * </p> * <p> * If a result is from the corp profile, it makes the following changes to the data: * <ul> @@ -5984,6 +6000,45 @@ public final class ContactsContract { "lookup"); /** + * <p>URI used for enterprise email lookup.</p> + * + * <p> + * It supports the same semantics as {@link #CONTENT_LOOKUP_URI} and returns the same + * columns. If the device has no corp profile that is linked to the current profile, it + * behaves in the exact same way as {@link #CONTENT_LOOKUP_URI}. If there is a + * corp profile linked to the current profile, it first queries against the personal contact database, + * and if no matching contacts are found there, then queries against the + * corp contacts database. + * </p> + * <p> + * If a result is from the corp profile, it makes the following changes to the data: + * <ul> + * <li> + * {@link #PHOTO_THUMBNAIL_URI} and {@link #PHOTO_URI} will be rewritten to special + * URIs. Use {@link ContentResolver#openAssetFileDescriptor} or its siblings to + * load pictures from them. + * {@link #PHOTO_ID} and {@link #PHOTO_FILE_ID} will be set to null. Do not + * use them. + * </li> + * <li> + * Corp contacts will get artificial {@link #CONTACT_ID}s. In order to tell whether + * a contact + * is from the corp profile, use + * {@link ContactsContract.Contacts#isEnterpriseContactId(long)}. + * </li> + * </ul> + * <p> + * This URI does NOT support selection nor order-by. + * + * <pre> + * Uri lookupUri = Uri.withAppendedPath(Email.ENTERPRISE_CONTENT_LOOKUP_URI, + * Uri.encode(email)); + * </pre> + */ + public static final Uri ENTERPRISE_CONTENT_LOOKUP_URI = + Uri.withAppendedPath(CONTENT_URI, "lookup_enterprise"); + + /** * <p> * The content:// style URL for email lookup using a filter. The filter returns * records of MIME type {@link #CONTENT_ITEM_TYPE}. The filter is applied diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 3813277..de536bd 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5375,6 +5375,7 @@ public final class Settings { BACKUP_AUTO_RESTORE, ENABLED_ACCESSIBILITY_SERVICES, ENABLED_NOTIFICATION_LISTENERS, + ENABLED_INPUT_METHODS, TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, TOUCH_EXPLORATION_ENABLED, ACCESSIBILITY_ENABLED, @@ -5963,6 +5964,14 @@ public final class Settings { "hdmi_control_auto_device_off_enabled"; /** + * Whether to use the DHCP client from Lollipop and earlier instead of the newer Android DHCP + * client. + * (0 = false, 1 = true) + * @hide + */ + public static final String LEGACY_DHCP_CLIENT = "legacy_dhcp_client"; + + /** * Whether TV will switch to MHL port when a mobile device is plugged in. * (0 = false, 1 = true) * @hide diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index ac6bbb7..d24bc13 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -19,6 +19,7 @@ package android.security; import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterBlob; import android.security.keymaster.OperationResult; import android.security.KeystoreArguments; @@ -61,11 +62,12 @@ interface IKeystoreService { int addRngEntropy(in byte[] data); int generateKey(String alias, in KeymasterArguments arguments, int uid, int flags, out KeyCharacteristics characteristics); - int getKeyCharacteristics(String alias, in byte[] clientId, - in byte[] appId, out KeyCharacteristics characteristics); + int getKeyCharacteristics(String alias, in KeymasterBlob clientId, in KeymasterBlob appId, + out KeyCharacteristics characteristics); int importKey(String alias, in KeymasterArguments arguments, int format, in byte[] keyData, int uid, int flags, out KeyCharacteristics characteristics); - ExportResult exportKey(String alias, int format, in byte[] clientId, in byte[] appId); + ExportResult exportKey(String alias, int format, in KeymasterBlob clientId, + in KeymasterBlob appId); OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable, in KeymasterArguments params, out KeymasterArguments operationParams); OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input); diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java new file mode 100644 index 0000000..0b3bf45 --- /dev/null +++ b/core/java/android/security/NetworkSecurityPolicy.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * Network security policy. + * + * <p>Network stacks/components should honor this policy to make it possible to centrally control + * the relevant aspects of network security behavior. + * + * <p>The policy currently consists of a single flag: whether cleartext network traffic is + * permitted. See {@link #isCleartextTrafficPermitted()}. + */ +public class NetworkSecurityPolicy { + + private static final NetworkSecurityPolicy INSTANCE = new NetworkSecurityPolicy(); + + private NetworkSecurityPolicy() {} + + /** + * Gets the policy for this process. + * + * <p>It's fine to cache this reference. Any changes to the policy will be immediately visible + * through the reference. + */ + public static NetworkSecurityPolicy getInstance() { + return INSTANCE; + } + + /** + * Returns whether cleartext network traffic (e.g. HTTP, FTP, WebSockets, XMPP, IMAP, SMTP -- + * without TLS or STARTTLS) is permitted for this process. + * + * <p>When cleartext network traffic is not permitted, the platform's components (e.g. HTTP and + * FTP stacks, {@link android.webkit.WebView}, {@link android.media.MediaPlayer}) will refuse + * this process's requests to use cleartext traffic. Third-party libraries are strongly + * encouraged to honor this setting as well. + * + * <p>This flag is honored on a best effort basis because it's impossible to prevent all + * cleartext traffic from Android applications given the level of access provided to them. For + * example, there's no expectation that the {@link java.net.Socket} API will honor this flag + * because it cannot determine whether its traffic is in cleartext. However, most network + * traffic from applications is handled by higher-level network stacks/components which can + * honor this aspect of the policy. + */ + public boolean isCleartextTrafficPermitted() { + return libcore.net.NetworkSecurityPolicy.isCleartextTrafficPermitted(); + } + + /** + * Sets whether cleartext network traffic is permitted for this process. + * + * <p>This method is used by the platform early on in the application's initialization to set + * the policy. + * + * @hide + */ + public void setCleartextTrafficPermitted(boolean permitted) { + libcore.net.NetworkSecurityPolicy.setCleartextTrafficPermitted(permitted); + } +} diff --git a/core/java/android/security/keymaster/KeymasterBlob.aidl b/core/java/android/security/keymaster/KeymasterBlob.aidl new file mode 100644 index 0000000..8f70f7c --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterBlob.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +/* @hide */ +parcelable KeymasterBlob; diff --git a/core/java/android/security/keymaster/KeymasterBlob.java b/core/java/android/security/keymaster/KeymasterBlob.java new file mode 100644 index 0000000..cb95604 --- /dev/null +++ b/core/java/android/security/keymaster/KeymasterBlob.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keymaster; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +public class KeymasterBlob implements Parcelable { + public byte[] blob; + + public KeymasterBlob(byte[] blob) { + this.blob = blob; + } + public static final Parcelable.Creator<KeymasterBlob> CREATOR = new + Parcelable.Creator<KeymasterBlob>() { + public KeymasterBlob createFromParcel(Parcel in) { + return new KeymasterBlob(in); + } + + public KeymasterBlob[] newArray(int length) { + return new KeymasterBlob[length]; + } + }; + + protected KeymasterBlob(Parcel in) { + blob = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeByteArray(blob); + } +} diff --git a/core/java/android/security/keymaster/KeymasterBlobArgument.java b/core/java/android/security/keymaster/KeymasterBlobArgument.java index 9af4445..7d587bf 100644 --- a/core/java/android/security/keymaster/KeymasterBlobArgument.java +++ b/core/java/android/security/keymaster/KeymasterBlobArgument.java @@ -26,6 +26,13 @@ class KeymasterBlobArgument extends KeymasterArgument { public KeymasterBlobArgument(int tag, byte[] blob) { super(tag); + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_BIGNUM: + case KeymasterDefs.KM_BYTES: + break; // OK. + default: + throw new IllegalArgumentException("Bad blob tag " + tag); + } this.blob = blob; } diff --git a/core/java/android/security/keymaster/KeymasterBooleanArgument.java b/core/java/android/security/keymaster/KeymasterBooleanArgument.java index 5481e8f..9c03674 100644 --- a/core/java/android/security/keymaster/KeymasterBooleanArgument.java +++ b/core/java/android/security/keymaster/KeymasterBooleanArgument.java @@ -28,6 +28,12 @@ class KeymasterBooleanArgument extends KeymasterArgument { public KeymasterBooleanArgument(int tag) { super(tag); + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_BOOL: + break; // OK. + default: + throw new IllegalArgumentException("Bad bool tag " + tag); + } } public KeymasterBooleanArgument(int tag, Parcel in) { diff --git a/core/java/android/security/keymaster/KeymasterDateArgument.java b/core/java/android/security/keymaster/KeymasterDateArgument.java index 310f546..bffd24d 100644 --- a/core/java/android/security/keymaster/KeymasterDateArgument.java +++ b/core/java/android/security/keymaster/KeymasterDateArgument.java @@ -27,6 +27,12 @@ class KeymasterDateArgument extends KeymasterArgument { public KeymasterDateArgument(int tag, Date date) { super(tag); + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_DATE: + break; // OK. + default: + throw new IllegalArgumentException("Bad date tag " + tag); + } this.date = date; } diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 88cad79..e653b74 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -37,6 +37,7 @@ public final class KeymasterDefs { public static final int KM_BOOL = 7 << 28; public static final int KM_BIGNUM = 8 << 28; public static final int KM_BYTES = 9 << 28; + public static final int KM_LONG_REP = 10 << 28; // Tag values. public static final int KM_TAG_INVALID = KM_INVALID | 0; @@ -66,9 +67,10 @@ public final class KeymasterDefs { public static final int KM_TAG_ALL_USERS = KM_BOOL | 500; public static final int KM_TAG_USER_ID = KM_INT | 501; - public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 502; - public static final int KM_TAG_USER_AUTH_ID = KM_INT_REP | 503; - public static final int KM_TAG_AUTH_TIMEOUT = KM_INT | 504; + public static final int KM_TAG_USER_SECURE_ID = KM_LONG_REP | 502; + public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503; + public static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504; + public static final int KM_TAG_AUTH_TIMEOUT = KM_INT | 505; public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600; public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601; @@ -82,6 +84,7 @@ public final class KeymasterDefs { public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000; public static final int KM_TAG_NONCE = KM_BYTES | 1001; public static final int KM_TAG_CHUNK_LENGTH = KM_INT | 1002; + public static final int KM_TAG_AUTH_TOKEN = KM_BYTES | 1003; // Algorithm values. public static final int KM_ALGORITHM_RSA = 1; diff --git a/core/java/android/security/keymaster/KeymasterIntArgument.java b/core/java/android/security/keymaster/KeymasterIntArgument.java index c3738d7..da81715 100644 --- a/core/java/android/security/keymaster/KeymasterIntArgument.java +++ b/core/java/android/security/keymaster/KeymasterIntArgument.java @@ -26,6 +26,15 @@ class KeymasterIntArgument extends KeymasterArgument { public KeymasterIntArgument(int tag, int value) { super(tag); + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_INT: + case KeymasterDefs.KM_INT_REP: + case KeymasterDefs.KM_ENUM: + case KeymasterDefs.KM_ENUM_REP: + break; // OK. + default: + throw new IllegalArgumentException("Bad int tag " + tag); + } this.value = value; } diff --git a/core/java/android/security/keymaster/KeymasterLongArgument.java b/core/java/android/security/keymaster/KeymasterLongArgument.java index 3c565b8..9d2be09 100644 --- a/core/java/android/security/keymaster/KeymasterLongArgument.java +++ b/core/java/android/security/keymaster/KeymasterLongArgument.java @@ -26,6 +26,12 @@ class KeymasterLongArgument extends KeymasterArgument { public KeymasterLongArgument(int tag, long value) { super(tag); + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_LONG: + break; // OK. + default: + throw new IllegalArgumentException("Bad long tag " + tag); + } this.value = value; } diff --git a/core/java/android/service/fingerprint/Fingerprint.aidl b/core/java/android/service/fingerprint/Fingerprint.aidl new file mode 100644 index 0000000..c9fd989 --- /dev/null +++ b/core/java/android/service/fingerprint/Fingerprint.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.fingerprint; + +// @hide +parcelable Fingerprint; diff --git a/core/java/android/service/fingerprint/Fingerprint.java b/core/java/android/service/fingerprint/Fingerprint.java new file mode 100644 index 0000000..37552eb --- /dev/null +++ b/core/java/android/service/fingerprint/Fingerprint.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.fingerprint; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Container for fingerprint metadata. + * @hide + */ +public final class Fingerprint implements Parcelable { + private CharSequence mName; + private int mGroupId; + private int mFingerId; + private long mDeviceId; // physical device this is associated with + + public Fingerprint(CharSequence name, int groupId, int fingerId, long deviceId) { + mName = name; + mGroupId = groupId; + mFingerId = fingerId; + mDeviceId = deviceId; + } + + private Fingerprint(Parcel in) { + mName = in.readString(); + mGroupId = in.readInt(); + mFingerId = in.readInt(); + mDeviceId = in.readLong(); + } + + /** + * Gets the human-readable name for the given fingerprint. + * @return name given to finger + */ + public CharSequence getName() { return mName; } + + /** + * Gets the device-specific finger id. Used by Settings to map a name to a specific + * fingerprint template. + * @return device-specific id for this finger + * @hide + */ + public int getFingerId() { return mFingerId; } + + /** + * Gets the group id specified when the fingerprint was enrolled. + * @return group id for the set of fingerprints this one belongs to. + * @hide + */ + public int getGroupId() { return mGroupId; } + + /** + * Device this fingerprint belongs to. + * @hide + */ + public long getDeviceId() { return mDeviceId; } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(mName.toString()); + out.writeInt(mGroupId); + out.writeInt(mFingerId); + out.writeLong(mDeviceId); + } + + public static final Parcelable.Creator<Fingerprint> CREATOR + = new Parcelable.Creator<Fingerprint>() { + public Fingerprint createFromParcel(Parcel in) { + return new Fingerprint(in); + } + + public Fingerprint[] newArray(int size) { + return new Fingerprint[size]; + } + }; +};
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java index 6375668..bb90e40 100644 --- a/core/java/android/service/fingerprint/FingerprintManager.java +++ b/core/java/android/service/fingerprint/FingerprintManager.java @@ -20,17 +20,25 @@ import android.app.ActivityManagerNative; import android.content.ContentResolver; import android.content.Context; import android.os.Binder; +import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; +import android.service.fingerprint.FingerprintManager.EnrollmentCallback; import android.util.Log; import android.util.Slog; +import java.security.Signature; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import javax.crypto.Cipher; + /** * A class that coordinates access to the fingerprint hardware. * @hide @@ -45,9 +53,6 @@ public class FingerprintManager { private static final int MSG_ERROR = 103; private static final int MSG_REMOVED = 104; - // Errors generated by layers above HAL - public static final int FINGERPRINT_ERROR_NO_RECEIVER = -10; - // Message types. Must agree with HAL (fingerprint.h) public static final int FINGERPRINT_ERROR = -1; public static final int FINGERPRINT_ACQUIRED = 1; @@ -60,254 +65,499 @@ public class FingerprintManager { public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; public static final int FINGERPRINT_ERROR_TIMEOUT = 3; public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + public static final int FINGERPRINT_ERROR_CANCELED = 5; + public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000; - // FINGERPRINT_ACQUIRED messages. Must agree with HAL (fingerprint.h) + // Image acquisition messages. Must agree with HAL (fingerprint.h) public static final int FINGERPRINT_ACQUIRED_GOOD = 0; public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; - public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 4; - public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 8; - public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 16; + public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; + public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; + public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; + public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; private IFingerprintService mService; - private FingerprintManagerReceiver mClientReceiver; private Context mContext; private IBinder mToken = new Binder(); + private AuthenticationCallback mAuthenticationCallback; + private EnrollmentCallback mEnrollmentCallback; + private RemovalCallback mRemovalCallback; + private CryptoObject mCryptoObject; + private Fingerprint mRemovalFingerprint; + private boolean mListening; - private Handler mHandler = new Handler() { - public void handleMessage(android.os.Message msg) { - if (mClientReceiver != null) { - switch(msg.what) { - case MSG_ENROLL_RESULT: - mClientReceiver.onEnrollResult(msg.arg1, msg.arg2); - break; - case MSG_ACQUIRED: - mClientReceiver.onAcquired(msg.arg1); - break; - case MSG_PROCESSED: - mClientReceiver.onProcessed(msg.arg1); - break; - case MSG_ERROR: - mClientReceiver.onError(msg.arg1); - break; - case MSG_REMOVED: - mClientReceiver.onRemoved(msg.arg1); - } - } - } + /** + * A wrapper class for a limited number of crypto objects supported by FingerprintManager. + */ + public static class CryptoObject { + CryptoObject(Signature signature) { mSignature = signature; } + CryptoObject(Cipher cipher) { mCipher = cipher; } + private Signature mSignature; + private Cipher mCipher; }; - public static final class FingerprintItem { - public CharSequence name; - public int id; - FingerprintItem(CharSequence name, int id) { - this.name = name; - this.id = id; + /** + * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject, + * AuthenticationCallback, CancellationSignal, int)} + */ + public static final class AuthenticationResult { + private Fingerprint mFingerprint; + private CryptoObject mCryptoObject; + + public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint) { + mCryptoObject = crypto; + mFingerprint = fingerprint; } - } + + /** + * Obtain the crypto object associated with this transaction + * @return crypto object provided to {@link FingerprintManager#authenticate(CryptoObject, + * AuthenticationCallback, CancellationSignal, int)} + */ + public CryptoObject getCryptoObject() { return mCryptoObject; } + + /** + * Obtain the Fingerprint associated with this operation. Applications are discouraged + * from associating specific fingers with specific applications or operations. Hence this + * is not public. + * @hide + */ + public Fingerprint getFingerprint() { return mFingerprint; } + }; /** - * @hide + * Callback structure provided to {@link FingerprintManager#authenticate(CryptoObject, + * AuthenticationCallback, CancellationSignal, int)}. Users of {@link #FingerprintManager()} + * must provide an implementation of this to {@link FingerprintManager#authenticate( + * CryptoObject, AuthenticationCallback, CancellationSignal, int) for listening to fingerprint + * events. */ - public FingerprintManager(Context context, IFingerprintService service) { - mContext = context; - mService = service; - if (mService == null) { - Slog.v(TAG, "FingerprintManagerService was null"); - } - } + public static abstract class AuthenticationCallback { + /** + * Called when an unrecoverable error has been encountered and the operation is complete. + * No further callbacks will be made on this object. + * @param errMsgId an integer identifying the error message. + * @param errString a human-readible error string that can be shown in UI. + */ + public abstract void onAuthenticationError(int errMsgId, CharSequence errString); - private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { + /** + * Called when a recoverable error has been encountered during authentication. The help + * string is provided to give the user guidance for what went wrong, such as + * "Sensor dirty, please clean it." + * @param helpMsgId an integer identifying the error message. + * @param helpString a human-readible string that can be shown in UI. + */ + public abstract void onAuthenticationHelp(int helpMsgId, CharSequence helpString); - public void onEnrollResult(int fingerprintId, int remaining) { - mHandler.obtainMessage(MSG_ENROLL_RESULT, fingerprintId, remaining).sendToTarget(); - } + /** + * Called when a fingerprint is recognized. + * @param result an object containing authentication-related data. + */ + public abstract void onAuthenticationSucceeded(AuthenticationResult result); + }; - public void onAcquired(int acquireInfo) { - mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, 0).sendToTarget(); - } + /** + * Callback structure provided to {@link FingerprintManager#enroll(long, EnrollmentCallback, + * CancellationSignal, int). Users of {@link #FingerprintManager()} + * must provide an implementation of this to {@link FingerprintManager#enroll(long, + * EnrollmentCallback, CancellationSignal, int) for listening to fingerprint events. + */ + public static abstract class EnrollmentCallback { + /** + * Called when an unrecoverable error has been encountered and the operation is complete. + * No further callbacks will be made on this object. + * @param errMsgId an integer identifying the error message. + * @param errString a human-readible error string that can be shown in UI. + */ + public abstract void onEnrollmentError(int errMsgId, CharSequence errString); - public void onProcessed(int fingerprintId) { - mHandler.obtainMessage(MSG_PROCESSED, fingerprintId, 0).sendToTarget(); - } + /** + * Called when a recoverable error has been encountered during enrollment. The help + * string is provided to give the user guidance for what went wrong, such as + * "Sensor dirty, please clean it" or what they need to do next, such as + * "Touch sensor again." + * @param helpMsgId an integer identifying the error message. + * @param helpString a human-readible string that can be shown in UI. + */ + public abstract void onEnrollmentHelp(int helpMsgId, CharSequence helpString); - public void onError(int error) { - mHandler.obtainMessage(MSG_ERROR, error, 0).sendToTarget(); - } + /** + * Called as each enrollment step progresses. Enrollment is considered complete when + * remaining reaches 0. This function will not be called if enrollment fails. See + * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} + * @param remaining the number of remaining steps. + */ + public abstract void onEnrollmentProgress(int remaining); + }; - public void onRemoved(int fingerprintId) { - mHandler.obtainMessage(MSG_REMOVED, fingerprintId, 0).sendToTarget(); - } + /** + * Callback structure provided to {@link FingerprintManager#remove(int). Users of + * {@link #FingerprintManager()} may optionally provide an implementation of this to + * {@link FingerprintManager#remove(int, int, RemovalCallback)} for listening to + * fingerprint template removal events. + */ + public static abstract class RemovalCallback { + /** + * Called when the given fingerprint can't be removed. + * @param fp the fingerprint that the call attempted to remove. + * @param errMsgId an associated error message id. + * @param errString an error message indicating why the fingerprint id can't be removed. + */ + public abstract void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString); + + /** + * Called when a given fingerprint is successfully removed. + * @param fingerprint the fingerprint template that was removed. + */ + public abstract void onRemovalSucceeded(Fingerprint fingerprint); }; /** - * Determine whether the user has at least one fingerprint enrolled and enabled. + * Request authentication of a crypto object. This call warms up the fingerprint hardware + * and starts scanning for a fingerprint. It terminates when + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or + * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult) is called, at + * which point the object is no longer valid. The operation can be canceled by using the + * provided cancel object. * - * @return true if at least one is enrolled and enabled + * @param crypto object associated with the call or null if none required. + * @param callback an object to receive authentication events + * @param cancel an object that can be used to cancel authentication + * @param flags optional flags */ - public boolean enrolledAndEnabled() { - ContentResolver res = mContext.getContentResolver(); - return Settings.Secure.getInt(res, "fingerprint_enabled", 0) != 0 - && FingerprintUtils.getFingerprintIdsForUser(res, getCurrentUserId()).length > 0; + public void authenticate(CryptoObject crypto, AuthenticationCallback callback, + CancellationSignal cancel, int flags) { + if (callback == null) { + throw new IllegalArgumentException("Must supply an authentication callback"); + } + + // TODO: handle cancel + + if (mService != null) try { + mAuthenticationCallback = callback; + mCryptoObject = crypto; + long sessionId = 0; // TODO: get from crypto object + startListening(); + mService.authenticate(mToken, sessionId, getCurrentUserId(), flags); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception while authenticating: ", e); + stopListening(); + } } /** - * Start the enrollment process. Timeout dictates how long to wait for the user to - * enroll a fingerprint. - * - * @param timeout + * Request fingerprint enrollment. This call warms up the fingerprint hardware + * and starts scanning for fingerprints. Progress will be indicated by callbacks to the + * {@link EnrollmentCallback} object. It terminates when + * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or + * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at + * which point the object is no longer valid. The operation can be canceled by using the + * provided cancel object. + * @param challenge a unique id provided by a recent verification of device credentials + * (e.g. pin, pattern or password). + * @param callback an object to receive enrollment events + * @param cancel an object that can be used to cancel enrollment + * @param flags optional flags */ - public void enroll(long timeout) { - if (mServiceReceiver == null) { - sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); - return; + public void enroll(long challenge, EnrollmentCallback callback, + CancellationSignal cancel, int flags) { + if (callback == null) { + throw new IllegalArgumentException("Must supply an enrollment callback"); } + + // TODO: handle cancel + if (mService != null) try { - mService.enroll(mToken, timeout, getCurrentUserId()); + mEnrollmentCallback = callback; + startListening(); + mService.enroll(mToken, getCurrentUserId(), flags); } catch (RemoteException e) { - Log.v(TAG, "Remote exception while enrolling: ", e); - sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + Log.v(TAG, "Remote exception in enroll: ", e); + stopListening(); } } /** - * Remove the given fingerprintId from the system. FingerprintId of 0 has special meaning - * which is to delete all fingerprint data for the current user. Use with caution. - * @param fingerprintId + * Remove given fingerprint template from fingerprint hardware and/or protected storage. + * @param fp the fingerprint item to remove + * @param callback an optional callback to verify that fingerprint templates have been + * successfully removed. May be null of no callback is required. + * @hide */ - public void remove(int fingerprintId) { - if (mServiceReceiver == null) { - sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); - return; - } - if (mService != null) { - try { - mService.remove(mToken, fingerprintId, getCurrentUserId()); - } catch (RemoteException e) { - Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e); - } - } else { - Log.w(TAG, "remove(): Service not connected!"); - sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + public void remove(Fingerprint fp, RemovalCallback callback) { + if (mService != null) try { + mRemovalCallback = callback; + mRemovalFingerprint = fp; + startListening(); + mService.remove(mToken, fp.getFingerId(), getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote in remove: ", e); + stopListening(); } } /** - * Starts listening for fingerprint events. When a finger is scanned or recognized, the - * client will be notified via the callback. + * Renames the given fingerprint template + * @param fpId the fingerprint id + * @param newName the new name + * @hide */ - public void startListening(FingerprintManagerReceiver receiver) { - mClientReceiver = receiver; + public void rename(int fpId, String newName) { + // Renames the given fpId if (mService != null) { try { - mService.startListening(mToken, mServiceReceiver, getCurrentUserId()); + mService.rename(fpId, getCurrentUserId(), newName); } catch (RemoteException e) { - Log.v(TAG, "Remote exception in startListening(): ", e); + Log.v(TAG, "Remote exception in rename(): ", e); } } else { - Log.w(TAG, "startListening(): Service not connected!"); - sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + Log.w(TAG, "rename(): Service not connected!"); } } - private int getCurrentUserId() { - try { - return ActivityManagerNative.getDefault().getCurrentUser().id; + /** + * Obtain the list of enrolled fingerprints templates. + * @return list of current fingerprint items + */ + public List<Fingerprint> getEnrolledFingerprints() { + if (mService != null) try { + return mService.getEnrolledFingerprints(getCurrentUserId()); } catch (RemoteException e) { - Log.w(TAG, "Failed to get current user id\n"); - return UserHandle.USER_NULL; + Log.v(TAG, "Remote exception in getEnrolledFingerprints: ", e); } + return null; } /** - * Stops the client from listening to fingerprint events. + * Determine if fingerprint hardware is present and functional. + * @return true if hardware is present and functional, false otherwise. + * @hide */ - public void stopListening() { + public boolean isHardwareDetected() { if (mService != null) { try { - mService.stopListening(mToken, getCurrentUserId()); - mClientReceiver = null; + long deviceId = 0; /* TODO: plumb hardware id to FPMS */ + return mService.isHardwareDetected(deviceId); } catch (RemoteException e) { - Log.v(TAG, "Remote exception in stopListening(): ", e); + Log.v(TAG, "Remote exception in isFingerprintHardwareDetected(): ", e); } } else { - Log.w(TAG, "stopListening(): Service not connected!"); - sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + Log.w(TAG, "isFingerprintHardwareDetected(): Service not connected!"); } + return false; } - public void enrollCancel() { - if (mServiceReceiver == null) { - sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0); - return; + private Handler mHandler = new Handler() { + public void handleMessage(android.os.Message msg) { + switch(msg.what) { + case MSG_ENROLL_RESULT: + sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */); + break; + case MSG_ACQUIRED: + sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */); + break; + case MSG_PROCESSED: + sendProcessedResult((Fingerprint) msg.obj); + break; + case MSG_ERROR: + sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */); + break; + case MSG_REMOVED: + sendRemovedResult((Long) msg.obj /* deviceId */, msg.arg1 /* fingerId */, + msg.arg2 /* groupId */); + } } - if (mService != null) { - try { - mService.enrollCancel(mToken, getCurrentUserId()); - mClientReceiver = null; - } catch (RemoteException e) { - Log.v(TAG, "Remote exception in enrollCancel(): ", e); - sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0); + + private void sendRemovedResult(long deviceId, int fingerId, int groupId) { + if (mRemovalCallback != null) { + int reqFingerId = mRemovalFingerprint.getFingerId(); + int reqGroupId = mRemovalFingerprint.getGroupId(); + if (fingerId != reqFingerId) { + Log.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId); + } + if (fingerId != reqFingerId) { + Log.w(TAG, "Group id didn't match: " + groupId + " != " + reqGroupId); + } + mRemovalCallback.onRemovalSucceeded(mRemovalFingerprint); } - } else { - Log.w(TAG, "enrollCancel(): Service not connected!"); } - } - private void sendError(int msg, int arg1, int arg2) { - mHandler.obtainMessage(msg, arg1, arg2); - } + private void sendErrorResult(long deviceId, int errMsgId) { + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentError(errMsgId, getErrorString(errMsgId)); + } else if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationError(errMsgId, getErrorString(errMsgId)); + } else if (mRemovalCallback != null) { + mRemovalCallback.onRemovalError(mRemovalFingerprint, errMsgId, + getErrorString(errMsgId)); + } + } + + private void sendEnrollResult(Fingerprint fp, int remaining) { + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentProgress(remaining); + } + } + + private void sendProcessedResult(Fingerprint fp) { + if (mAuthenticationCallback != null) { + AuthenticationResult result = new AuthenticationResult(mCryptoObject, fp); + mAuthenticationCallback.onAuthenticationSucceeded(result); + } + } + + private void sendAcquiredResult(long deviceId, int acquireInfo) { + final String msg = getAcquiredString(acquireInfo); + if (msg == null) return; + + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentHelp(acquireInfo, msg); + } else if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationHelp(acquireInfo, msg); + } + } + + private String getErrorString(int errMsg) { + switch (errMsg) { + case FINGERPRINT_ERROR_UNABLE_TO_PROCESS: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_unable_to_process); + case FINGERPRINT_ERROR_HW_UNAVAILABLE: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_hw_not_available); + case FINGERPRINT_ERROR_NO_SPACE: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_no_space); + case FINGERPRINT_ERROR_TIMEOUT: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_timeout); + default: + if (errMsg >= FINGERPRINT_ERROR_VENDOR_BASE) { + int msgNumber = errMsg - FINGERPRINT_ERROR_VENDOR_BASE; + String[] msgArray = mContext.getResources().getStringArray( + com.android.internal.R.array.fingerprint_error_vendor); + if (msgNumber < msgArray.length) { + return msgArray[msgNumber]; + } + } + return null; + } + } + + private String getAcquiredString(int acquireInfo) { + switch (acquireInfo) { + case FINGERPRINT_ACQUIRED_GOOD: + return null; + case FINGERPRINT_ACQUIRED_PARTIAL: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_partial); + case FINGERPRINT_ACQUIRED_INSUFFICIENT: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_insufficient); + case FINGERPRINT_ACQUIRED_IMAGER_DIRTY: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_imager_dirty); + case FINGERPRINT_ACQUIRED_TOO_SLOW: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_too_slow); + case FINGERPRINT_ACQUIRED_TOO_FAST: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_too_fast); + default: + if (acquireInfo >= FINGERPRINT_ACQUIRED_VENDOR_BASE) { + int msgNumber = acquireInfo - FINGERPRINT_ACQUIRED_VENDOR_BASE; + String[] msgArray = mContext.getResources().getStringArray( + com.android.internal.R.array.fingerprint_acquired_vendor); + if (msgNumber < msgArray.length) { + return msgArray[msgNumber]; + } + } + return null; + } + } + }; /** - * @return list of current fingerprint items * @hide */ - public List<FingerprintItem> getEnrolledFingerprints() { - int[] ids = FingerprintUtils.getFingerprintIdsForUser(mContext.getContentResolver(), - getCurrentUserId()); - List<FingerprintItem> result = new ArrayList<FingerprintItem>(); - for (int i = 0; i < ids.length; i++) { - // TODO: persist names in Settings - FingerprintItem item = new FingerprintItem("Finger" + ids[i], ids[i]); - result.add(item); + public FingerprintManager(Context context, IFingerprintService service) { + mContext = context; + mService = service; + if (mService == null) { + Slog.v(TAG, "FingerprintManagerService was null"); + } + } + + private int getCurrentUserId() { + try { + return ActivityManagerNative.getDefault().getCurrentUser().id; + } catch (RemoteException e) { + Log.w(TAG, "Failed to get current user id\n"); + return UserHandle.USER_NULL; } - return result; } /** - * Determine if fingerprint hardware is present and functional. - * @return true if hardware is present and functional, false otherwise. - * @hide + * Stops the client from listening to fingerprint events. */ - public boolean isHardwareDetected() { + private void stopListening() { if (mService != null) { try { - return mService.isHardwareDetected(); + if (mListening) { + mService.removeListener(mToken, mServiceReceiver); + mListening = false; + } } catch (RemoteException e) { - Log.v(TAG, "Remote exception in isFingerprintHardwareDetected(): ", e); + Log.v(TAG, "Remote exception in stopListening(): ", e); } } else { - Log.w(TAG, "isFingerprintHardwareDetected(): Service not connected!"); + Log.w(TAG, "stopListening(): Service not connected!"); } - return false; } /** - * Renames the given fingerprint template - * @param fpId the fingerprint id - * @param newName the new name - * @hide + * Starts listening for fingerprint events for this client. */ - public void rename(int fpId, String newName) { - // Renames the given fpId + private void startListening() { if (mService != null) { try { - mService.rename(fpId, newName); + if (!mListening) { + mService.addListener(mToken, mServiceReceiver, getCurrentUserId()); + mListening = true; + } } catch (RemoteException e) { - Log.v(TAG, "Remote exception in rename(): ", e); + Log.v(TAG, "Remote exception in startListening(): ", e); } } else { - Log.w(TAG, "rename(): Service not connected!"); + Log.w(TAG, "startListening(): Service not connected!"); } } + + private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { + + public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { + mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, + new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget(); + } + + public void onAcquired(long deviceId, int acquireInfo) { + mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, 0, deviceId).sendToTarget(); + } + + public void onProcessed(long deviceId, int fingerId, int groupId) { + mHandler.obtainMessage(MSG_PROCESSED, + new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget(); + } + + public void onError(long deviceId, int error) { + mHandler.obtainMessage(MSG_ERROR, error, 0, deviceId).sendToTarget(); + } + + public void onRemoved(long deviceId, int fingerId, int groupId) { + mHandler.obtainMessage(MSG_REMOVED, fingerId, groupId, deviceId).sendToTarget(); + } + }; + }
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java deleted file mode 100644 index 85677ba..0000000 --- a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java +++ /dev/null @@ -1,76 +0,0 @@ -package android.service.fingerprint; -/** - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @hide - */ -public class FingerprintManagerReceiver { - /** - * Fingerprint enrollment progress update. Enrollment is considered complete if - * remaining hits 0 without {@link #onError(int)} being called. - * - * @param fingerprintId the fingerprint we're currently enrolling - * @param remaining the number of samples required to complete enrollment. It's up to - * the hardware to define what each step in enrollment means. Some hardware - * requires multiple samples of the same part of the finger. Others require sampling of - * different parts of the finger. The enrollment flow can use remaining to - * mean "step x" of the process or "just need another sample." - */ - public void onEnrollResult(int fingerprintId, int remaining) { } - - /** - * Fingerprint touch detected, but not processed yet. Clients will use this message to - * determine a good or bad scan before the fingerprint is processed. This is meant for the - * client to provide feedback about the scan or alert the user that recognition is to follow. - * - * @param acquiredInfo one of: - * {@link FingerprintManager#FINGERPRINT_ACQUIRED_GOOD}, - * {@link FingerprintManager#FINGERPRINT_ACQUIRED_PARTIAL}, - * {@link FingerprintManager#FINGERPRINT_ACQUIRED_INSUFFICIENT}, - * {@link FingerprintManager#FINGERPRINT_ACQUIRED_IMAGER_DIRTY}, - * {@link FingerprintManager#FINGERPRINT_ACQUIRED_TOO_SLOW}, - * {@link FingerprintManager#FINGERPRINT_ACQUIRED_TOO_FAST} - */ - public void onAcquired(int acquiredInfo) { } - - /** - * Fingerprint has been detected and processed. A non-zero return indicates a valid - * fingerprint was detected. - * - * @param fingerprintId the finger id, or 0 if not recognized. - */ - public void onProcessed(int fingerprintId) { } - - /** - * An error was detected during scan or enrollment. One of - * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}, - * {@link FingerprintManager#FINGERPRINT_ERROR_UNABLE_TO_PROCESS} or - * {@link FingerprintManager#FINGERPRINT_ERROR_TIMEOUT} - * {@link FingerprintManager#FINGERPRINT_ERROR_NO_SPACE} - * - * @param error one of the above error codes - */ - public void onError(int error) { } - - /** - * The given fingerprint template was successfully removed by the driver. - * See {@link FingerprintManager#remove(int)} - * - * @param fingerprintId id of template to remove. - */ - public void onRemoved(int fingerprintId) { } -}
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/service/fingerprint/FingerprintUtils.java index cc17b99..62acbb9 100644 --- a/core/java/android/service/fingerprint/FingerprintUtils.java +++ b/core/java/android/service/fingerprint/FingerprintUtils.java @@ -67,7 +67,7 @@ class FingerprintUtils { return toIntArray(tmp); } - public static void addFingerprintIdForUser(int fingerId, ContentResolver res, int userId) { + public static void addFingerprintIdForUser(ContentResolver res, int fingerId, int userId) { // FingerId 0 has special meaning. if (fingerId == 0) { Log.w(TAG, "Tried to add fingerId 0"); diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl index 9b4750b..e5d3ad4 100644 --- a/core/java/android/service/fingerprint/IFingerprintService.aidl +++ b/core/java/android/service/fingerprint/IFingerprintService.aidl @@ -17,31 +17,42 @@ package android.service.fingerprint; import android.os.Bundle; import android.service.fingerprint.IFingerprintServiceReceiver; +import android.service.fingerprint.Fingerprint; +import java.util.List; /** * Communication channel from client to the fingerprint service. * @hide */ interface IFingerprintService { - // Any errors resulting from this call will be returned to the listener - void enroll(IBinder token, long timeout, int userId); + // Authenticate the given sessionId with a fingerprint + void authenticate(IBinder token, long sessionId, int groupId, int flags); - // Any errors resulting from this call will be returned to the listener - void enrollCancel(IBinder token, int userId); + // Start fingerprint enrollment + void enroll(IBinder token, int groupId, int flags); // Any errors resulting from this call will be returned to the listener - void remove(IBinder token, int fingerprintId, int userId); + void remove(IBinder token, int fingerId, int groupId); + + // Rename the fingerprint specified by fingerId and groupId to the given name + void rename(int fingerId, int groupId, String name); - // Start listening for fingerprint events. This has the side effect of starting - // the hardware if not already started. - void startListening(IBinder token, IFingerprintServiceReceiver receiver, int userId); + // Get a list of enrolled fingerprints in the given group. + List<Fingerprint> getEnrolledFingerprints(int groupId); - // Stops listening for fingerprints - void stopListening(IBinder token, int userId); + // Register listener for an instance of FingerprintManager + void addListener(IBinder token, IFingerprintServiceReceiver receiver, int userId); + + // Unregister listener for an instance of FingerprintManager + void removeListener(IBinder token, IFingerprintServiceReceiver receiver); // Determine if HAL is loaded and ready - boolean isHardwareDetected(); + boolean isHardwareDetected(long deviceId); + + // Gets the number of hardware devices + // int getHardwareDeviceCount(); + + // Gets the unique device id for hardware enumerated at i + // long getHardwareDevice(int i); - // Rename the given fingerprint id - void rename(int fpId, String name); } diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl index af4128f..f025064 100644 --- a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl @@ -23,9 +23,9 @@ import android.os.UserHandle; * @hide */ oneway interface IFingerprintServiceReceiver { - void onEnrollResult(int fingerprintId, int remaining); - void onAcquired(int acquiredInfo); - void onProcessed(int fingerprintId); - void onError(int error); - void onRemoved(int fingerprintId); + void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining); + void onAcquired(long deviceId, int acquiredInfo); + void onProcessed(long deviceId, int fingerId, int groupId); + void onError(long deviceId, int error); + void onRemoved(long deviceId, int fingerId, int groupId); } diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index 797457a..4f4b2d5 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -17,6 +17,7 @@ package android.service.voice; import android.content.Intent; +import android.graphics.Bitmap; import android.os.Bundle; /** @@ -26,6 +27,7 @@ oneway interface IVoiceInteractionSession { void show(in Bundle sessionArgs, int flags); void hide(); void handleAssist(in Bundle assistData); + void handleScreenshot(in Bitmap screenshot); void taskStarted(in Intent intent, int taskId); void taskFinished(in Intent intent, int taskId); void closeSystemDialogs(); diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 0c01b25..419b92b 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -71,11 +71,17 @@ public class VoiceInteractionService extends Service { public static final String SERVICE_META_DATA = "android.voice_interaction"; /** - * Flag for use with {@link #showSession: request that the session be started with + * Flag for use with {@link #showSession}: request that the session be started with * assist data from the currently focused activity. */ public static final int START_WITH_ASSIST = 1<<0; + /** + * Flag for use with {@link #showSession}: request that the session be started with + * a screen shot of the currently focused activity. + */ + public static final int START_WITH_SCREENSHOT = 1<<1; + IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { @Override public void ready() { mHandler.sendEmptyMessage(MSG_READY); diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 11eaa06..7a5bb90 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -22,6 +22,7 @@ import android.app.VoiceInteractor; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.Region; import android.inputmethodservice.SoftInputWindow; @@ -179,6 +180,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } @Override + public void handleScreenshot(Bitmap screenshot) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_SCREENSHOT, + screenshot)); + } + + @Override public void taskStarted(Intent intent, int taskId) { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED, taskId, intent)); @@ -323,8 +330,9 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { static final int MSG_CLOSE_SYSTEM_DIALOGS = 102; static final int MSG_DESTROY = 103; static final int MSG_HANDLE_ASSIST = 104; - static final int MSG_SHOW = 105; - static final int MSG_HIDE = 106; + static final int MSG_HANDLE_SCREENSHOT = 105; + static final int MSG_SHOW = 106; + static final int MSG_HIDE = 107; class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback { @Override @@ -396,9 +404,13 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { doDestroy(); break; case MSG_HANDLE_ASSIST: - if (DEBUG) Log.d(TAG, "onHandleAssist: " + (Bundle)msg.obj); + if (DEBUG) Log.d(TAG, "onHandleAssist: " + msg.obj); onHandleAssist((Bundle) msg.obj); break; + case MSG_HANDLE_SCREENSHOT: + if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj); + onHandleScreenshot((Bitmap) msg.obj); + break; case MSG_SHOW: if (DEBUG) Log.d(TAG, "doShow: args=" + msg.obj + " flags=" + msg.arg1); @@ -768,6 +780,9 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { public void onHandleAssist(Bundle assistBundle) { } + public void onHandleScreenshot(Bitmap screenshot) { + } + public boolean onKeyDown(int keyCode, KeyEvent event) { return false; } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 4902a71..1674950 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -17,15 +17,12 @@ package android.service.wallpaper; import android.content.res.TypedArray; -import android.os.Build; import android.os.SystemProperties; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.ViewRootImpl; import android.view.WindowInsets; import com.android.internal.R; import com.android.internal.os.HandlerCaller; +import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.BaseIWindow; import com.android.internal.view.BaseSurfaceHolder; @@ -158,7 +155,6 @@ public abstract class WallpaperService extends Service { WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; int mCurWindowFlags = mWindowFlags; int mCurWindowPrivateFlags = mWindowPrivateFlags; - TypedValue mOutsetBottom; final Rect mVisibleInsets = new Rect(); final Rect mWinFrame = new Rect(); final Rect mOverscanInsets = new Rect(); @@ -171,8 +167,6 @@ public abstract class WallpaperService extends Service { final Rect mFinalStableInsets = new Rect(); final Configuration mConfiguration = new Configuration(); - private boolean mIsEmulator; - private boolean mIsCircularEmulator; private boolean mWindowIsRound; final WindowManager.LayoutParams mLayout @@ -629,31 +623,12 @@ public abstract class WallpaperService extends Service { mLayout.token = mWindowToken; if (!mCreated) { - // Retrieve watch round and outset info - final WindowManager windowService = (WindowManager)getSystemService( - Context.WINDOW_SERVICE); + // Retrieve watch round info TypedArray windowStyle = obtainStyledAttributes( com.android.internal.R.styleable.Window); - final Display display = windowService.getDefaultDisplay(); - final boolean shouldUseBottomOutset = - display.getDisplayId() == Display.DEFAULT_DISPLAY; - if (shouldUseBottomOutset && windowStyle.hasValue( - R.styleable.Window_windowOutsetBottom)) { - if (mOutsetBottom == null) mOutsetBottom = new TypedValue(); - windowStyle.getValue(R.styleable.Window_windowOutsetBottom, - mOutsetBottom); - } else { - mOutsetBottom = null; - } - mWindowIsRound = getResources().getBoolean( - com.android.internal.R.bool.config_windowIsRound); + mWindowIsRound = ScreenShapeHelper.getWindowIsRound(getResources()); windowStyle.recycle(); - // detect emulator - mIsEmulator = Build.HARDWARE.contains("goldfish"); - mIsCircularEmulator = SystemProperties.getBoolean( - ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false); - // Add window mLayout.type = mIWallpaperEngine.mWindowType; mLayout.gravity = Gravity.START|Gravity.TOP; @@ -783,18 +758,11 @@ public abstract class WallpaperService extends Service { mDispatchedOverscanInsets.set(mOverscanInsets); mDispatchedContentInsets.set(mContentInsets); mDispatchedStableInsets.set(mStableInsets); - final boolean isRound = (mIsEmulator && mIsCircularEmulator) - || mWindowIsRound; mFinalSystemInsets.set(mDispatchedOverscanInsets); mFinalStableInsets.set(mDispatchedStableInsets); - if (mOutsetBottom != null) { - final DisplayMetrics metrics = getResources().getDisplayMetrics(); - mFinalSystemInsets.bottom = - ( (int) mOutsetBottom.getDimension(metrics) ) - + mIWallpaperEngine.mDisplayPadding.bottom; - } + mFinalSystemInsets.bottom = mIWallpaperEngine.mDisplayPadding.bottom; WindowInsets insets = new WindowInsets(mFinalSystemInsets, - null, mFinalStableInsets, isRound); + null, mFinalStableInsets, mWindowIsRound); onApplyWindowInsets(insets); } diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index dc93bc2..7bebbfb 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -244,13 +244,18 @@ public class Html { next++; } - withinParagraph(out, text, i, next - nl, nl, next == end); + if (withinParagraph(out, text, i, next - nl, nl, next == end)) { + /* Paragraph should be closed */ + out.append("</p>\n"); + out.append(getOpenParaTagWithDirection(text, next, end)); + } } out.append("</p>\n"); } - private static void withinParagraph(StringBuilder out, Spanned text, + /* Returns true if the caller should close and reopen the paragraph. */ + private static boolean withinParagraph(StringBuilder out, Spanned text, int start, int end, int nl, boolean last) { int next; @@ -363,17 +368,14 @@ public class Html { } } - String p = last ? "" : "</p>\n" + getOpenParaTagWithDirection(text, start, end); - if (nl == 1) { out.append("<br>\n"); - } else if (nl == 2) { - out.append(p); + return false; } else { for (int i = 2; i < nl; i++) { out.append("<br>"); } - out.append(p); + return !last; } } @@ -672,7 +674,7 @@ class HtmlToSpannedConverter implements ContentHandler { String name = f.mColor.substring(1); int colorRes = res.getIdentifier(name, "color", "android"); if (colorRes != 0) { - ColorStateList colors = res.getColorStateList(colorRes); + ColorStateList colors = res.getColorStateList(colorRes, null); text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index fcf1828..928bf16 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1153,7 +1153,10 @@ public abstract class Layout { return end - 1; } - if (ch != ' ' && ch != '\t') { + // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace() + if (!(ch == ' ' || ch == '\t' || ch == 0x1680 || + (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) || + ch == 0x205F || ch == 0x3000)) { break; } diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 7ce44e1..992dc4d 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -1006,28 +1006,43 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return new String(buf); } + /** + * Returns the depth of TextWatcher callbacks. Returns 0 when the object is not handling + * TextWatchers. A return value greater than 1 implies that a TextWatcher caused a change that + * recursively triggered a TextWatcher. + */ + public int getTextWatcherDepth() { + return mTextWatcherDepth; + } + private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) { int n = watchers.length; + mTextWatcherDepth++; for (int i = 0; i < n; i++) { watchers[i].beforeTextChanged(this, start, before, after); } + mTextWatcherDepth--; } private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) { int n = watchers.length; + mTextWatcherDepth++; for (int i = 0; i < n; i++) { watchers[i].onTextChanged(this, start, before, after); } + mTextWatcherDepth--; } private void sendAfterTextChanged(TextWatcher[] watchers) { int n = watchers.length; + mTextWatcherDepth++; for (int i = 0; i < n; i++) { watchers[i].afterTextChanged(this); } + mTextWatcherDepth--; } private void sendSpanAdded(Object what, int start, int end) { @@ -1524,6 +1539,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private IdentityHashMap<Object, Integer> mIndexOfSpan; private int mLowWaterMark; // indices below this have not been touched + // TextWatcher callbacks may trigger changes that trigger more callbacks. This keeps track of + // how deep the callbacks go. + private int mTextWatcherDepth; + // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned} private static final int MARK = 1; private static final int POINT = 2; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index ee39e27..b47418f 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -170,8 +170,9 @@ public class StaticLayout extends Layout { * Measurement and break iteration is done in native code. The protocol for using * the native code is as follows. * - * For each paragraph, do a nSetText of the paragraph text. Then, for each run within the - * paragraph: + * For each paragraph, do a nSetText of the paragraph text. Also do nSetLineWidth. + * + * Then, for each run within the paragraph: * - setLocale (this must be done at least for the first run, optional afterwards) * - one of the following, depending on the type of run: * + addStyleRun (a text run, to be measured in native code) @@ -459,7 +460,26 @@ public class StaticLayout extends Layout { byte[] chdirs = measured.mLevels; int dir = measured.mDir; boolean easy = measured.mEasy; - nSetText(b.mNativePtr, chs, paraEnd - paraStart); + + // tab stop locations + int[] variableTabStops = null; + if (spanned != null) { + TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, + paraEnd, TabStopSpan.class); + if (spans.length > 0) { + int[] stops = new int[spans.length]; + for (int i = 0; i < spans.length; i++) { + stops[i] = spans[i].getTabStop(); + } + Arrays.sort(stops, 0, stops.length); + variableTabStops = stops; + } + } + + int breakStrategy = 0; // 0 = kBreakStrategy_Greedy + nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, + firstWidth, firstWidthLineCount, restWidth, + variableTabStops, TAB_INCREMENT, breakStrategy); // measurement has to be done before performing line breaking // but we don't want to recompute fontmetrics or span ranges the @@ -505,25 +525,9 @@ public class StaticLayout extends Layout { spanEndCacheCount++; } - // tab stop locations - int[] variableTabStops = null; - if (spanned != null) { - TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, - paraEnd, TabStopSpan.class); - if (spans.length > 0) { - int[] stops = new int[spans.length]; - for (int i = 0; i < spans.length; i++) { - stops[i] = spans[i].getTabStop(); - } - Arrays.sort(stops, 0, stops.length); - variableTabStops = stops; - } - } - nGetWidths(b.mNativePtr, widths); - int breakCount = nComputeLineBreaks(b.mNativePtr, paraEnd - paraStart, firstWidth, - firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks, - lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length); + int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks, + lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length); int[] breaks = lineBreaks.breaks; float[] lineWidths = lineBreaks.widths; @@ -966,7 +970,10 @@ public class StaticLayout extends Layout { private static native void nFinishBuilder(long nativePtr); private static native void nSetLocale(long nativePtr, String locale); - private static native void nSetText(long nativePtr, char[] text, int length); + // Set up paragraph text and settings; done as one big method to minimize jni crossings + private static native void nSetupParagraph(long nativePtr, char[] text, int length, + float firstWidth, int firstWidthLineCount, float restWidth, + int[] variableTabStops, int defaultTabStop, int breakStrategy); private static native float nAddStyleRun(long nativePtr, long nativePaint, long nativeTypeface, int start, int end, boolean isRtl); @@ -983,9 +990,7 @@ public class StaticLayout extends Layout { // the arrays inside the LineBreaks objects are passed in as well // to reduce the number of JNI calls in the common case where the // arrays do not have to be resized - private static native int nComputeLineBreaks(long nativePtr, - int length, float firstWidth, int firstWidthLineCount, float restWidth, - int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle, + private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength); private int mLineCount; diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java index d29bfb6..0669b6f 100644 --- a/core/java/android/text/style/URLSpan.java +++ b/core/java/android/text/style/URLSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -23,6 +24,7 @@ import android.os.Parcel; import android.provider.Browser; import android.text.ParcelableSpan; import android.text.TextUtils; +import android.util.Log; import android.view.View; public class URLSpan extends ClickableSpan implements ParcelableSpan { @@ -59,6 +61,10 @@ public class URLSpan extends ClickableSpan implements ParcelableSpan { Context context = widget.getContext(); Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); - context.startActivity(intent); + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w("URLSpan", "Actvity was not found for intent, " + intent.toString()); + } } } diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index 8e9eb48..c119277 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -525,10 +525,6 @@ public class Linkify { return 0; } - - public final boolean equals(Object o) { - return false; - } }; Collections.sort(links, c); diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index e8d3947..9326203 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -18,6 +18,7 @@ package android.util; import com.android.internal.util.ArrayUtils; +import java.util.Arrays; import libcore.util.EmptyArray; /** @@ -78,6 +79,24 @@ public class IntArray implements Cloneable { } /** + * Searches the array for the specified value using the binary search algorithm. The array must + * be sorted (as by the {@link Arrays#sort(int[], int, int)} method) prior to making this call. + * If it is not sorted, the results are undefined. If the range contains multiple elements with + * the specified value, there is no guarantee which one will be found. + * + * @param value The value to search for. + * @return index of the search key, if it is contained in the array; otherwise, <i>(-(insertion + * point) - 1)</i>. The insertion point is defined as the point at which the key would + * be inserted into the array: the index of the first element greater than the key, or + * {@link #size()} if all elements in the array are less than the specified key. + * Note that this guarantees that the return value will be >= 0 if and only if the key + * is found. + */ + public int binarySearch(int value) { + return ContainerHelpers.binarySearch(mValues, mSize, value); + } + + /** * Adds the values in the specified array to this array. */ public void addAll(IntArray values) { @@ -159,4 +178,11 @@ public class IntArray implements Cloneable { public int size() { return mSize; } + + /** + * Returns a new array with the contents of this IntArray. + */ + public int[] toArray() { + return Arrays.copyOf(mValues, mSize); + } } diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java index 92b19be..18dc262 100644 --- a/core/java/android/util/PathParser.java +++ b/core/java/android/util/PathParser.java @@ -164,7 +164,7 @@ public class PathParser { * @return array of floats */ private static float[] getFloats(String s) { - if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { + if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') { return new float[0]; } try { diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java index 90e1f86..3caf6f0 100644 --- a/core/java/android/view/DisplayListCanvas.java +++ b/core/java/android/view/DisplayListCanvas.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.CanvasProperty; import android.graphics.NinePatch; import android.graphics.Paint; @@ -32,8 +33,10 @@ import android.util.Pools.SynchronizedPool; * This is intended for use with a DisplayList. This class keeps a list of all the Paint and * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while * the DisplayList is still holding a native reference to the memory. + * + * @hide */ -class DisplayListCanvas extends HardwareCanvas { +public class DisplayListCanvas extends Canvas { // The recording canvas pool should be large enough to handle a deeply nested // view hierarchy because display lists are generated recursively. private static final int POOL_LIMIT = 25; @@ -85,7 +88,6 @@ class DisplayListCanvas extends HardwareCanvas { // Constructors /////////////////////////////////////////////////////////////////////////// - private DisplayListCanvas() { super(nCreateDisplayListRenderer()); } @@ -103,6 +105,16 @@ class DisplayListCanvas extends HardwareCanvas { /////////////////////////////////////////////////////////////////////////// @Override + public boolean isHardwareAccelerated() { + return true; + } + + @Override + public void setBitmap(Bitmap bitmap) { + throw new UnsupportedOperationException(); + } + + @Override public boolean isOpaque() { return false; } @@ -171,7 +183,11 @@ class DisplayListCanvas extends HardwareCanvas { private static native void nInsertReorderBarrier(long renderer, boolean enableReorder); - @Override + /** + * Invoked before any drawing operation is performed in this canvas. + * + * @param dirty The dirty rectangle to update, can be null. + */ public void onPreDraw(Rect dirty) { if (dirty != null) { nPrepareDirty(mNativeCanvasWrapper, dirty.left, dirty.top, dirty.right, dirty.bottom); @@ -183,7 +199,9 @@ class DisplayListCanvas extends HardwareCanvas { private static native void nPrepare(long renderer); private static native void nPrepareDirty(long renderer, int left, int top, int right, int bottom); - @Override + /** + * Invoked after all drawing operation have been performed. + */ public void onPostDraw() { nFinish(mNativeCanvasWrapper); } @@ -194,7 +212,13 @@ class DisplayListCanvas extends HardwareCanvas { // Functor /////////////////////////////////////////////////////////////////////////// - @Override + /** + * Calls the function specified with the drawGLFunction function pointer. This is + * functionality used by webkit for calling into their renderer from our display lists. + * This function may return true if an invalidation is needed after the call. + * + * @param drawGLFunction A native function pointer + */ public void callDrawGLFunction2(long drawGLFunction) { nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction); } @@ -207,7 +231,23 @@ class DisplayListCanvas extends HardwareCanvas { protected static native long nFinishRecording(long renderer); - @Override + /** + * Draws the specified display list onto this canvas. The display list can only + * be drawn if {@link android.view.RenderNode#isValid()} returns true. + * + * @param renderNode The RenderNode to replay. + */ + public void drawRenderNode(RenderNode renderNode) { + drawRenderNode(renderNode, RenderNode.FLAG_CLIP_CHILDREN); + } + + /** + * Draws the specified display list onto this canvas. + * + * @param renderNode The RenderNode to replay. + * @param flags Optional flags about drawing, see {@link RenderNode} for + * the possible flags. + */ public void drawRenderNode(RenderNode renderNode, int flags) { nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList(), flags); } @@ -219,6 +259,14 @@ class DisplayListCanvas extends HardwareCanvas { // Hardware layer /////////////////////////////////////////////////////////////////////////// + /** + * Draws the specified layer onto this canvas. + * + * @param layer The layer to composite on this canvas + * @param x The left coordinate of the layer + * @param y The top coordinate of the layer + * @param paint The paint used to draw the layer + */ void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) { layer.setLayerPaint(paint); nDrawLayer(mNativeCanvasWrapper, layer.getLayerHandle(), x, y); @@ -253,7 +301,6 @@ class DisplayListCanvas extends HardwareCanvas { private static native void nDrawPatch(long renderer, long bitmap, long chunk, float left, float top, float right, float bottom, long paint); - @Override public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, CanvasProperty<Float> radius, CanvasProperty<Paint> paint) { nDrawCircle(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(), @@ -263,7 +310,6 @@ class DisplayListCanvas extends HardwareCanvas { private static native void nDrawCircle(long renderer, long propCx, long propCy, long propRadius, long propPaint); - @Override public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top, CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx, CanvasProperty<Float> ry, CanvasProperty<Paint> paint) { diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java index 20baad0..d58e7c0 100644 --- a/core/java/android/view/GhostView.java +++ b/core/java/android/view/GhostView.java @@ -46,14 +46,14 @@ public class GhostView extends View { @Override protected void onDraw(Canvas canvas) { - if (canvas instanceof HardwareCanvas) { - HardwareCanvas hwCanvas = (HardwareCanvas) canvas; + if (canvas instanceof DisplayListCanvas) { + DisplayListCanvas dlCanvas = (DisplayListCanvas) canvas; mView.mRecreateDisplayList = true; RenderNode renderNode = mView.getDisplayList(); if (renderNode.isValid()) { - hwCanvas.insertReorderBarrier(); // enable shadow for this rendernode - hwCanvas.drawRenderNode(renderNode); - hwCanvas.insertInorderBarrier(); // re-disable reordering/shadows + dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode + dlCanvas.drawRenderNode(renderNode); + dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows } } } diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java deleted file mode 100644 index fc2b55b..0000000 --- a/core/java/android/view/HardwareCanvas.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.CanvasProperty; -import android.graphics.Paint; -import android.graphics.Rect; - -/** - * Hardware accelerated canvas. - * - * @hide - */ -public abstract class HardwareCanvas extends Canvas { - - /** - * Pass a reference to the native renderer to our superclass's - * constructor. - */ - protected HardwareCanvas(long renderer) { - super(renderer); - } - - @Override - public boolean isHardwareAccelerated() { - return true; - } - - @Override - public void setBitmap(Bitmap bitmap) { - throw new UnsupportedOperationException(); - } - - /** - * Invoked before any drawing operation is performed in this canvas. - * - * @param dirty The dirty rectangle to update, can be null. - * - * @hide - */ - public abstract void onPreDraw(Rect dirty); - - /** - * Invoked after all drawing operation have been performed. - * - * @hide - */ - public abstract void onPostDraw(); - - /** - * Draws the specified display list onto this canvas. The display list can only - * be drawn if {@link android.view.RenderNode#isValid()} returns true. - * - * @param renderNode The RenderNode to replay. - */ - public void drawRenderNode(RenderNode renderNode) { - drawRenderNode(renderNode, RenderNode.FLAG_CLIP_CHILDREN); - } - - /** - * Draws the specified display list onto this canvas. - * - * @param renderNode The RenderNode to replay. - * @param dirty Ignored, can be null. - * @param flags Optional flags about drawing, see {@link RenderNode} for - * the possible flags. - * - * @hide - */ - public abstract void drawRenderNode(RenderNode renderNode, int flags); - - /** - * Draws the specified layer onto this canvas. - * - * @param layer The layer to composite on this canvas - * @param x The left coordinate of the layer - * @param y The top coordinate of the layer - * @param paint The paint used to draw the layer - * - * @hide - */ - abstract void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint); - - /** - * Calls the function specified with the drawGLFunction function pointer. This is - * functionality used by webkit for calling into their renderer from our display lists. - * This function may return true if an invalidation is needed after the call. - * - * @param drawGLFunction A native function pointer - * - * @hide - */ - public void callDrawGLFunction2(long drawGLFunction) { - // Noop - this is done in the display list recorder subclass - } - - public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, - CanvasProperty<Float> radius, CanvasProperty<Paint> paint); - - public abstract void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top, - CanvasProperty<Float> right, CanvasProperty<Float> bottom, - CanvasProperty<Float> rx, CanvasProperty<Float> ry, - CanvasProperty<Paint> paint); - - public static void setProperty(String name, String value) { - DisplayListCanvas.setProperty(name, value); - } -} diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index afa7f51..6632f39 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -326,7 +326,7 @@ public abstract class HardwareRenderer { * * @param canvas The Canvas used to render the view. */ - void onHardwarePreDraw(HardwareCanvas canvas); + void onHardwarePreDraw(DisplayListCanvas canvas); /** * Invoked after a view is drawn by a hardware renderer. @@ -334,7 +334,7 @@ public abstract class HardwareRenderer { * * @param canvas The Canvas used to render the view. */ - void onHardwarePostDraw(HardwareCanvas canvas); + void onHardwarePostDraw(DisplayListCanvas canvas); } /** diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 8ac8bc5..d6625c8 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -16,6 +16,7 @@ package android.view; +import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; @@ -220,10 +221,14 @@ interface IWindowManager boolean isRotationFrozen(); /** + * Used only for assist -- request a screenshot of the current application. + */ + boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver); + + /** * Create a screenshot of the applications currently displayed. */ - Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, - int maxHeight, boolean force565); + Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight); /** * Called by the status bar to notify Views of changes to System UI visiblity. diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 358ae8a..2eac549 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -49,6 +49,7 @@ public final class InputDevice implements Parcelable { private final String mName; private final int mVendorId; private final int mProductId; + private final String mUniqueId; private final String mDescriptor; private final InputDeviceIdentifier mIdentifier; private final boolean mIsExternal; @@ -356,14 +357,16 @@ public final class InputDevice implements Parcelable { // Called by native code. private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, - int productId, String descriptor, boolean isExternal, int sources, int keyboardType, - KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasButtonUnderPad) { + int productId, String uniqueId, String descriptor, boolean isExternal, int sources, + int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator, + boolean hasButtonUnderPad) { mId = id; mGeneration = generation; mControllerNumber = controllerNumber; mName = name; mVendorId = vendorId; mProductId = productId; + mUniqueId = uniqueId; mDescriptor = descriptor; mIsExternal = isExternal; mSources = sources; @@ -381,6 +384,7 @@ public final class InputDevice implements Parcelable { mName = in.readString(); mVendorId = in.readInt(); mProductId = in.readInt(); + mUniqueId = in.readString(); mDescriptor = in.readString(); mIsExternal = in.readInt() != 0; mSources = in.readInt(); @@ -505,6 +509,23 @@ public final class InputDevice implements Parcelable { } /** + * Gets the vendor's unique id for the given device, if available. + * <p> + * A vendor may assign a unique id to a device (e.g., MAC address for + * Bluetooth devices). A null value will be assigned where a unique id is + * not available. + * </p><p> + * This method is dependent on the vendor, whereas {@link #getDescriptor} + * attempts to create a unique id even when the vendor has not provided one. + * </p> + * + * @return The unique id of a given device + */ + public String getUniqueId() { + return mUniqueId; + } + + /** * Gets the input device descriptor, which is a stable identifier for an input device. * <p> * An input device descriptor uniquely identifies an input device. Its value @@ -843,6 +864,7 @@ public final class InputDevice implements Parcelable { out.writeString(mName); out.writeInt(mVendorId); out.writeInt(mProductId); + out.writeString(mUniqueId); out.writeString(mDescriptor); out.writeInt(mIsExternal ? 1 : 0); out.writeInt(mSources); diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 1a07aee..457d6ad 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -452,9 +452,10 @@ public abstract class LayoutInflater { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); + final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); - Context lastContext = (Context)mConstructorArgs[0]; - mConstructorArgs[0] = mContext; + Context lastContext = (Context) mConstructorArgs[0]; + mConstructorArgs[0] = inflaterContext; View result = root; try { @@ -485,10 +486,10 @@ public abstract class LayoutInflater { + "ViewGroup root and attachToRoot=true"); } - rInflate(parser, root, attrs, false, false); + rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml - final View temp = createViewFromTag(root, name, attrs, false); + final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; @@ -509,8 +510,10 @@ public abstract class LayoutInflater { if (DEBUG) { System.out.println("-----> start inflating children"); } - // Inflate all children under temp - rInflate(parser, temp, attrs, true, true); + + // Inflate all children under temp against its context. + rInflateChildren(parser, temp, attrs, true); + if (DEBUG) { System.out.println("-----> done inflating children"); } @@ -692,59 +695,68 @@ public abstract class LayoutInflater { } /** + * Convenience method for calling through to the five-arg createViewFromTag + * method. This method passes {@code false} for the {@code ignoreThemeAttr} + * argument and should be used for everything except {@code >include>} + * tag parsing. + */ + private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { + return createViewFromTag(parent, name, context, attrs, false); + } + + /** * Creates a view from a tag name using the supplied attribute set. * <p> - * If {@code inheritContext} is true and the parent is non-null, the view - * will be inflated in parent view's context. If the view specifies a - * <theme> attribute, the inflation context will be wrapped with the - * specified theme. - * <p> - * Note: Default visibility so the BridgeInflater can override it. + * <strong>Note:</strong> Default visibility so the BridgeInflater can + * override it. + * + * @param parent the parent view, used to inflate layout params + * @param name the name of the XML tag used to define the view + * @param context the inflation context for the view, typically the + * {@code parent} or base layout inflater context + * @param attrs the attribute set for the XML tag used to define the view + * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme} + * attribute (if set) for the view being inflated, + * {@code false} otherwise */ - View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) { + View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, + boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } - Context viewContext; - if (parent != null && inheritContext) { - viewContext = parent.getContext(); - } else { - viewContext = mContext; - } - - // Apply a theme wrapper, if requested. - final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME); - final int themeResId = ta.getResourceId(0, 0); - if (themeResId != 0) { - viewContext = new ContextThemeWrapper(viewContext, themeResId); + // Apply a theme wrapper, if allowed and one is specified. + if (!ignoreThemeAttr) { + final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); + final int themeResId = ta.getResourceId(0, 0); + if (themeResId != 0) { + context = new ContextThemeWrapper(context, themeResId); + } + ta.recycle(); } - ta.recycle(); if (name.equals(TAG_1995)) { // Let's party like it's 1995! - return new BlinkLayout(viewContext, attrs); + return new BlinkLayout(context, attrs); } - if (DEBUG) System.out.println("******** Creating view: " + name); - try { View view; if (mFactory2 != null) { - view = mFactory2.onCreateView(parent, name, viewContext, attrs); + view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { - view = mFactory.onCreateView(name, viewContext, attrs); + view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { - view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs); + view = mPrivateFactory.onCreateView(parent, name, context, attrs); } if (view == null) { final Object lastContext = mConstructorArgs[0]; - mConstructorArgs[0] = viewContext; + mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); @@ -756,20 +768,18 @@ public abstract class LayoutInflater { } } - if (DEBUG) System.out.println("Created view is: " + view); return view; - } catch (InflateException e) { throw e; } catch (ClassNotFoundException e) { - InflateException ie = new InflateException(attrs.getPositionDescription() + final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name); ie.initCause(e); throw ie; } catch (Exception e) { - InflateException ie = new InflateException(attrs.getPositionDescription() + final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name); ie.initCause(e); throw ie; @@ -777,16 +787,26 @@ public abstract class LayoutInflater { } /** + * Recursive method used to inflate internal (non-root) children. This + * method calls through to {@link #rInflate} using the parent context as + * the inflation context. + * <strong>Note:</strong> Default visibility so the BridgeInflater can + * call it. + */ + final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, + boolean finishInflate) throws XmlPullParserException, IOException { + rInflate(parser, parent, parent.getContext(), attrs, finishInflate); + } + + /** * Recursive method used to descend down the xml hierarchy and instantiate * views, instantiate their children, and then call onFinishInflate(). - * - * @param inheritContext Whether the root view should be inflated in its - * parent's context. This should be true when called inflating - * child views recursively, or false otherwise. + * <p> + * <strong>Note:</strong> Default visibility so the BridgeInflater can + * override it. */ - void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, - boolean finishInflate, boolean inheritContext) throws XmlPullParserException, - IOException { + void rInflate(XmlPullParser parser, View parent, Context context, + AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; @@ -808,19 +828,21 @@ public abstract class LayoutInflater { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } - parseInclude(parser, parent, attrs, inheritContext); + parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { - final View view = createViewFromTag(parent, name, attrs, inheritContext); + final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); - rInflate(parser, view, attrs, true, true); + rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } - if (finishInflate) parent.onFinishInflate(); + if (finishInflate) { + parent.onFinishInflate(); + } } /** @@ -829,13 +851,9 @@ public abstract class LayoutInflater { */ private void parseRequestFocus(XmlPullParser parser, View view) throws XmlPullParserException, IOException { - int type; view.requestFocus(); - final int currentDepth = parser.getDepth(); - while (((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { - // Empty - } + + consumeChildElements(parser); } /** @@ -844,33 +862,29 @@ public abstract class LayoutInflater { */ private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs) throws XmlPullParserException, IOException { - int type; - - final TypedArray ta = view.getContext().obtainStyledAttributes( - attrs, com.android.internal.R.styleable.ViewTag); - final int key = ta.getResourceId(com.android.internal.R.styleable.ViewTag_id, 0); - final CharSequence value = ta.getText(com.android.internal.R.styleable.ViewTag_value); + final Context context = view.getContext(); + final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag); + final int key = ta.getResourceId(R.styleable.ViewTag_id, 0); + final CharSequence value = ta.getText(R.styleable.ViewTag_value); view.setTag(key, value); ta.recycle(); - final int currentDepth = parser.getDepth(); - while (((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { - // Empty - } + consumeChildElements(parser); } - private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs, - boolean inheritContext) throws XmlPullParserException, IOException { + private void parseInclude(XmlPullParser parser, Context context, View parent, + AttributeSet attrs) throws XmlPullParserException, IOException { int type; if (parent instanceof ViewGroup) { - Context context = inheritContext ? parent.getContext() : mContext; - - // Apply a theme wrapper, if requested. + // Apply a theme wrapper, if requested. This is sort of a weird + // edge case, since developers think the <include> overwrites + // values in the AttributeSet of the included View. So, if the + // included View has a theme attribute, we'll need to ignore it. final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); - if (themeResId != 0) { + final boolean hasThemeOverride = themeResId != 0; + if (hasThemeOverride) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); @@ -880,11 +894,12 @@ public abstract class LayoutInflater { int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0); if (layout == 0) { final String value = attrs.getAttributeValue(null, ATTR_LAYOUT); - if (value == null || value.length() < 1) { + if (value == null || value.length() <= 0) { throw new InflateException("You must specify a layout in the" + " include tag: <include layout=\"@layout/layoutID\" />"); } + // Attempt to resolve the "?attr/name" string to an identifier. layout = context.getResources().getIdentifier(value.substring(1), null, null); } @@ -901,8 +916,7 @@ public abstract class LayoutInflater { throw new InflateException("You must specify a valid layout " + "reference. The layout ID " + value + " is not valid."); } else { - final XmlResourceParser childParser = - getContext().getResources().getLayout(layout); + final XmlResourceParser childParser = context.getResources().getLayout(layout); try { final AttributeSet childAttrs = Xml.asAttributeSet(childParser); @@ -920,11 +934,12 @@ public abstract class LayoutInflater { final String childName = childParser.getName(); if (TAG_MERGE.equals(childName)) { - // Inflate all children. - rInflate(childParser, parent, childAttrs, false, inheritContext); + // The <merge> tag doesn't support android:theme, so + // nothing special to do here. + rInflate(childParser, parent, context, childAttrs, false); } else { - final View view = createViewFromTag(parent, childName, childAttrs, - inheritContext); + final View view = createViewFromTag(parent, childName, + context, childAttrs, hasThemeOverride); final ViewGroup group = (ViewGroup) parent; final TypedArray a = context.obtainStyledAttributes( @@ -957,7 +972,7 @@ public abstract class LayoutInflater { view.setLayoutParams(params); // Inflate all children. - rInflate(childParser, view, childAttrs, true, true); + rInflateChildren(childParser, view, childAttrs, true); if (id != View.NO_ID) { view.setId(id); @@ -985,6 +1000,16 @@ public abstract class LayoutInflater { throw new InflateException("<include /> can only be used inside of a ViewGroup"); } + LayoutInflater.consumeChildElements(parser); + } + + /** + * <strong>Note:</strong> default visibility so that + * LayoutInflater_Delegate can call it. + */ + final static void consumeChildElements(XmlPullParser parser) + throws XmlPullParserException, IOException { + int type; final int currentDepth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { diff --git a/core/java/android/view/PhoneFallbackEventHandler.java b/core/java/android/view/PhoneFallbackEventHandler.java index fbf5732..350650d 100644 --- a/core/java/android/view/PhoneFallbackEventHandler.java +++ b/core/java/android/view/PhoneFallbackEventHandler.java @@ -25,8 +25,13 @@ import android.content.res.Configuration; import android.media.AudioManager; import android.media.session.MediaSessionLegacyHelper; import android.os.UserHandle; +import android.provider.Settings; import android.telephony.TelephonyManager; -import android.util.Slog; +import android.util.Log; +import android.view.View; +import android.view.HapticFeedbackConstants; +import android.view.FallbackEventHandler; +import android.view.KeyEvent; /** * @hide @@ -112,15 +117,20 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { dispatcher.startTracking(event, this); } else if (event.isLongPress() && dispatcher.isTracking(event)) { dispatcher.performedLongPress(event); - mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - // launch the VoiceDialer - Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - sendCloseSystemWindows(); - mContext.startActivity(intent); - } catch (ActivityNotFoundException e) { - startCallActivity(); + if (isUserSetupComplete()) { + mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + // launch the VoiceDialer + Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + sendCloseSystemWindows(); + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + startCallActivity(); + } + } else { + Log.i(TAG, "Not starting call activity because user " + + "setup is in progress."); } } return true; @@ -134,13 +144,18 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { dispatcher.startTracking(event, this); } else if (event.isLongPress() && dispatcher.isTracking(event)) { dispatcher.performedLongPress(event); - mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - sendCloseSystemWindows(); - // Broadcast an intent that the Camera button was longpressed - Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null); - intent.putExtra(Intent.EXTRA_KEY_EVENT, event); - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF, - null, null, null, 0, null, null); + if (isUserSetupComplete()) { + mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + sendCloseSystemWindows(); + // Broadcast an intent that the Camera button was longpressed + Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null); + intent.putExtra(Intent.EXTRA_KEY_EVENT, event); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF, + null, null, null, 0, null, null); + } else { + Log.i(TAG, "Not dispatching CAMERA long press because user " + + "setup is in progress."); + } } return true; } @@ -155,21 +170,26 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { Configuration config = mContext.getResources().getConfiguration(); if (config.keyboard == Configuration.KEYBOARD_NOKEYS || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { - // launch the search activity - Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - sendCloseSystemWindows(); - getSearchManager().stopSearch(); - mContext.startActivity(intent); - // Only clear this if we successfully start the - // activity; otherwise we will allow the normal short - // press action to be performed. - dispatcher.performedLongPress(event); - return true; - } catch (ActivityNotFoundException e) { - // Ignore + if (isUserSetupComplete()) { + // launch the search activity + Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + sendCloseSystemWindows(); + getSearchManager().stopSearch(); + mContext.startActivity(intent); + // Only clear this if we successfully start the + // activity; otherwise we will allow the normal short + // press action to be performed. + dispatcher.performedLongPress(event); + return true; + } catch (ActivityNotFoundException e) { + // Ignore + } + } else { + Log.i(TAG, "Not dispatching SEARCH long press because user " + + "setup is in progress."); } } } @@ -181,7 +201,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { boolean onKeyUp(int keyCode, KeyEvent event) { if (DEBUG) { - Slog.d(TAG, "up " + keyCode); + Log.d(TAG, "up " + keyCode); } final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState(); if (dispatcher != null) { @@ -229,7 +249,12 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { break; } if (event.isTracking() && !event.isCanceled()) { - startCallActivity(); + if (isUserSetupComplete()) { + startCallActivity(); + } else { + Log.i(TAG, "Not starting call activity because user " + + "setup is in progress."); + } } return true; } @@ -244,7 +269,7 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { try { mContext.startActivity(intent); } catch (ActivityNotFoundException e) { - Slog.w(TAG, "No activity found for android.intent.action.CALL_BUTTON."); + Log.w(TAG, "No activity found for android.intent.action.CALL_BUTTON."); } } @@ -284,5 +309,10 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { private void handleMediaKeyEvent(KeyEvent keyEvent) { MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false); } + + private boolean isUserSetupComplete() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; + } } diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java index 37859b8..cb32697 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/android/view/PhoneWindow.java @@ -28,6 +28,7 @@ import android.app.SearchManager; import android.os.UserHandle; import com.android.internal.R; +import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.RootViewSurfaceTaker; import com.android.internal.view.StandaloneActionMode; import com.android.internal.view.menu.ContextMenuBuilder; @@ -64,6 +65,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.transition.Scene; import android.transition.Transition; import android.transition.TransitionInflater; @@ -125,7 +127,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { TypedValue mFixedWidthMinor; TypedValue mFixedHeightMajor; TypedValue mFixedHeightMinor; - TypedValue mOutsetBottom; + int mOutsetBottomPx; // This is the top-level view of the window, containing the window decor. private DecorView mDecor; @@ -1759,7 +1761,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } else { MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy( mVolumeControlStreamType, direction, - AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); + AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE + | AudioManager.FLAG_FROM_KEY); } return true; } @@ -1837,15 +1840,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: { + final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE + | AudioManager.FLAG_FROM_KEY; // If we have a session send it the volume command, otherwise // use the suggested stream. if (mMediaController != null) { - mMediaController.adjustVolume(0, AudioManager.FLAG_PLAY_SOUND - | AudioManager.FLAG_VIBRATE); + mMediaController.adjustVolume(0, flags); } else { MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy( - mVolumeControlStreamType, 0, - AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE); + mVolumeControlStreamType, 0, flags); } return true; } @@ -2367,12 +2370,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { - if (mOutsetBottom != null) { - final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); - int bottom = (int) mOutsetBottom.getDimension(metrics); + if (mOutsetBottomPx != 0) { WindowInsets newInsets = insets.replaceSystemWindowInsets( insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), - insets.getSystemWindowInsetRight(), bottom); + insets.getSystemWindowInsetRight(), mOutsetBottomPx); return super.dispatchApplyWindowInsets(newInsets); } else { return super.dispatchApplyWindowInsets(insets); @@ -2591,12 +2592,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - if (mOutsetBottom != null) { + if (mOutsetBottomPx != 0) { int mode = MeasureSpec.getMode(heightMeasureSpec); if (mode != MeasureSpec.UNSPECIFIED) { - int outset = (int) mOutsetBottom.getDimension(metrics); int height = MeasureSpec.getSize(heightMeasureSpec); - heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + outset, mode); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + mOutsetBottomPx, mode); } } @@ -2980,8 +2980,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mStatusGuard == null) { mStatusGuard = new View(mContext); - mStatusGuard.setBackgroundColor(mContext.getResources() - .getColor(R.color.input_method_navigation_guard)); + mStatusGuard.setBackgroundColor(mContext.getColor( + R.color.input_method_navigation_guard)); addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), new LayoutParams(LayoutParams.MATCH_PARENT, mlp.topMargin, Gravity.START | Gravity.TOP)); @@ -3040,8 +3040,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // position the navigation guard view, creating it if necessary if (mNavigationGuard == null) { mNavigationGuard = new View(mContext); - mNavigationGuard.setBackgroundColor(mContext.getResources() - .getColor(R.color.input_method_navigation_guard)); + mNavigationGuard.setBackgroundColor(mContext.getColor( + R.color.input_method_navigation_guard)); addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view), new LayoutParams(LayoutParams.MATCH_PARENT, insets.getSystemWindowInsetBottom(), @@ -3471,10 +3471,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { final boolean shouldUseBottomOutset = display.getDisplayId() == Display.DEFAULT_DISPLAY || (getForcedWindowFlags() & FLAG_FULLSCREEN) != 0; - if (shouldUseBottomOutset && a.hasValue(R.styleable.Window_windowOutsetBottom)) { - if (mOutsetBottom == null) mOutsetBottom = new TypedValue(); - a.getValue(R.styleable.Window_windowOutsetBottom, - mOutsetBottom); + if (shouldUseBottomOutset) { + mOutsetBottomPx = ScreenShapeHelper.getWindowOutsetBottomPx( + getContext().getResources().getDisplayMetrics(), a); } } diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 38867a8..ef98bbc 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -26,7 +26,7 @@ import android.graphics.Rect; /** * <p>A display list records a series of graphics related operations and can replay * them later. Display lists are usually built by recording operations on a - * {@link HardwareCanvas}. Replaying the operations from a display list avoids + * {@link DisplayListCanvas}. Replaying the operations from a display list avoids * executing application code on every frame, and is thus much more efficient.</p> * * <p>Display lists are used internally for all views by default, and are not @@ -43,7 +43,7 @@ import android.graphics.Rect; * affected paragraph needs to be recorded again.</p> * * <h3>Hardware acceleration</h3> - * <p>Display lists can only be replayed using a {@link HardwareCanvas}. They are not + * <p>Display lists can only be replayed using a {@link DisplayListCanvas}. They are not * supported in software. Always make sure that the {@link android.graphics.Canvas} * you are using to render a display list is hardware accelerated using * {@link android.graphics.Canvas#isHardwareAccelerated()}.</p> @@ -53,7 +53,7 @@ import android.graphics.Rect; * HardwareRenderer renderer = myView.getHardwareRenderer(); * if (renderer != null) { * DisplayList displayList = renderer.createDisplayList(); - * HardwareCanvas canvas = displayList.start(width, height); + * DisplayListCanvas canvas = displayList.start(width, height); * try { * // Draw onto the canvas * // For instance: canvas.drawBitmap(...); @@ -67,8 +67,8 @@ import android.graphics.Rect; * <pre class="prettyprint"> * protected void onDraw(Canvas canvas) { * if (canvas.isHardwareAccelerated()) { - * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas; - * hardwareCanvas.drawDisplayList(mDisplayList); + * DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas; + * displayListCanvas.drawDisplayList(mDisplayList); * } * } * </pre> @@ -92,7 +92,7 @@ import android.graphics.Rect; * <pre class="prettyprint"> * private void createDisplayList() { * mDisplayList = DisplayList.create("MyDisplayList"); - * HardwareCanvas canvas = mDisplayList.start(width, height); + * DisplayListCanvas canvas = mDisplayList.start(width, height); * try { * for (Bitmap b : mBitmaps) { * canvas.drawBitmap(b, 0.0f, 0.0f, null); @@ -105,8 +105,8 @@ import android.graphics.Rect; * * protected void onDraw(Canvas canvas) { * if (canvas.isHardwareAccelerated()) { - * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas; - * hardwareCanvas.drawDisplayList(mDisplayList); + * DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas; + * displayListCanvas.drawDisplayList(mDisplayList); * } * } * @@ -128,7 +128,7 @@ import android.graphics.Rect; public class RenderNode { /** * Flag used when calling - * {@link HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int)} + * {@link DisplayListCanvas#drawRenderNode * When this flag is set, draw operations lying outside of the bounds of the * display list will be culled early. It is recommeneded to always set this * flag. @@ -140,29 +140,29 @@ public class RenderNode { /** * Indicates that the display list is done drawing. * - * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int) + * @see DisplayListCanvas#drawRenderNode(RenderNode, int) */ public static final int STATUS_DONE = 0x0; /** * Indicates that the display list needs another drawing pass. * - * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int) + * @see DisplayListCanvas#drawRenderNode(RenderNode, int) */ public static final int STATUS_DRAW = 0x1; /** * Indicates that the display list needs to re-execute its GL functors. * - * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int) - * @see HardwareCanvas#callDrawGLFunction(long) + * @see DisplayListCanvas#drawRenderNode(RenderNode, int) + * @see DisplayListCanvas#callDrawGLFunction2(long) */ public static final int STATUS_INVOKE = 0x2; /** * Indicates that the display list performed GL drawing operations. * - * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int) + * @see DisplayListCanvas#drawRenderNode(RenderNode, int) */ public static final int STATUS_DREW = 0x4; @@ -213,7 +213,7 @@ public class RenderNode { * stored in this display list. * * Calling this method will mark the render node invalid until - * {@link #end(HardwareCanvas)} is called. + * {@link #end(DisplayListCanvas)} is called. * Only valid render nodes can be replayed. * * @param width The width of the recording viewport @@ -221,11 +221,11 @@ public class RenderNode { * * @return A canvas to record drawing operations. * - * @see #end(HardwareCanvas) + * @see #end(DisplayListCanvas) * @see #isValid() */ - public HardwareCanvas start(int width, int height) { - HardwareCanvas canvas = DisplayListCanvas.obtain(this); + public DisplayListCanvas start(int width, int height) { + DisplayListCanvas canvas = DisplayListCanvas.obtain(this); canvas.setViewport(width, height); // The dirty rect should always be null for a display list canvas.onPreDraw(null); @@ -240,7 +240,7 @@ public class RenderNode { * @see #start(int, int) * @see #isValid() */ - public void end(HardwareCanvas endCanvas) { + public void end(DisplayListCanvas endCanvas) { if (!(endCanvas instanceof DisplayListCanvas)) { throw new IllegalArgumentException("Passed an invalid canvas to end!"); } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 83b8100..6de4d3e 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -572,7 +572,7 @@ public class Surface implements Parcelable { private final class HwuiContext { private final RenderNode mRenderNode; private long mHwuiRenderer; - private HardwareCanvas mCanvas; + private DisplayListCanvas mCanvas; HwuiContext() { mRenderNode = RenderNode.create("HwuiCanvas", null); diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 59ec058..ad34f02 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -723,6 +723,12 @@ public class TextureView extends View { mSurface.release(); } mSurface = surfaceTexture; + + // If the view is visible, update the listener in the new surface to use + // the existing listener in the view. + if (((mViewFlags & VISIBILITY_MASK) == VISIBLE)) { + mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler); + } mUpdateSurface = true; invalidateParentIfNeeded(); } diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 51fefe9..031be07 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -276,7 +276,7 @@ public class ThreadedRenderer extends HardwareRenderer { updateViewTreeDisplayList(view); if (mRootNodeNeedsUpdate || !mRootNode.isValid()) { - HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight); + DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight); try { final int saveCount = canvas.save(); canvas.translate(mInsetLeft, mInsetTop); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d345bed..cfcc6fe 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -83,6 +83,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEventSource; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -3168,6 +3169,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private Drawable mBackground; private TintInfo mBackgroundTint; + @ViewDebug.ExportedProperty(deepExport = true, prefix = "fg_") + private ForegroundInfo mForegroundInfo; + /** * RenderNode used for backgrounds. * <p> @@ -3182,13 +3186,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private String mTransitionName; - private static class TintInfo { + static class TintInfo { ColorStateList mTintList; PorterDuff.Mode mTintMode; boolean mHasTintMode; boolean mHasTintList; } + private static class ForegroundInfo { + private Drawable mDrawable; + private TintInfo mTintInfo; + private int mGravity = Gravity.FILL; + private boolean mInsidePadding = true; + private boolean mBoundsChanged = true; + private final Rect mSelfBounds = new Rect(); + private final Rect mOverlayBounds = new Rect(); + } + static class ListenerInfo { /** * Listener used to dispatch focus change events. @@ -4056,6 +4070,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setOutlineProviderFromAttribute(a.getInt(R.styleable.View_outlineProvider, PROVIDER_BACKGROUND)); break; + case R.styleable.View_foreground: + setForeground(a.getDrawable(attr)); + break; + case R.styleable.View_foregroundGravity: + setForegroundGravity(a.getInt(attr, Gravity.NO_GRAVITY)); + break; + case R.styleable.View_foregroundTintMode: + setForegroundTintMode(Drawable.parseTintMode(a.getInt(attr, -1), null)); + break; + case R.styleable.View_foregroundTint: + setForegroundTintList(a.getColorStateList(attr)); + break; + case R.styleable.View_foregroundInsidePadding: + if (mForegroundInfo == null) { + mForegroundInfo = new ForegroundInfo(); + } + mForegroundInfo.mInsidePadding = a.getBoolean(attr, + mForegroundInfo.mInsidePadding); + break; } } @@ -4813,10 +4846,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ protected boolean performButtonActionOnTouchDown(MotionEvent event) { - if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { - if (showContextMenu(event.getX(), event.getY(), event.getMetaState())) { - return true; - } + if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE && + (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { + showContextMenu(event.getX(), event.getY(), event.getMetaState()); + mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT; + return true; } return false; } @@ -5781,6 +5815,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH); } + + info.addAction(AccessibilityAction.ACTION_SHOW_ON_SCREEN); } private View findLabelForView(View view, int labeledId) { @@ -8228,6 +8264,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } } break; + case R.id.accessibility_action_show_on_screen: { + if (mAttachInfo != null) { + final Rect r = mAttachInfo.mTmpInvalRect; + getDrawingRect(r); + return requestRectangleOnScreen(r, true); + } + } break; } return false; } @@ -8801,6 +8844,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (dr != null && visible != dr.isVisible()) { dr.setVisible(visible, false); } + final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; + if (fg != null && visible != fg.isVisible()) { + fg.setVisible(visible, false); + } } /** @@ -9917,6 +9964,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mBackgroundSizeChanged = true; + if (mForegroundInfo != null) { + mForegroundInfo.mBoundsChanged = true; + } final AttachInfo ai = mAttachInfo; if (ai != null) { @@ -10755,6 +10805,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); } mBackgroundSizeChanged = true; + if (mForegroundInfo != null) { + mForegroundInfo.mBoundsChanged = true; + } invalidateParentIfNeeded(); if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed @@ -10820,6 +10873,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); } mBackgroundSizeChanged = true; + if (mForegroundInfo != null) { + mForegroundInfo.mBoundsChanged = true; + } invalidateParentIfNeeded(); if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed @@ -10879,6 +10935,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); } mBackgroundSizeChanged = true; + if (mForegroundInfo != null) { + mForegroundInfo.mBoundsChanged = true; + } invalidateParentIfNeeded(); if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed @@ -10935,6 +10994,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); } mBackgroundSizeChanged = true; + if (mForegroundInfo != null) { + mForegroundInfo.mBoundsChanged = true; + } invalidateParentIfNeeded(); if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed @@ -14216,7 +14278,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int height = mBottom - mTop; int layerType = getLayerType(); - final HardwareCanvas canvas = renderNode.start(width, height); + final DisplayListCanvas canvas = renderNode.start(width, height); canvas.setHighContrastText(mAttachInfo.mHighContrastText); try { @@ -15197,7 +15259,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (layer != null && layer.isValid()) { int restoreAlpha = mLayerPaint.getAlpha(); mLayerPaint.setAlpha((int) (alpha * 255)); - ((HardwareCanvas) canvas).drawHardwareLayer(layer, 0, 0, mLayerPaint); + ((DisplayListCanvas) canvas).drawHardwareLayer(layer, 0, 0, mLayerPaint); mLayerPaint.setAlpha(restoreAlpha); layerRendered = true; } else { @@ -15220,7 +15282,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } else { mPrivateFlags &= ~PFLAG_DIRTY_MASK; - ((HardwareCanvas) canvas).drawRenderNode(renderNode, flags); + ((DisplayListCanvas) canvas).drawRenderNode(renderNode, flags); } } } else if (cache != null) { @@ -15313,13 +15375,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Step 4, draw the children dispatchDraw(canvas); - // Step 6, draw decorations (scrollbars) - onDrawScrollBars(canvas); - + // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } + // Step 6, draw decorations (foreground, scrollbars) + onDrawForeground(canvas); + // we're done... return; } @@ -15461,12 +15524,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, canvas.restoreToCount(saveCount); - // Step 6, draw decorations (scrollbars) - onDrawScrollBars(canvas); - + // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } + + // Step 6, draw decorations (foreground, scrollbars) + onDrawForeground(canvas); } /** @@ -15494,7 +15558,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final RenderNode renderNode = mBackgroundRenderNode; if (renderNode != null && renderNode.isValid()) { setBackgroundRenderNodeProperties(renderNode); - ((HardwareCanvas) canvas).drawRenderNode(renderNode); + ((DisplayListCanvas) canvas).drawRenderNode(renderNode); return; } } @@ -15531,7 +15595,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final Rect bounds = drawable.getBounds(); final int width = bounds.width(); final int height = bounds.height(); - final HardwareCanvas canvas = renderNode.start(width, height); + final DisplayListCanvas canvas = renderNode.start(width, height); // Reverse left/top translation done by drawable canvas, which will // instead be applied by rendernode's LTRB bounds below. This way, the @@ -15849,6 +15913,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= drawn; mBackgroundSizeChanged = true; + if (mForegroundInfo != null) { + mForegroundInfo.mBoundsChanged = true; + } notifySubtreeAccessibilityStateChangedIfNeeded(); } @@ -15992,6 +16059,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mBackground != null) { mBackground.setLayoutDirection(layoutDirection); } + if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) { + mForegroundInfo.mDrawable.setLayoutDirection(layoutDirection); + } mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED; onResolveDrawables(layoutDirection); } @@ -16047,7 +16117,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @CallSuper protected boolean verifyDrawable(Drawable who) { - return who == mBackground || (mScrollCache != null && mScrollCache.scrollBar == who); + return who == mBackground || (mScrollCache != null && mScrollCache.scrollBar == who) + || (mForegroundInfo != null && mForegroundInfo.mDrawable == who); } /** @@ -16065,9 +16136,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected void drawableStateChanged() { final int[] state = getDrawableState(); - final Drawable d = mBackground; - if (d != null && d.isStateful()) { - d.setState(state); + final Drawable bg = mBackground; + if (bg != null && bg.isStateful()) { + bg.setState(state); + } + + final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; + if (fg != null && fg.isStateful()) { + fg.setState(state); } if (mScrollCache != null) { @@ -16099,6 +16175,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mBackground != null) { mBackground.setHotspot(x, y); } + if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) { + mForegroundInfo.mDrawable.setHotspot(x, y); + } dispatchDrawableHotspotChanged(x, y); } @@ -16270,6 +16349,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mStateListAnimator != null) { mStateListAnimator.jumpToCurrentState(); } + if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) { + mForegroundInfo.mDrawable.jumpToCurrentState(); + } } /** @@ -16554,6 +16636,249 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns the drawable used as the foreground of this View. The + * foreground drawable, if non-null, is always drawn on top of the view's content. + * + * @return a Drawable or null if no foreground was set + * + * @see #onDrawForeground(Canvas) + */ + public Drawable getForeground() { + return mForegroundInfo != null ? mForegroundInfo.mDrawable : null; + } + + /** + * Supply a Drawable that is to be rendered on top of all of the content in the view. + * + * @param foreground the Drawable to be drawn on top of the children + * + * @attr ref android.R.styleable#View_foreground + */ + public void setForeground(Drawable foreground) { + if (mForegroundInfo == null) { + if (foreground == null) { + // Nothing to do. + return; + } + mForegroundInfo = new ForegroundInfo(); + } + + if (foreground == mForegroundInfo.mDrawable) { + // Nothing to do + return; + } + + if (mForegroundInfo.mDrawable != null) { + mForegroundInfo.mDrawable.setCallback(null); + unscheduleDrawable(mForegroundInfo.mDrawable); + } + + mForegroundInfo.mDrawable = foreground; + mForegroundInfo.mBoundsChanged = true; + if (foreground != null) { + setWillNotDraw(false); + foreground.setCallback(this); + foreground.setLayoutDirection(getLayoutDirection()); + if (foreground.isStateful()) { + foreground.setState(getDrawableState()); + } + applyForegroundTint(); + } + requestLayout(); + invalidate(); + } + + /** + * Magic bit used to support features of framework-internal window decor implementation details. + * This used to live exclusively in FrameLayout. + * + * @return true if the foreground should draw inside the padding region or false + * if it should draw inset by the view's padding + * @hide internal use only; only used by FrameLayout and internal screen layouts. + */ + public boolean isForegroundInsidePadding() { + return mForegroundInfo != null ? mForegroundInfo.mInsidePadding : true; + } + + /** + * Describes how the foreground is positioned. + * + * @return foreground gravity. + * + * @see #setForegroundGravity(int) + * + * @attr ref android.R.styleable#View_foregroundGravity + */ + public int getForegroundGravity() { + return mForegroundInfo != null ? mForegroundInfo.mGravity + : Gravity.START | Gravity.TOP; + } + + /** + * Describes how the foreground is positioned. Defaults to START and TOP. + * + * @param gravity see {@link android.view.Gravity} + * + * @see #getForegroundGravity() + * + * @attr ref android.R.styleable#View_foregroundGravity + */ + public void setForegroundGravity(int gravity) { + if (mForegroundInfo == null) { + mForegroundInfo = new ForegroundInfo(); + } + + if (mForegroundInfo.mGravity != gravity) { + if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.START; + } + + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.TOP; + } + + mForegroundInfo.mGravity = gravity; + requestLayout(); + } + } + + /** + * Applies a tint to the foreground drawable. Does not modify the current tint + * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. + * <p> + * Subsequent calls to {@link #setForeground(Drawable)} will automatically + * mutate the drawable and apply the specified tint and tint mode using + * {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#View_foregroundTint + * @see #getForegroundTintList() + * @see Drawable#setTintList(ColorStateList) + */ + public void setForegroundTintList(@Nullable ColorStateList tint) { + if (mForegroundInfo == null) { + mForegroundInfo = new ForegroundInfo(); + } + if (mForegroundInfo.mTintInfo == null) { + mForegroundInfo.mTintInfo = new TintInfo(); + } + mForegroundInfo.mTintInfo.mTintList = tint; + mForegroundInfo.mTintInfo.mHasTintList = true; + + applyForegroundTint(); + } + + /** + * Return the tint applied to the foreground drawable, if specified. + * + * @return the tint applied to the foreground drawable + * @attr ref android.R.styleable#View_foregroundTint + * @see #setForegroundTintList(ColorStateList) + */ + @Nullable + public ColorStateList getForegroundTintList() { + return mForegroundInfo != null && mForegroundInfo.mTintInfo != null + ? mBackgroundTint.mTintList : null; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setForegroundTintList(ColorStateList)}} to the background + * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#View_foregroundTintMode + * @see #getForegroundTintMode() + * @see Drawable#setTintMode(PorterDuff.Mode) + */ + public void setForegroundTintMode(@Nullable PorterDuff.Mode tintMode) { + if (mBackgroundTint == null) { + mBackgroundTint = new TintInfo(); + } + mBackgroundTint.mTintMode = tintMode; + mBackgroundTint.mHasTintMode = true; + + applyBackgroundTint(); + } + + /** + * Return the blending mode used to apply the tint to the foreground + * drawable, if specified. + * + * @return the blending mode used to apply the tint to the foreground + * drawable + * @attr ref android.R.styleable#View_foregroundTintMode + * @see #setBackgroundTintMode(PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getForegroundTintMode() { + return mForegroundInfo != null && mForegroundInfo.mTintInfo != null + ? mForegroundInfo.mTintInfo.mTintMode : null; + } + + private void applyForegroundTint() { + if (mForegroundInfo != null && mForegroundInfo.mDrawable != null + && mForegroundInfo.mTintInfo != null) { + final TintInfo tintInfo = mForegroundInfo.mTintInfo; + if (tintInfo.mHasTintList || tintInfo.mHasTintMode) { + mForegroundInfo.mDrawable = mForegroundInfo.mDrawable.mutate(); + + if (tintInfo.mHasTintList) { + mForegroundInfo.mDrawable.setTintList(tintInfo.mTintList); + } + + if (tintInfo.mHasTintMode) { + mForegroundInfo.mDrawable.setTintMode(tintInfo.mTintMode); + } + + // The drawable (or one of its children) may not have been + // stateful before applying the tint, so let's try again. + if (mForegroundInfo.mDrawable.isStateful()) { + mForegroundInfo.mDrawable.setState(getDrawableState()); + } + } + } + } + + /** + * Draw any foreground content for this view. + * + * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground} + * drawable or other view-specific decorations. The foreground is drawn on top of the + * primary view content.</p> + * + * @param canvas canvas to draw into + */ + public void onDrawForeground(Canvas canvas) { + onDrawScrollBars(canvas); + + final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; + if (foreground != null) { + if (mForegroundInfo.mBoundsChanged) { + mForegroundInfo.mBoundsChanged = false; + final Rect selfBounds = mForegroundInfo.mSelfBounds; + final Rect overlayBounds = mForegroundInfo.mOverlayBounds; + + if (mForegroundInfo.mInsidePadding) { + selfBounds.set(0, 0, getWidth(), getHeight()); + } else { + selfBounds.set(getPaddingLeft(), getPaddingTop(), + getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); + } + + final int ld = getLayoutDirection(); + Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), + foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); + foreground.setBounds(overlayBounds); + } + + foreground.draw(canvas); + } + } + + /** * Sets the padding. The view may add on the space required to display * the scrollbars, depending on the style and visibility of the scrollbars. * So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop}, @@ -18090,6 +18415,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // parts from this transparent region. applyDrawableToTransparentRegion(mBackground, region); } + final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; + if (foreground != null) { + applyDrawableToTransparentRegion(mForegroundInfo.mDrawable, region); + } } return true; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1473806..294174a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -40,7 +40,6 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.media.AudioManager; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -77,6 +76,7 @@ import android.widget.Scroller; import com.android.internal.R; import com.android.internal.os.SomeArgs; +import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; @@ -118,10 +118,11 @@ public final class ViewRootImpl implements ViewParent, * at 60 Hz. This can be used to measure the potential framerate. */ private static final String PROPERTY_PROFILE_RENDERING = "viewroot.profile_rendering"; - private static final String PROPERTY_MEDIA_DISABLED = "config.disable_media"; - // property used by emulator to determine display shape + // properties used by emulator to determine display shape public static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; + public static final String PROPERTY_EMULATOR_WIN_OUTSET_BOTTOM_PX = + "ro.emu.win_outset_bottom_px"; /** * Maximum time we allow the user to roll the trackball enough to generate @@ -299,8 +300,6 @@ public final class ViewRootImpl implements ViewParent, private Choreographer.FrameCallback mRenderProfiler; private boolean mRenderProfilingEnabled; - private boolean mMediaDisabled; - // Variables to track frames per second, enabled via DEBUG_FPS flag private long mFpsStartTime = -1; private long mFpsPrevTime = -1; @@ -334,8 +333,6 @@ public final class ViewRootImpl implements ViewParent, /** Set to true once doDie() has been called. */ private boolean mRemoved; - private boolean mIsEmulator; - private boolean mIsCircularEmulator; private final boolean mWindowIsRound; /** @@ -392,8 +389,7 @@ public final class ViewRootImpl implements ViewParent, mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); loadSystemProperties(); - mWindowIsRound = context.getResources().getBoolean( - com.android.internal.R.bool.config_windowIsRound); + mWindowIsRound = ScreenShapeHelper.getWindowIsRound(context.getResources()); } public static void addFirstDrawHandler(Runnable callback) { @@ -1248,9 +1244,8 @@ public final class ViewRootImpl implements ViewParent, contentInsets = mPendingContentInsets; stableInsets = mPendingStableInsets; } - final boolean isRound = (mIsEmulator && mIsCircularEmulator) || mWindowIsRound; mLastWindowInsets = new WindowInsets(contentInsets, - null /* windowDecorInsets */, stableInsets, isRound); + null /* windowDecorInsets */, stableInsets, mWindowIsRound); } return mLastWindowInsets; } @@ -2309,12 +2304,12 @@ public final class ViewRootImpl implements ViewParent, final Paint mResizePaint = new Paint(); @Override - public void onHardwarePreDraw(HardwareCanvas canvas) { + public void onHardwarePreDraw(DisplayListCanvas canvas) { canvas.translate(-mHardwareXOffset, -mHardwareYOffset); } @Override - public void onHardwarePostDraw(HardwareCanvas canvas) { + public void onHardwarePostDraw(DisplayListCanvas canvas) { if (mResizeBuffer != null) { mResizePaint.setAlpha(mResizeAlpha); canvas.drawHardwareLayer(mResizeBuffer, mHardwareXOffset, mHardwareYOffset, @@ -5362,10 +5357,6 @@ public final class ViewRootImpl implements ViewParent, public void playSoundEffect(int effectId) { checkThread(); - if (mMediaDisabled) { - return; - } - try { final AudioManager audioManager = getAudioManager(); @@ -5572,9 +5563,6 @@ public final class ViewRootImpl implements ViewParent, mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false); profileRendering(mAttachInfo.mHasWindowFocus); - // Media (used by sound effects) - mMediaDisabled = SystemProperties.getBoolean(PROPERTY_MEDIA_DISABLED, false); - // Hardware rendering if (mAttachInfo.mHardwareRenderer != null) { if (mAttachInfo.mHardwareRenderer.loadSystemProperties()) { @@ -5590,11 +5578,6 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200); } } - - // detect emulator - mIsEmulator = Build.HARDWARE.contains("goldfish"); - mIsCircularEmulator = - SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false); } }); } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 1cebe3f..57558ff 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -249,12 +249,12 @@ public final class WindowManagerGlobal { final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); - } else if (ActivityManager.isHighEndGfx()) { - // If there's no parent and we're running on L or above (or in the - // system context), assume we want hardware acceleration. + } else { + // If there's no parent, then hardware acceleration for this view is + // set from the application's hardware acceleration setting. final Context context = view.getContext(); - if (context != null && context.getApplicationInfo().targetSdkVersion - >= Build.VERSION_CODES.LOLLIPOP) { + if (context != null + && context.getApplicationInfo().hardwareAccelerated) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 6096d7d..77082b0 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -29,6 +29,8 @@ import android.util.LongArray; import android.util.Pools.SynchronizedPool; import android.view.View; +import com.android.internal.R; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -3402,6 +3404,15 @@ public class AccessibilityNodeInfo implements Parcelable { new AccessibilityAction( AccessibilityNodeInfo.ACTION_SET_TEXT, null); + /** + * Action that requests the node make its bounding rectangle visible + * on the screen, scrolling if necessary just enough. + * + * @see View#requestRectangleOnScreen(Rect) + */ + public static final AccessibilityAction ACTION_SHOW_ON_SCREEN = + new AccessibilityAction(R.id.accessibility_action_show_on_screen, null); + private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<AccessibilityAction>(); static { sStandardActions.add(ACTION_FOCUS); @@ -3426,6 +3437,7 @@ public class AccessibilityNodeInfo implements Parcelable { sStandardActions.add(ACTION_COLLAPSE); sStandardActions.add(ACTION_DISMISS); sStandardActions.add(ACTION_SET_TEXT); + sStandardActions.add(ACTION_SHOW_ON_SCREEN); } private final int mActionId; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 0996810..78604bf 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1992,8 +1992,8 @@ public final class InputMethodManager { List<Object> info = mService.getShortcutInputMethodsAndSubtypes(); // "info" has imi1, subtype1, subtype2, imi2, subtype2, imi3, subtype3..in the list ArrayList<InputMethodSubtype> subtypes = null; - final int N = info.size(); - if (info != null && N > 0) { + if (info != null && !info.isEmpty()) { + final int N = info.size(); for (int i = 0; i < N; ++i) { Object o = info.get(i); if (o instanceof InputMethodInfo) { diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 4737e9b..0b18bb8 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -284,13 +284,19 @@ public class WebChromeClient { * currently set for that origin. The host application should invoke the * specified callback with the desired permission state. See * {@link GeolocationPermissions} for details. + * + * If this method isn't overridden, the callback is invoked with permission + * denied state. + * * @param origin The origin of the web content attempting to use the * Geolocation API. * @param callback The callback to use to set the permission state for the * origin. */ public void onGeolocationPermissionsShowPrompt(String origin, - GeolocationPermissions.Callback callback) {} + GeolocationPermissions.Callback callback) { + callback.invoke(origin, false, false); + } /** * Notify the host application that a request for Geolocation permissions, diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 6711a6b..aa77d5e 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -299,7 +299,6 @@ public class WebView extends AbsoluteLayout "android.webkit.DATA_REDUCTION_PROXY_SETTING_CHANGED"; private static final String LOGTAG = "WebView"; - private static final boolean TRACE = false; // Throwing an exception for incorrect thread usage if the // build target is JB MR2 or newer. Defaults to false, and is @@ -599,7 +598,6 @@ public class WebView extends AbsoluteLayout sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2; checkThread(); - if (TRACE) Log.d(LOGTAG, "WebView<init>"); ensureProviderCreated(); mProvider.init(javaScriptInterfaces, privateBrowsing); @@ -614,7 +612,6 @@ public class WebView extends AbsoluteLayout */ public void setHorizontalScrollbarOverlay(boolean overlay) { checkThread(); - if (TRACE) Log.d(LOGTAG, "setHorizontalScrollbarOverlay=" + overlay); mProvider.setHorizontalScrollbarOverlay(overlay); } @@ -625,7 +622,6 @@ public class WebView extends AbsoluteLayout */ public void setVerticalScrollbarOverlay(boolean overlay) { checkThread(); - if (TRACE) Log.d(LOGTAG, "setVerticalScrollbarOverlay=" + overlay); mProvider.setVerticalScrollbarOverlay(overlay); } @@ -680,7 +676,6 @@ public class WebView extends AbsoluteLayout @Deprecated public void setCertificate(SslCertificate certificate) { checkThread(); - if (TRACE) Log.d(LOGTAG, "setCertificate=" + certificate); mProvider.setCertificate(certificate); } @@ -704,7 +699,6 @@ public class WebView extends AbsoluteLayout @Deprecated public void savePassword(String host, String username, String password) { checkThread(); - if (TRACE) Log.d(LOGTAG, "savePassword=" + host); mProvider.savePassword(host, username, password); } @@ -724,7 +718,6 @@ public class WebView extends AbsoluteLayout public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) { checkThread(); - if (TRACE) Log.d(LOGTAG, "setHttpAuthUsernamePassword=" + host); mProvider.setHttpAuthUsernamePassword(host, realm, username, password); } @@ -754,7 +747,6 @@ public class WebView extends AbsoluteLayout */ public void destroy() { checkThread(); - if (TRACE) Log.d(LOGTAG, "destroy"); mProvider.destroy(); } @@ -800,7 +792,6 @@ public class WebView extends AbsoluteLayout */ public void setNetworkAvailable(boolean networkUp) { checkThread(); - if (TRACE) Log.d(LOGTAG, "setNetworkAvailable=" + networkUp); mProvider.setNetworkAvailable(networkUp); } @@ -817,7 +808,6 @@ public class WebView extends AbsoluteLayout */ public WebBackForwardList saveState(Bundle outState) { checkThread(); - if (TRACE) Log.d(LOGTAG, "saveState"); return mProvider.saveState(outState); } @@ -834,7 +824,6 @@ public class WebView extends AbsoluteLayout @Deprecated public boolean savePicture(Bundle b, final File dest) { checkThread(); - if (TRACE) Log.d(LOGTAG, "savePicture=" + dest.getName()); return mProvider.savePicture(b, dest); } @@ -852,7 +841,6 @@ public class WebView extends AbsoluteLayout @Deprecated public boolean restorePicture(Bundle b, File src) { checkThread(); - if (TRACE) Log.d(LOGTAG, "restorePicture=" + src.getName()); return mProvider.restorePicture(b, src); } @@ -870,7 +858,6 @@ public class WebView extends AbsoluteLayout */ public WebBackForwardList restoreState(Bundle inState) { checkThread(); - if (TRACE) Log.d(LOGTAG, "restoreState"); return mProvider.restoreState(inState); } @@ -887,15 +874,6 @@ public class WebView extends AbsoluteLayout */ public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { checkThread(); - if (TRACE) { - StringBuilder headers = new StringBuilder(); - if (additionalHttpHeaders != null) { - for (Map.Entry<String, String> entry : additionalHttpHeaders.entrySet()) { - headers.append(entry.getKey() + ":" + entry.getValue() + "\n"); - } - } - Log.d(LOGTAG, "loadUrl(extra headers)=" + url + "\n" + headers); - } mProvider.loadUrl(url, additionalHttpHeaders); } @@ -906,7 +884,6 @@ public class WebView extends AbsoluteLayout */ public void loadUrl(String url) { checkThread(); - if (TRACE) Log.d(LOGTAG, "loadUrl=" + url); mProvider.loadUrl(url); } @@ -921,7 +898,6 @@ public class WebView extends AbsoluteLayout */ public void postUrl(String url, byte[] postData) { checkThread(); - if (TRACE) Log.d(LOGTAG, "postUrl=" + url); if (URLUtil.isNetworkUrl(url)) { mProvider.postUrl(url, postData); } else { @@ -960,7 +936,6 @@ public class WebView extends AbsoluteLayout */ public void loadData(String data, String mimeType, String encoding) { checkThread(); - if (TRACE) Log.d(LOGTAG, "loadData"); mProvider.loadData(data, mimeType, encoding); } @@ -993,7 +968,6 @@ public class WebView extends AbsoluteLayout public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) { checkThread(); - if (TRACE) Log.d(LOGTAG, "loadDataWithBaseURL=" + baseUrl); mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); } @@ -1010,7 +984,6 @@ public class WebView extends AbsoluteLayout */ public void evaluateJavascript(String script, ValueCallback<String> resultCallback) { checkThread(); - if (TRACE) Log.d(LOGTAG, "evaluateJavascript=" + script); mProvider.evaluateJavaScript(script, resultCallback); } @@ -1021,7 +994,6 @@ public class WebView extends AbsoluteLayout */ public void saveWebArchive(String filename) { checkThread(); - if (TRACE) Log.d(LOGTAG, "saveWebArchive=" + filename); mProvider.saveWebArchive(filename); } @@ -1039,7 +1011,6 @@ public class WebView extends AbsoluteLayout */ public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) { checkThread(); - if (TRACE) Log.d(LOGTAG, "saveWebArchive(auto)=" + basename); mProvider.saveWebArchive(basename, autoname, callback); } @@ -1048,7 +1019,6 @@ public class WebView extends AbsoluteLayout */ public void stopLoading() { checkThread(); - if (TRACE) Log.d(LOGTAG, "stopLoading"); mProvider.stopLoading(); } @@ -1057,7 +1027,6 @@ public class WebView extends AbsoluteLayout */ public void reload() { checkThread(); - if (TRACE) Log.d(LOGTAG, "reload"); mProvider.reload(); } @@ -1076,7 +1045,6 @@ public class WebView extends AbsoluteLayout */ public void goBack() { checkThread(); - if (TRACE) Log.d(LOGTAG, "goBack"); mProvider.goBack(); } @@ -1095,7 +1063,6 @@ public class WebView extends AbsoluteLayout */ public void goForward() { checkThread(); - if (TRACE) Log.d(LOGTAG, "goForward"); mProvider.goForward(); } @@ -1121,7 +1088,6 @@ public class WebView extends AbsoluteLayout */ public void goBackOrForward(int steps) { checkThread(); - if (TRACE) Log.d(LOGTAG, "goBackOrForwad=" + steps); mProvider.goBackOrForward(steps); } @@ -1141,7 +1107,6 @@ public class WebView extends AbsoluteLayout */ public boolean pageUp(boolean top) { checkThread(); - if (TRACE) Log.d(LOGTAG, "pageUp"); return mProvider.pageUp(top); } @@ -1153,7 +1118,6 @@ public class WebView extends AbsoluteLayout */ public boolean pageDown(boolean bottom) { checkThread(); - if (TRACE) Log.d(LOGTAG, "pageDown"); return mProvider.pageDown(bottom); } @@ -1207,7 +1171,6 @@ public class WebView extends AbsoluteLayout */ public void insertVisualStateCallback(long requestId, VisualStateCallback callback) { checkThread(); - if (TRACE) Log.d(LOGTAG, "insertVisualStateCallback"); mProvider.insertVisualStateCallback(requestId, callback); } @@ -1220,7 +1183,6 @@ public class WebView extends AbsoluteLayout @Deprecated public void clearView() { checkThread(); - if (TRACE) Log.d(LOGTAG, "clearView"); mProvider.clearView(); } @@ -1251,7 +1213,6 @@ public class WebView extends AbsoluteLayout @Deprecated public Picture capturePicture() { checkThread(); - if (TRACE) Log.d(LOGTAG, "capturePicture"); return mProvider.capturePicture(); } @@ -1262,7 +1223,6 @@ public class WebView extends AbsoluteLayout @Deprecated public PrintDocumentAdapter createPrintDocumentAdapter() { checkThread(); - if (TRACE) Log.d(LOGTAG, "createPrintDocumentAdapter"); return mProvider.createPrintDocumentAdapter("default"); } @@ -1281,7 +1241,6 @@ public class WebView extends AbsoluteLayout */ public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) { checkThread(); - if (TRACE) Log.d(LOGTAG, "createPrintDocumentAdapter"); return mProvider.createPrintDocumentAdapter(documentName); } @@ -1321,7 +1280,6 @@ public class WebView extends AbsoluteLayout */ public void setInitialScale(int scaleInPercent) { checkThread(); - if (TRACE) Log.d(LOGTAG, "setInitialScale=" + scaleInPercent); mProvider.setInitialScale(scaleInPercent); } @@ -1332,7 +1290,6 @@ public class WebView extends AbsoluteLayout */ public void invokeZoomPicker() { checkThread(); - if (TRACE) Log.d(LOGTAG, "invokeZoomPicker"); mProvider.invokeZoomPicker(); } @@ -1356,7 +1313,6 @@ public class WebView extends AbsoluteLayout */ public HitTestResult getHitTestResult() { checkThread(); - if (TRACE) Log.d(LOGTAG, "getHitTestResult"); return mProvider.getHitTestResult(); } @@ -1375,7 +1331,6 @@ public class WebView extends AbsoluteLayout */ public void requestFocusNodeHref(Message hrefMsg) { checkThread(); - if (TRACE) Log.d(LOGTAG, "requestFocusNodeHref"); mProvider.requestFocusNodeHref(hrefMsg); } @@ -1388,7 +1343,6 @@ public class WebView extends AbsoluteLayout */ public void requestImageRef(Message msg) { checkThread(); - if (TRACE) Log.d(LOGTAG, "requestImageRef"); mProvider.requestImageRef(msg); } @@ -1493,7 +1447,6 @@ public class WebView extends AbsoluteLayout */ public void pauseTimers() { checkThread(); - if (TRACE) Log.d(LOGTAG, "pauseTimers"); mProvider.pauseTimers(); } @@ -1503,7 +1456,6 @@ public class WebView extends AbsoluteLayout */ public void resumeTimers() { checkThread(); - if (TRACE) Log.d(LOGTAG, "resumeTimers"); mProvider.resumeTimers(); } @@ -1516,7 +1468,6 @@ public class WebView extends AbsoluteLayout */ public void onPause() { checkThread(); - if (TRACE) Log.d(LOGTAG, "onPause"); mProvider.onPause(); } @@ -1525,7 +1476,6 @@ public class WebView extends AbsoluteLayout */ public void onResume() { checkThread(); - if (TRACE) Log.d(LOGTAG, "onResume"); mProvider.onResume(); } @@ -1548,7 +1498,6 @@ public class WebView extends AbsoluteLayout @Deprecated public void freeMemory() { checkThread(); - if (TRACE) Log.d(LOGTAG, "freeMemory"); mProvider.freeMemory(); } @@ -1560,7 +1509,6 @@ public class WebView extends AbsoluteLayout */ public void clearCache(boolean includeDiskFiles) { checkThread(); - if (TRACE) Log.d(LOGTAG, "clearCache"); mProvider.clearCache(includeDiskFiles); } @@ -1572,7 +1520,6 @@ public class WebView extends AbsoluteLayout */ public void clearFormData() { checkThread(); - if (TRACE) Log.d(LOGTAG, "clearFormData"); mProvider.clearFormData(); } @@ -1581,7 +1528,6 @@ public class WebView extends AbsoluteLayout */ public void clearHistory() { checkThread(); - if (TRACE) Log.d(LOGTAG, "clearHistory"); mProvider.clearHistory(); } @@ -1591,7 +1537,6 @@ public class WebView extends AbsoluteLayout */ public void clearSslPreferences() { checkThread(); - if (TRACE) Log.d(LOGTAG, "clearSslPreferences"); mProvider.clearSslPreferences(); } @@ -1607,7 +1552,6 @@ public class WebView extends AbsoluteLayout * callback. The runnable will be called in UI thread. */ public static void clearClientCertPreferences(Runnable onCleared) { - if (TRACE) Log.d(LOGTAG, "clearClientCertPreferences"); getFactory().getStatics().clearClientCertPreferences(onCleared); } @@ -1649,7 +1593,6 @@ public class WebView extends AbsoluteLayout */ public void findNext(boolean forward) { checkThread(); - if (TRACE) Log.d(LOGTAG, "findNext"); mProvider.findNext(forward); } @@ -1665,7 +1608,6 @@ public class WebView extends AbsoluteLayout @Deprecated public int findAll(String find) { checkThread(); - if (TRACE) Log.d(LOGTAG, "findAll"); StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync"); return mProvider.findAll(find); } @@ -1680,7 +1622,6 @@ public class WebView extends AbsoluteLayout */ public void findAllAsync(String find) { checkThread(); - if (TRACE) Log.d(LOGTAG, "findAllAsync"); mProvider.findAllAsync(find); } @@ -1701,7 +1642,6 @@ public class WebView extends AbsoluteLayout @Deprecated public boolean showFindDialog(String text, boolean showIme) { checkThread(); - if (TRACE) Log.d(LOGTAG, "showFindDialog"); return mProvider.showFindDialog(text, showIme); } @@ -1758,7 +1698,6 @@ public class WebView extends AbsoluteLayout */ public void clearMatches() { checkThread(); - if (TRACE) Log.d(LOGTAG, "clearMatches"); mProvider.clearMatches(); } @@ -1819,7 +1758,6 @@ public class WebView extends AbsoluteLayout @Deprecated public void setPictureListener(PictureListener listener) { checkThread(); - if (TRACE) Log.d(LOGTAG, "setPictureListener=" + listener); mProvider.setPictureListener(listener); } @@ -1876,7 +1814,6 @@ public class WebView extends AbsoluteLayout */ public void addJavascriptInterface(Object object, String name) { checkThread(); - if (TRACE) Log.d(LOGTAG, "addJavascriptInterface=" + name); mProvider.addJavascriptInterface(object, name); } @@ -1889,7 +1826,6 @@ public class WebView extends AbsoluteLayout */ public void removeJavascriptInterface(String name) { checkThread(); - if (TRACE) Log.d(LOGTAG, "removeJavascriptInterface=" + name); mProvider.removeJavascriptInterface(name); } @@ -1905,7 +1841,6 @@ public class WebView extends AbsoluteLayout */ public WebMessagePort[] createWebMessageChannel() { checkThread(); - if (TRACE) Log.d(LOGTAG, "createWebMessageChannel"); return mProvider.createWebMessageChannel(); } @@ -1920,7 +1855,6 @@ public class WebView extends AbsoluteLayout */ public void postMessageToMainFrame(WebMessage message, Uri targetOrigin) { checkThread(); - if (TRACE) Log.d(LOGTAG, "postMessageToMainFrame. TargetOrigin=" + targetOrigin); mProvider.postMessageToMainFrame(message, targetOrigin); } @@ -2024,7 +1958,6 @@ public class WebView extends AbsoluteLayout public void flingScroll(int vx, int vy) { checkThread(); - if (TRACE) Log.d(LOGTAG, "flingScroll"); mProvider.flingScroll(vx, vy); } diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java index ac360fa..23af384 100644 --- a/core/java/android/webkit/WebViewDelegate.java +++ b/core/java/android/webkit/WebViewDelegate.java @@ -25,7 +25,7 @@ import android.graphics.Canvas; import android.os.SystemProperties; import android.os.Trace; import android.util.SparseArray; -import android.view.HardwareCanvas; +import android.view.DisplayListCanvas; import android.view.View; import android.view.ViewRootImpl; @@ -101,12 +101,12 @@ public final class WebViewDelegate { * @throws IllegalArgumentException if the canvas is not hardware accelerated */ public void callDrawGlFunction(Canvas canvas, long nativeDrawGLFunctor) { - if (!(canvas instanceof HardwareCanvas)) { + if (!(canvas instanceof DisplayListCanvas)) { // Canvas#isHardwareAccelerated() is only true for subclasses of HardwareCanvas. throw new IllegalArgumentException(canvas.getClass().getName() - + " is not hardware accelerated"); + + " is not a DisplayList canvas"); } - ((HardwareCanvas) canvas).callDrawGLFunction2(nativeDrawGLFunctor); + ((DisplayListCanvas) canvas).callDrawGLFunction2(nativeDrawGLFunctor); } /** @@ -153,7 +153,7 @@ public final class WebViewDelegate { } /** - * Adds the WebView asset path to {@link AssetManager}. + * Adds the WebView asset path to {@link android.content.res.AssetManager}. */ public void addWebViewAssetPath(Context context) { context.getAssets().addAssetPath( diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java index 5c05b5a..6feb94b 100644 --- a/core/java/android/widget/AppSecurityPermissions.java +++ b/core/java/android/widget/AppSecurityPermissions.java @@ -16,6 +16,7 @@ */ package android.widget; +import android.os.UserHandle; import com.android.internal.R; import android.app.AlertDialog; @@ -243,7 +244,8 @@ public class AppSecurityPermissions { @Override public void onClick(DialogInterface dialog, int which) { PackageManager pm = getContext().getPackageManager(); - pm.revokePermission(mPackageName, mPerm.name); + pm.revokePermission(mPackageName, mPerm.name, + new UserHandle(mContext.getUserId())); PermissionItemView.this.setVisibility(View.GONE); } }; @@ -298,7 +300,7 @@ public class AppSecurityPermissions { } extractPerms(info, permSet, installedPkgInfo); } - // Get permissions related to shared user if any + // Get permissions related to shared user if any if (info.sharedUserId != null) { int sharedUid; try { @@ -358,7 +360,7 @@ public class AppSecurityPermissions { String permName = strList[i]; // If we are only looking at an existing app, then we only // care about permissions that have actually been granted to it. - if (installedPkgInfo != null && info == installedPkgInfo) { + if (installedPkgInfo != null && info != installedPkgInfo) { if ((flagsList[i]&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { continue; } diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index 5bc16cb..2aaa356 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -18,6 +18,7 @@ package android.widget; import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.StyleRes; import android.annotation.Widget; import android.content.Context; import android.content.res.Configuration; @@ -31,6 +32,7 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -118,7 +120,9 @@ public class CalendarView extends FrameLayout { * @param count The shown week count. * * @attr ref android.R.styleable#CalendarView_shownWeekCount + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setShownWeekCount(int count) { mDelegate.setShownWeekCount(count); } @@ -129,7 +133,9 @@ public class CalendarView extends FrameLayout { * @return The shown week count. * * @attr ref android.R.styleable#CalendarView_shownWeekCount + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public int getShownWeekCount() { return mDelegate.getShownWeekCount(); } @@ -140,7 +146,9 @@ public class CalendarView extends FrameLayout { * @param color The week background color. * * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setSelectedWeekBackgroundColor(@ColorInt int color) { mDelegate.setSelectedWeekBackgroundColor(color); } @@ -151,8 +159,10 @@ public class CalendarView extends FrameLayout { * @return The week background color. * * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getSelectedWeekBackgroundColor() { return mDelegate.getSelectedWeekBackgroundColor(); } @@ -163,7 +173,9 @@ public class CalendarView extends FrameLayout { * @param color The focused month date color. * * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setFocusedMonthDateColor(@ColorInt int color) { mDelegate.setFocusedMonthDateColor(color); } @@ -174,8 +186,10 @@ public class CalendarView extends FrameLayout { * @return The focused month date color. * * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getFocusedMonthDateColor() { return mDelegate.getFocusedMonthDateColor(); } @@ -186,7 +200,9 @@ public class CalendarView extends FrameLayout { * @param color A not focused month date color. * * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setUnfocusedMonthDateColor(@ColorInt int color) { mDelegate.setUnfocusedMonthDateColor(color); } @@ -197,8 +213,10 @@ public class CalendarView extends FrameLayout { * @return A not focused month date color. * * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getUnfocusedMonthDateColor() { return mDelegate.getUnfocusedMonthDateColor(); } @@ -209,7 +227,9 @@ public class CalendarView extends FrameLayout { * @param color The week number color. * * @attr ref android.R.styleable#CalendarView_weekNumberColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setWeekNumberColor(@ColorInt int color) { mDelegate.setWeekNumberColor(color); } @@ -220,8 +240,10 @@ public class CalendarView extends FrameLayout { * @return The week number color. * * @attr ref android.R.styleable#CalendarView_weekNumberColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getWeekNumberColor() { return mDelegate.getWeekNumberColor(); } @@ -232,7 +254,9 @@ public class CalendarView extends FrameLayout { * @param color The week separator color. * * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setWeekSeparatorLineColor(@ColorInt int color) { mDelegate.setWeekSeparatorLineColor(color); } @@ -243,8 +267,10 @@ public class CalendarView extends FrameLayout { * @return The week separator color. * * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor + * @deprecated No longer used by Material-style CalendarView. */ @ColorInt + @Deprecated public int getWeekSeparatorLineColor() { return mDelegate.getWeekSeparatorLineColor(); } @@ -256,7 +282,9 @@ public class CalendarView extends FrameLayout { * @param resourceId The vertical bar drawable resource id. * * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setSelectedDateVerticalBar(@DrawableRes int resourceId) { mDelegate.setSelectedDateVerticalBar(resourceId); } @@ -268,7 +296,9 @@ public class CalendarView extends FrameLayout { * @param drawable The vertical bar drawable. * * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public void setSelectedDateVerticalBar(Drawable drawable) { mDelegate.setSelectedDateVerticalBar(drawable); } @@ -278,7 +308,9 @@ public class CalendarView extends FrameLayout { * the end of the selected date. * * @return The vertical bar drawable. + * @deprecated No longer used by Material-style CalendarView. */ + @Deprecated public Drawable getSelectedDateVerticalBar() { return mDelegate.getSelectedDateVerticalBar(); } @@ -519,29 +551,36 @@ public class CalendarView extends FrameLayout { void setShownWeekCount(int count); int getShownWeekCount(); - void setSelectedWeekBackgroundColor(int color); + void setSelectedWeekBackgroundColor(@ColorInt int color); + @ColorInt int getSelectedWeekBackgroundColor(); - void setFocusedMonthDateColor(int color); + void setFocusedMonthDateColor(@ColorInt int color); + @ColorInt int getFocusedMonthDateColor(); - void setUnfocusedMonthDateColor(int color); + void setUnfocusedMonthDateColor(@ColorInt int color); + @ColorInt int getUnfocusedMonthDateColor(); - void setWeekNumberColor(int color); + void setWeekNumberColor(@ColorInt int color); + @ColorInt int getWeekNumberColor(); - void setWeekSeparatorLineColor(int color); + void setWeekSeparatorLineColor(@ColorInt int color); + @ColorInt int getWeekSeparatorLineColor(); - void setSelectedDateVerticalBar(int resourceId); + void setSelectedDateVerticalBar(@DrawableRes int resourceId); void setSelectedDateVerticalBar(Drawable drawable); Drawable getSelectedDateVerticalBar(); - void setWeekDayTextAppearance(int resourceId); + void setWeekDayTextAppearance(@StyleRes int resourceId); + @StyleRes int getWeekDayTextAppearance(); - void setDateTextAppearance(int resourceId); + void setDateTextAppearance(@StyleRes int resourceId); + @StyleRes int getDateTextAppearance(); void setMinDate(long minDate); @@ -569,18 +608,12 @@ public class CalendarView extends FrameLayout { * An abstract class which can be used as a start for CalendarView implementations */ abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate { - /** String for parsing dates. */ - private static final String DATE_FORMAT = "MM/dd/yyyy"; - /** The default minimal date. */ protected static final String DEFAULT_MIN_DATE = "01/01/1900"; /** The default maximal date. */ protected static final String DEFAULT_MAX_DATE = "01/01/2100"; - /** Date format for parsing dates. */ - protected static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); - protected CalendarView mDelegator; protected Context mContext; protected Locale mCurrentLocale; @@ -600,21 +633,131 @@ public class CalendarView extends FrameLayout { mCurrentLocale = locale; } - /** - * Parses the given <code>date</code> and in case of success sets - * the result to the <code>outDate</code>. - * - * @return True if the date was parsed. - */ - protected boolean parseDate(String date, Calendar outDate) { - try { - outDate.setTime(DATE_FORMATTER.parse(date)); - return true; - } catch (ParseException e) { - Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); - return false; - } + @Override + public void setShownWeekCount(int count) { + // Deprecated. + } + + @Override + public int getShownWeekCount() { + // Deprecated. + return 0; + } + + @Override + public void setSelectedWeekBackgroundColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getSelectedWeekBackgroundColor() { + return 0; + } + + @Override + public void setFocusedMonthDateColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getFocusedMonthDateColor() { + return 0; + } + + @Override + public void setUnfocusedMonthDateColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getUnfocusedMonthDateColor() { + return 0; + } + + @Override + public void setWeekNumberColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getWeekNumberColor() { + // Deprecated. + return 0; + } + + @Override + public void setWeekSeparatorLineColor(@ColorInt int color) { + // Deprecated. + } + + @ColorInt + @Override + public int getWeekSeparatorLineColor() { + // Deprecated. + return 0; + } + + @Override + public void setSelectedDateVerticalBar(@DrawableRes int resId) { + // Deprecated. + } + + @Override + public void setSelectedDateVerticalBar(Drawable drawable) { + // Deprecated. + } + + @Override + public Drawable getSelectedDateVerticalBar() { + // Deprecated. + return null; + } + + @Override + public void setShowWeekNumber(boolean showWeekNumber) { + // Deprecated. + } + + @Override + public boolean getShowWeekNumber() { + // Deprecated. + return false; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // Nothing to do here, configuration changes are already propagated + // by ViewGroup. } } + /** String for parsing dates. */ + private static final String DATE_FORMAT = "MM/dd/yyyy"; + + /** Date format for parsing dates. */ + private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); + + /** + * Utility method for the date format used by CalendarView's min/max date. + * + * @hide Use only as directed. For internal use only. + */ + public static boolean parseDate(String date, Calendar outDate) { + if (date == null || date.isEmpty()) { + return false; + } + + try { + final Date parsedDate = DATE_FORMATTER.parse(date); + outDate.setTime(parsedDate); + return true; + } catch (ParseException e) { + Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); + return false; + } + } } diff --git a/core/java/android/widget/CalendarViewLegacyDelegate.java b/core/java/android/widget/CalendarViewLegacyDelegate.java index 2ab3548..6ab3828 100644 --- a/core/java/android/widget/CalendarViewLegacyDelegate.java +++ b/core/java/android/widget/CalendarViewLegacyDelegate.java @@ -27,7 +27,6 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.text.TextUtils; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -267,12 +266,12 @@ class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelega mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, LocaleData.get(Locale.getDefault()).firstDayOfWeek); final String minDate = a.getString(R.styleable.CalendarView_minDate); - if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { - parseDate(DEFAULT_MIN_DATE, mMinDate); + if (!CalendarView.parseDate(minDate, mMinDate)) { + CalendarView.parseDate(DEFAULT_MIN_DATE, mMinDate); } final String maxDate = a.getString(R.styleable.CalendarView_maxDate); - if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { - parseDate(DEFAULT_MAX_DATE, mMaxDate); + if (!CalendarView.parseDate(maxDate, mMaxDate)) { + CalendarView.parseDate(DEFAULT_MAX_DATE, mMaxDate); } if (mMaxDate.before(mMinDate)) { throw new IllegalArgumentException("Max date cannot be before min date."); diff --git a/core/java/android/widget/CalendarViewMaterialDelegate.java b/core/java/android/widget/CalendarViewMaterialDelegate.java index b0f3740..7bce756 100644 --- a/core/java/android/widget/CalendarViewMaterialDelegate.java +++ b/core/java/android/widget/CalendarViewMaterialDelegate.java @@ -16,20 +16,11 @@ package android.widget; -import com.android.internal.R; - +import android.annotation.StyleRes; import android.content.Context; -import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; import android.util.AttributeSet; -import android.util.MathUtils; import java.util.Calendar; -import java.util.Locale; - -import libcore.icu.LocaleData; class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDelegate { private final DayPickerView mDayPickerView; @@ -40,142 +31,32 @@ class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDele int defStyleAttr, int defStyleRes) { super(delegator, context); - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.CalendarView, defStyleAttr, defStyleRes); - final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, - LocaleData.get(Locale.getDefault()).firstDayOfWeek); - - final long minDate = parseDateToMillis(a.getString( - R.styleable.CalendarView_minDate), DEFAULT_MIN_DATE); - final long maxDate = parseDateToMillis(a.getString( - R.styleable.CalendarView_maxDate), DEFAULT_MAX_DATE); - if (maxDate < minDate) { - throw new IllegalArgumentException("max date cannot be before min date"); - } - - final long setDate = MathUtils.constrain(System.currentTimeMillis(), minDate, maxDate); - final int dateTextAppearanceResId = a.getResourceId( - R.styleable.CalendarView_dateTextAppearance, - R.style.TextAppearance_DeviceDefault_Small); - - a.recycle(); - - mDayPickerView = new DayPickerView(context); - mDayPickerView.setFirstDayOfWeek(firstDayOfWeek); - mDayPickerView.setCalendarTextAppearance(dateTextAppearanceResId); - mDayPickerView.setMinDate(minDate); - mDayPickerView.setMaxDate(maxDate); - mDayPickerView.setDate(setDate, false, true); + mDayPickerView = new DayPickerView(context, attrs, defStyleAttr, defStyleRes); mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener); delegator.addView(mDayPickerView); } - private long parseDateToMillis(String dateStr, String defaultDateStr) { - final Calendar tempCalendar = Calendar.getInstance(); - if (TextUtils.isEmpty(dateStr) || !parseDate(dateStr, tempCalendar)) { - parseDate(defaultDateStr, tempCalendar); - } - return tempCalendar.getTimeInMillis(); - } - @Override - public void setShownWeekCount(int count) { - // Deprecated. - } - - @Override - public int getShownWeekCount() { - // Deprecated. - return 0; - } - - @Override - public void setSelectedWeekBackgroundColor(int color) { - // TODO: Should use a ColorStateList. Deprecate? - } - - @Override - public int getSelectedWeekBackgroundColor() { - return 0; - } - - @Override - public void setFocusedMonthDateColor(int color) { - // TODO: Should use a ColorStateList. Deprecate? - } - - @Override - public int getFocusedMonthDateColor() { - return 0; - } - - @Override - public void setUnfocusedMonthDateColor(int color) { - // TODO: Should use a ColorStateList. Deprecate? - } - - @Override - public int getUnfocusedMonthDateColor() { - return 0; - } - - @Override - public void setWeekDayTextAppearance(int resourceId) { - + public void setWeekDayTextAppearance(@StyleRes int resId) { + mDayPickerView.setDayOfWeekTextAppearance(resId); } + @StyleRes @Override public int getWeekDayTextAppearance() { - return 0; + return mDayPickerView.getDayOfWeekTextAppearance(); } @Override - public void setDateTextAppearance(int resourceId) { - + public void setDateTextAppearance(@StyleRes int resId) { + mDayPickerView.setDayTextAppearance(resId); } + @StyleRes @Override public int getDateTextAppearance() { - return 0; - } - - @Override - public void setWeekNumberColor(int color) { - // Deprecated. - } - - @Override - public int getWeekNumberColor() { - // Deprecated. - return 0; - } - - @Override - public void setWeekSeparatorLineColor(int color) { - // Deprecated. - } - - @Override - public int getWeekSeparatorLineColor() { - // Deprecated. - return 0; - } - - @Override - public void setSelectedDateVerticalBar(int resourceId) { - // Deprecated. - } - - @Override - public void setSelectedDateVerticalBar(Drawable drawable) { - // Deprecated. - } - - @Override - public Drawable getSelectedDateVerticalBar() { - // Deprecated. - return null; + return mDayPickerView.getDayTextAppearance(); } @Override @@ -199,17 +80,6 @@ class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDele } @Override - public void setShowWeekNumber(boolean showWeekNumber) { - // Deprecated. - } - - @Override - public boolean getShowWeekNumber() { - // Deprecated. - return false; - } - - @Override public void setFirstDayOfWeek(int firstDayOfWeek) { mDayPickerView.setFirstDayOfWeek(firstDayOfWeek); } @@ -221,12 +91,12 @@ class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDele @Override public void setDate(long date) { - mDayPickerView.setDate(date, true, false); + mDayPickerView.setDate(date, true); } @Override public void setDate(long date, boolean animate, boolean center) { - mDayPickerView.setDate(date, animate, center); + mDayPickerView.setDate(date, animate); } @Override @@ -239,12 +109,6 @@ class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDele mOnDateChangeListener = listener; } - @Override - public void onConfigurationChanged(Configuration newConfig) { - // Nothing to do here, configuration changes are already propagated - // by ViewGroup. - } - private final DayPickerView.OnDaySelectedListener mOnDaySelectedListener = new DayPickerView.OnDaySelectedListener() { @Override diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 45998f7..5f5943f 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -98,7 +98,7 @@ public class DatePicker extends FrameLayout { private final DatePickerDelegate mDelegate; /** - * The callback used to indicate the user changes\d the date. + * The callback used to indicate the user changed the date. */ public interface OnDateChangedListener { @@ -348,9 +348,13 @@ public class DatePicker extends FrameLayout { } /** - * Gets whether the {@link CalendarView} is shown. + * Returns whether the {@link CalendarView} is shown. + * <p> + * <strong>Note:</strong> This method returns {@code false} when the + * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set + * to {@code calendar}. * - * @return True if the calendar view is shown. + * @return {@code true} if the calendar view is shown * @see #getCalendarView() */ public boolean getCalendarViewShown() { @@ -358,13 +362,13 @@ public class DatePicker extends FrameLayout { } /** - * Gets the {@link CalendarView}. + * Returns the {@link CalendarView} used by this picker. * <p> - * This method returns {@code null} when the + * <strong>Note:</strong> This method returns {@code null} when the * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set * to {@code calendar}. * - * @return The calendar view. + * @return the calendar view * @see #getCalendarViewShown() */ public CalendarView getCalendarView() { @@ -374,20 +378,25 @@ public class DatePicker extends FrameLayout { /** * Sets whether the {@link CalendarView} is shown. * <p> - * Calling this method has no effect when the + * <strong>Note:</strong> Calling this method has no effect when the * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set * to {@code calendar}. * - * @param shown True if the calendar view is to be shown. + * @param shown {@code true} to show the calendar view, {@code false} to + * hide it */ public void setCalendarViewShown(boolean shown) { mDelegate.setCalendarViewShown(shown); } /** - * Gets whether the spinners are shown. + * Returns whether the spinners are shown. + * <p> + * <strong>Note:</strong> his method returns {@code false} when the + * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set + * to {@code calendar}. * - * @return True if the spinners are shown. + * @return {@code true} if the spinners are shown */ public boolean getSpinnersShown() { return mDelegate.getSpinnersShown(); @@ -395,8 +404,13 @@ public class DatePicker extends FrameLayout { /** * Sets whether the spinners are shown. + * <p> + * Calling this method has no effect when the + * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set + * to {@code calendar}. * - * @param shown True if the spinners are to be shown. + * @param shown {@code true} to show the spinners, {@code false} to hide + * them */ public void setSpinnersShown(boolean shown) { mDelegate.setSpinnersShown(shown); @@ -489,15 +503,14 @@ public class DatePicker extends FrameLayout { mDelegator = delegator; mContext = context; - // initialization based on locale setCurrentLocale(Locale.getDefault()); } protected void setCurrentLocale(Locale locale) { - if (locale.equals(mCurrentLocale)) { - return; + if (!locale.equals(mCurrentLocale)) { + mCurrentLocale = locale; + onLocaleChanged(locale); } - mCurrentLocale = locale; } @Override @@ -510,6 +523,10 @@ public class DatePicker extends FrameLayout { mValidationCallback.onValidationChanged(valid); } } + + protected void onLocaleChanged(Locale locale) { + // Stub. + } } /** diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index 0e3ec7f..7b8a979 100755 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -26,90 +27,84 @@ import android.os.Parcelable; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; +import android.util.StateSet; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; +import android.widget.DayPickerView.OnDaySelectedListener; +import android.widget.YearPickerView.OnYearSelectedListener; import com.android.internal.R; -import com.android.internal.widget.AccessibleDateAnimator; import java.text.SimpleDateFormat; import java.util.Calendar; -import java.util.HashSet; import java.util.Locale; /** * A delegate for picking up a date (day / month / year). */ -class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate implements - View.OnClickListener, DatePickerController { +class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { + private static final int USE_LOCALE = 0; private static final int UNINITIALIZED = -1; - private static final int MONTH_AND_DAY_VIEW = 0; - private static final int YEAR_VIEW = 1; + private static final int VIEW_MONTH_DAY = 0; + private static final int VIEW_YEAR = 1; private static final int DEFAULT_START_YEAR = 1900; private static final int DEFAULT_END_YEAR = 2100; private static final int ANIMATION_DURATION = 300; - private static final int MONTH_INDEX = 0; - private static final int DAY_INDEX = 1; - private static final int YEAR_INDEX = 2; + public static final int[] ATTRS_TEXT_COLOR = new int[]{com.android.internal.R.attr.textColor}; - private SimpleDateFormat mYearFormat = new SimpleDateFormat("y", Locale.getDefault()); - private SimpleDateFormat mDayFormat = new SimpleDateFormat("d", Locale.getDefault()); + public static final int[] ATTRS_DISABLED_ALPHA = new int[]{ + com.android.internal.R.attr.disabledAlpha}; - private TextView mDayOfWeekView; + private SimpleDateFormat mYearFormat; + private SimpleDateFormat mMonthDayFormat; - /** Layout that contains the current month, day, and year. */ - private LinearLayout mMonthDayYearLayout; + // Top-level container. + private ViewGroup mContainer; - /** Clickable layout that contains the current day and year. */ - private LinearLayout mMonthAndDayLayout; + // Header views. + private TextView mHeaderYear; + private TextView mHeaderMonthDay; - private TextView mHeaderMonthTextView; - private TextView mHeaderDayOfMonthTextView; - private TextView mHeaderYearTextView; + // Picker views. + private ViewAnimator mAnimator; private DayPickerView mDayPickerView; private YearPickerView mYearPickerView; - private boolean mIsEnabled = true; - // Accessibility strings. - private String mDayPickerDescription; private String mSelectDay; - private String mYearPickerDescription; private String mSelectYear; - private AccessibleDateAnimator mAnimator; - private DatePicker.OnDateChangedListener mDateChangedListener; private int mCurrentView = UNINITIALIZED; - private Calendar mCurrentDate; - private Calendar mTempDate; - private Calendar mMinDate; - private Calendar mMaxDate; + private final Calendar mCurrentDate; + private final Calendar mTempDate; + private final Calendar mMinDate; + private final Calendar mMaxDate; private int mFirstDayOfWeek = USE_LOCALE; - private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>(); - public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(delegator, context); - final Locale locale = Locale.getDefault(); - mMinDate = getCalendarForLocale(mMinDate, locale); - mMaxDate = getCalendarForLocale(mMaxDate, locale); - mTempDate = getCalendarForLocale(mMaxDate, locale); - mCurrentDate = getCalendarForLocale(mCurrentDate, locale); + final Locale locale = mCurrentLocale; + mCurrentDate = Calendar.getInstance(locale); + mTempDate = Calendar.getInstance(locale); + mMinDate = Calendar.getInstance(locale); + mMaxDate = Calendar.getInstance(locale); mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1); mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31); @@ -120,71 +115,64 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); final int layoutResourceId = a.getResourceId( - R.styleable.DatePicker_internalLayout, R.layout.date_picker_holo); - final View mainView = inflater.inflate(layoutResourceId, null); - mDelegator.addView(mainView); + R.styleable.DatePicker_internalLayout, R.layout.date_picker_material); - mDayOfWeekView = (TextView) mainView.findViewById(R.id.date_picker_header); + // Set up and attach container. + mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator); - // Layout that contains the current date and day name header. - final LinearLayout dateLayout = (LinearLayout) mainView.findViewById( - R.id.day_picker_selector_layout); - mMonthDayYearLayout = (LinearLayout) mainView.findViewById( - R.id.date_picker_month_day_year_layout); - mMonthAndDayLayout = (LinearLayout) mainView.findViewById( - R.id.date_picker_month_and_day_layout); - mMonthAndDayLayout.setOnClickListener(this); - mHeaderMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_month); - mHeaderDayOfMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_day); - mHeaderYearTextView = (TextView) mainView.findViewById(R.id.date_picker_year); - mHeaderYearTextView.setOnClickListener(this); + // Set up header views. + final ViewGroup header = (ViewGroup) mContainer.findViewById(R.id.date_picker_header); + mHeaderYear = (TextView) header.findViewById(R.id.date_picker_header_year); + mHeaderYear.setOnClickListener(mOnHeaderClickListener); + mHeaderMonthDay = (TextView) header.findViewById(R.id.date_picker_header_date); + mHeaderMonthDay.setOnClickListener(mOnHeaderClickListener); - // Obtain default highlight color from the theme. - final int defaultHighlightColor = mHeaderYearTextView.getHighlightColor(); + // For the sake of backwards compatibility, attempt to extract the text + // color from the header month text appearance. If it's set, we'll let + // that override the "real" header text color. + ColorStateList headerTextColor = null; - // Use Theme attributes if possible - final int dayOfWeekTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_dayOfWeekTextAppearance, 0); - if (dayOfWeekTextAppearanceResId != 0) { - mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId); + @SuppressWarnings("deprecation") + final int monthHeaderTextAppearance = a.getResourceId( + R.styleable.DatePicker_headerMonthTextAppearance, 0); + if (monthHeaderTextAppearance != 0) { + final TypedArray textAppearance = mContext.obtainStyledAttributes(null, + ATTRS_TEXT_COLOR, 0, monthHeaderTextAppearance); + final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0); + headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor); + textAppearance.recycle(); } - mDayOfWeekView.setBackground(a.getDrawable(R.styleable.DatePicker_dayOfWeekBackground)); - - dateLayout.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground)); - - final int monthTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerMonthTextAppearance, 0); - if (monthTextAppearanceResId != 0) { - mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId); + if (headerTextColor == null) { + headerTextColor = a.getColorStateList(R.styleable.DatePicker_headerTextColor); } - final int dayOfMonthTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerDayOfMonthTextAppearance, 0); - if (dayOfMonthTextAppearanceResId != 0) { - mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId); + if (headerTextColor != null) { + mHeaderYear.setTextColor(headerTextColor); + mHeaderMonthDay.setTextColor(headerTextColor); } - final int headerYearTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerYearTextAppearance, 0); - if (headerYearTextAppearanceResId != 0) { - mHeaderYearTextView.setTextAppearance(context, headerYearTextAppearanceResId); + // Set up header background, if available. + if (a.hasValueOrEmpty(R.styleable.DatePicker_headerBackground)) { + header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground)); } - mDayPickerView = new DayPickerView(mContext); + // Set up picker container. + mAnimator = (ViewAnimator) mContainer.findViewById(R.id.animator); + + // Set up day picker view. + mDayPickerView = (DayPickerView) mAnimator.findViewById(R.id.date_picker_day_picker); mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek); mDayPickerView.setMinDate(mMinDate.getTimeInMillis()); mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis()); mDayPickerView.setDate(mCurrentDate.getTimeInMillis()); mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener); - mYearPickerView = new YearPickerView(mContext); - mYearPickerView.init(this); + // Set up year picker view. + mYearPickerView = (YearPickerView) mAnimator.findViewById(R.id.date_picker_year_picker); mYearPickerView.setRange(mMinDate, mMaxDate); - - final ColorStateList yearBackgroundColor = a.getColorStateList( - R.styleable.DatePicker_yearListSelectorColor); - mYearPickerView.setYearBackgroundColor(yearBackgroundColor); + mYearPickerView.setDate(mCurrentDate.getTimeInMillis()); + mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener); final int yearTextAppearanceResId = a.getResourceId( R.styleable.DatePicker_yearListItemTextAppearance, 0); @@ -192,170 +180,201 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i mYearPickerView.setYearTextAppearance(yearTextAppearanceResId); } - final ColorStateList calendarTextColor = a.getColorStateList( - R.styleable.DatePicker_calendarTextColor); - mDayPickerView.setCalendarTextColor(calendarTextColor); + final int yearActivatedTextAppearanceResId = a.getResourceId( + R.styleable.DatePicker_yearListItemActivatedTextAppearance, 0); + if (yearActivatedTextAppearanceResId != 0) { + mYearPickerView.setYearActivatedTextAppearance(yearActivatedTextAppearanceResId); + } - final ColorStateList calendarDayBackgroundColor = a.getColorStateList( - R.styleable.DatePicker_calendarDayBackgroundColor); - mDayPickerView.setCalendarDayBackgroundColor(calendarDayBackgroundColor); + a.recycle(); - mDayPickerDescription = res.getString(R.string.day_picker_description); + // Set up content descriptions. mSelectDay = res.getString(R.string.select_day); - mYearPickerDescription = res.getString(R.string.year_picker_description); mSelectYear = res.getString(R.string.select_year); - mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator); - mAnimator.addView(mDayPickerView); - mAnimator.addView(mYearPickerView); - mAnimator.setDateMillis(mCurrentDate.getTimeInMillis()); + final Animation inAnim = new AlphaAnimation(0, 1); + inAnim.setDuration(ANIMATION_DURATION); + mAnimator.setInAnimation(inAnim); - final Animation animation = new AlphaAnimation(0.0f, 1.0f); - animation.setDuration(ANIMATION_DURATION); - mAnimator.setInAnimation(animation); + final Animation outAnim = new AlphaAnimation(1, 0); + outAnim.setDuration(ANIMATION_DURATION); + mAnimator.setOutAnimation(outAnim); - final Animation animation2 = new AlphaAnimation(1.0f, 0.0f); - animation2.setDuration(ANIMATION_DURATION); - mAnimator.setOutAnimation(animation2); + // Initialize for current locale. This also initializes the date, so no + // need to call onDateChanged. + onLocaleChanged(mCurrentLocale); - updateDisplay(false); - setCurrentView(MONTH_AND_DAY_VIEW); + setCurrentView(VIEW_MONTH_DAY); } /** - * Gets a calendar for locale bootstrapped with the value of a given calendar. + * The legacy text color might have been poorly defined. Ensures that it + * has an appropriate activated state, using the selected state if one + * exists or modifying the default text color otherwise. * - * @param oldCalendar The old calendar. - * @param locale The locale. + * @param color a legacy text color, or {@code null} + * @return a color state list with an appropriate activated state, or + * {@code null} if a valid activated state could not be generated */ - private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { - if (oldCalendar == null) { - return Calendar.getInstance(locale); + @Nullable + private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) { + if (color == null || color.hasState(R.attr.state_activated)) { + return color; + } + + final int activatedColor; + final int defaultColor; + if (color.hasState(R.attr.state_selected)) { + activatedColor = color.getColorForState(StateSet.get( + StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0); + defaultColor = color.getColorForState(StateSet.get( + StateSet.VIEW_STATE_ENABLED), 0); } else { - final long currentTimeMillis = oldCalendar.getTimeInMillis(); - Calendar newCalendar = Calendar.getInstance(locale); - newCalendar.setTimeInMillis(currentTimeMillis); - return newCalendar; + activatedColor = color.getDefaultColor(); + + // Generate a non-activated color using the disabled alpha. + final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA); + final float disabledAlpha = ta.getFloat(0, 0.30f); + defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha); + } + + if (activatedColor == 0 || defaultColor == 0) { + // We somehow failed to obtain the colors. + return null; } + + final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}}; + final int[] colors = new int[] { activatedColor, defaultColor }; + return new ColorStateList(stateSet, colors); + } + + private int multiplyAlphaComponent(int color, float alphaMod) { + final int srcRgb = color & 0xFFFFFF; + final int srcAlpha = (color >> 24) & 0xFF; + final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f); + return srcRgb | (dstAlpha << 24); } /** - * Compute the array representing the order of Month / Day / Year views in their layout. - * Will be used for I18N purpose as the order of them depends on the Locale. + * Listener called when the user selects a day in the day picker view. + */ + private final OnDaySelectedListener mOnDaySelectedListener = new OnDaySelectedListener() { + @Override + public void onDaySelected(DayPickerView view, Calendar day) { + mCurrentDate.setTimeInMillis(day.getTimeInMillis()); + onDateChanged(true, true); + } + }; + + /** + * Listener called when the user selects a year in the year picker view. */ - private int[] getMonthDayYearIndexes(String pattern) { - int[] result = new int[3]; + private final OnYearSelectedListener mOnYearSelectedListener = new OnYearSelectedListener() { + @Override + public void onYearChanged(YearPickerView view, int year) { + // If the newly selected month / year does not contain the + // currently selected day number, change the selected day number + // to the last day of the selected month or year. + // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30 + // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013 + final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); + final int month = mCurrentDate.get(Calendar.MONTH); + final int daysInMonth = getDaysInMonth(month, year); + if (day > daysInMonth) { + mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth); + } - final String filteredPattern = pattern.replaceAll("'.*?'", ""); + mCurrentDate.set(Calendar.YEAR, year); + onDateChanged(true, true); - final int dayIndex = filteredPattern.indexOf('d'); - final int monthMIndex = filteredPattern.indexOf("M"); - final int monthIndex = (monthMIndex != -1) ? monthMIndex : filteredPattern.indexOf("L"); - final int yearIndex = filteredPattern.indexOf("y"); + // Automatically switch to day picker. + setCurrentView(VIEW_MONTH_DAY); + } + }; - if (yearIndex < monthIndex) { - result[YEAR_INDEX] = 0; + /** + * Listener called when the user clicks on a header item. + */ + private final OnClickListener mOnHeaderClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + tryVibrate(); - if (monthIndex < dayIndex) { - result[MONTH_INDEX] = 1; - result[DAY_INDEX] = 2; - } else { - result[MONTH_INDEX] = 2; - result[DAY_INDEX] = 1; - } - } else { - result[YEAR_INDEX] = 2; - - if (monthIndex < dayIndex) { - result[MONTH_INDEX] = 0; - result[DAY_INDEX] = 1; - } else { - result[MONTH_INDEX] = 1; - result[DAY_INDEX] = 0; + switch (v.getId()) { + case R.id.date_picker_header_year: + setCurrentView(VIEW_YEAR); + break; + case R.id.date_picker_header_date: + setCurrentView(VIEW_MONTH_DAY); + break; } } - return result; - } + }; - private void updateDisplay(boolean announce) { - if (mDayOfWeekView != null) { - mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, - Locale.getDefault())); + @Override + protected void onLocaleChanged(Locale locale) { + final TextView headerYear = mHeaderYear; + if (headerYear == null) { + // Abort, we haven't initialized yet. This method will get called + // again later after everything has been set up. + return; } - // Compute indices of Month, Day and Year views - final String bestDateTimePattern = - DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd"); - final int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern); + // Update the date formatter. + final String datePattern = DateFormat.getBestDateTimePattern(locale, "EMMMd"); + mMonthDayFormat = new SimpleDateFormat(datePattern, locale); + mYearFormat = new SimpleDateFormat("y", locale); - // Position the Year and MonthAndDay views within the header. - mMonthDayYearLayout.removeAllViews(); - if (viewIndices[YEAR_INDEX] == 0) { - mMonthDayYearLayout.addView(mHeaderYearTextView); - mMonthDayYearLayout.addView(mMonthAndDayLayout); - } else { - mMonthDayYearLayout.addView(mMonthAndDayLayout); - mMonthDayYearLayout.addView(mHeaderYearTextView); - } + // Update the header text. + onCurrentDateChanged(false); + } - // Position Day and Month views within the MonthAndDay view. - mMonthAndDayLayout.removeAllViews(); - if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) { - mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView); - mMonthAndDayLayout.addView(mHeaderMonthTextView); - } else { - mMonthAndDayLayout.addView(mHeaderMonthTextView); - mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView); + private void onCurrentDateChanged(boolean announce) { + if (mHeaderYear == null) { + // Abort, we haven't initialized yet. This method will get called + // again later after everything has been set up. + return; } - mHeaderMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT, - Locale.getDefault()).toUpperCase(Locale.getDefault())); - mHeaderDayOfMonthTextView.setText(mDayFormat.format(mCurrentDate.getTime())); - mHeaderYearTextView.setText(mYearFormat.format(mCurrentDate.getTime())); + final String year = mYearFormat.format(mCurrentDate.getTime()); + mHeaderYear.setText(year); - // Accessibility. - long millis = mCurrentDate.getTimeInMillis(); - mAnimator.setDateMillis(millis); - int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR; - String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags); - mMonthAndDayLayout.setContentDescription(monthAndDayText); + final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime()); + mHeaderMonthDay.setText(monthDay); + // TODO: This should use live regions. if (announce) { - flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; - String fullDateText = DateUtils.formatDateTime(mContext, millis, flags); + final long millis = mCurrentDate.getTimeInMillis(); + final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; + final String fullDateText = DateUtils.formatDateTime(mContext, millis, flags); mAnimator.announceForAccessibility(fullDateText); } } private void setCurrentView(final int viewIndex) { - long millis = mCurrentDate.getTimeInMillis(); - switch (viewIndex) { - case MONTH_AND_DAY_VIEW: - mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); + case VIEW_MONTH_DAY: + mDayPickerView.setDate(mCurrentDate.getTimeInMillis()); + if (mCurrentView != viewIndex) { - mMonthAndDayLayout.setSelected(true); - mHeaderYearTextView.setSelected(false); - mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); + mHeaderMonthDay.setActivated(true); + mHeaderYear.setActivated(false); + mAnimator.setDisplayedChild(VIEW_MONTH_DAY); mCurrentView = viewIndex; } - final int flags = DateUtils.FORMAT_SHOW_DATE; - final String dayString = DateUtils.formatDateTime(mContext, millis, flags); - mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString); mAnimator.announceForAccessibility(mSelectDay); break; - case YEAR_VIEW: - mYearPickerView.onDateChanged(); + case VIEW_YEAR: + mYearPickerView.setDate(mCurrentDate.getTimeInMillis()); + if (mCurrentView != viewIndex) { - mMonthAndDayLayout.setSelected(false); - mHeaderYearTextView.setSelected(true); - mAnimator.setDisplayedChild(YEAR_VIEW); + mHeaderMonthDay.setActivated(false); + mHeaderYear.setActivated(true); + mAnimator.setDisplayedChild(VIEW_YEAR); mCurrentView = viewIndex; } - final CharSequence yearString = mYearFormat.format(millis); - mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString); mAnimator.announceForAccessibility(mSelectYear); break; } @@ -383,20 +402,18 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i } private void onDateChanged(boolean fromUser, boolean callbackToClient) { + final int year = mCurrentDate.get(Calendar.YEAR); + if (callbackToClient && mDateChangedListener != null) { - final int year = mCurrentDate.get(Calendar.YEAR); final int monthOfYear = mCurrentDate.get(Calendar.MONTH); final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH); mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth); } - for (OnDateChangedListener listener : mListeners) { - listener.onDateChanged(); - } - - mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); + mDayPickerView.setDate(mCurrentDate.getTimeInMillis()); + mYearPickerView.setYear(year); - updateDisplay(fromUser); + onCurrentDateChanged(fromUser); if (fromUser) { tryVibrate(); @@ -477,15 +494,12 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i @Override public void setEnabled(boolean enabled) { - mMonthAndDayLayout.setEnabled(enabled); - mHeaderYearTextView.setEnabled(enabled); - mAnimator.setEnabled(enabled); - mIsEnabled = enabled; + mContainer.setEnabled(false); } @Override public boolean isEnabled() { - return mIsEnabled; + return mContainer.isEnabled(); } @Override @@ -516,8 +530,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i @Override public void onConfigurationChanged(Configuration newConfig) { - mYearFormat = new SimpleDateFormat("y", newConfig.locale); - mDayFormat = new SimpleDateFormat("d", newConfig.locale); + setCurrentLocale(newConfig.locale); } @Override @@ -529,9 +542,9 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i int listPosition = -1; int listPositionOffset = -1; - if (mCurrentView == MONTH_AND_DAY_VIEW) { + if (mCurrentView == VIEW_MONTH_DAY) { listPosition = mDayPickerView.getMostVisiblePosition(); - } else if (mCurrentView == YEAR_VIEW) { + } else if (mCurrentView == VIEW_YEAR) { listPosition = mYearPickerView.getFirstVisiblePosition(); listPositionOffset = mYearPickerView.getFirstPositionOffset(); } @@ -544,20 +557,22 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; + // TODO: Move instance state into DayPickerView, YearPickerView. mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); mCurrentView = ss.getCurrentView(); mMinDate.setTimeInMillis(ss.getMinDate()); mMaxDate.setTimeInMillis(ss.getMaxDate()); - updateDisplay(false); + onCurrentDateChanged(false); setCurrentView(mCurrentView); final int listPosition = ss.getListPosition(); if (listPosition != -1) { - if (mCurrentView == MONTH_AND_DAY_VIEW) { - mDayPickerView.postSetSelection(listPosition); - } else if (mCurrentView == YEAR_VIEW) { - mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset()); + if (mCurrentView == VIEW_MONTH_DAY) { + mDayPickerView.setCurrentItem(listPosition); + } else if (mCurrentView == VIEW_YEAR) { + final int listPositionOffset = ss.getListPositionOffset(); + mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset); } } } @@ -577,28 +592,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i return DatePicker.class.getName(); } - @Override - public void onYearSelected(int year) { - adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year); - mCurrentDate.set(Calendar.YEAR, year); - onDateChanged(true, true); - - // Auto-advance to month and day view. - setCurrentView(MONTH_AND_DAY_VIEW); - } - - // If the newly selected month / year does not contain the currently selected day number, - // change the selected day number to the last day of the selected month or year. - // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30 - // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013 - private void adjustDayInMonthIfNeeded(int month, int year) { - int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); - int daysInMonth = getDaysInMonth(month, year); - if (day > daysInMonth) { - mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth); - } - } - public static int getDaysInMonth(int month, int year) { switch (month) { case Calendar.JANUARY: @@ -621,43 +614,10 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i } } - @Override - public void registerOnDateChangedListener(OnDateChangedListener listener) { - mListeners.add(listener); - } - - @Override - public Calendar getSelectedDay() { - return mCurrentDate; - } - - @Override - public void tryVibrate() { + private void tryVibrate() { mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE); } - @Override - public void onClick(View v) { - tryVibrate(); - if (v.getId() == R.id.date_picker_year) { - setCurrentView(YEAR_VIEW); - } else if (v.getId() == R.id.date_picker_month_and_day_layout) { - setCurrentView(MONTH_AND_DAY_VIEW); - } - } - - /** - * Listener called when the user selects a day in the day picker view. - */ - private final DayPickerView.OnDaySelectedListener - mOnDaySelectedListener = new DayPickerView.OnDaySelectedListener() { - @Override - public void onDaySelected(DayPickerView view, Calendar day) { - mCurrentDate.setTimeInMillis(day.getTimeInMillis()); - onDateChanged(true, true); - } - }; - /** * Class for managing state storing/restoring. */ diff --git a/core/java/android/widget/DayPickerAdapter.java b/core/java/android/widget/DayPickerAdapter.java new file mode 100644 index 0000000..4f9f09e --- /dev/null +++ b/core/java/android/widget/DayPickerAdapter.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import com.android.internal.widget.PagerAdapter; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SimpleMonthView.OnDayClickListener; + +import java.util.Calendar; + +/** + * An adapter for a list of {@link android.widget.SimpleMonthView} items. + */ +class DayPickerAdapter extends PagerAdapter { + private static final int MONTHS_IN_YEAR = 12; + + private final Calendar mMinDate = Calendar.getInstance(); + private final Calendar mMaxDate = Calendar.getInstance(); + + private final SparseArray<SimpleMonthView> mItems = new SparseArray<>(); + + private Calendar mSelectedDay = Calendar.getInstance(); + + private int mMonthTextAppearance; + private int mDayOfWeekTextAppearance; + private int mDayTextAppearance; + + private ColorStateList mCalendarTextColor; + private ColorStateList mDaySelectorColor; + private ColorStateList mDayHighlightColor; + + private OnDaySelectedListener mOnDaySelectedListener; + + private int mFirstDayOfWeek; + + public DayPickerAdapter(Context context) { + final TypedArray ta = context.obtainStyledAttributes(new int[] { + com.android.internal.R.attr.colorControlHighlight}); + mDayHighlightColor = ta.getColorStateList(0); + ta.recycle(); + } + + public void setRange(Calendar min, Calendar max) { + mMinDate.setTimeInMillis(min.getTimeInMillis()); + mMaxDate.setTimeInMillis(max.getTimeInMillis()); + + // Positions are now invalid, clear everything and start over. + notifyDataSetChanged(); + } + + /** + * Sets the first day of the week. + * + * @param weekStart which day the week should start on, valid values are + * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} + */ + public void setFirstDayOfWeek(int weekStart) { + mFirstDayOfWeek = weekStart; + + // Update displayed views. + final int count = mItems.size(); + for (int i = 0; i < count; i++) { + final SimpleMonthView monthView = mItems.valueAt(i); + monthView.setFirstDayOfWeek(weekStart); + } + } + + public int getFirstDayOfWeek() { + return mFirstDayOfWeek; + } + + /** + * Sets the selected day. + * + * @param day the selected day + */ + public void setSelectedDay(Calendar day) { + final int oldPosition = getPositionForDay(mSelectedDay); + final int newPosition = getPositionForDay(day); + + // Clear the old position if necessary. + if (oldPosition != newPosition) { + final SimpleMonthView oldMonthView = mItems.get(oldPosition, null); + if (oldMonthView != null) { + oldMonthView.setSelectedDay(-1); + } + } + + // Set the new position. + final SimpleMonthView newMonthView = mItems.get(newPosition, null); + if (newMonthView != null) { + final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH); + newMonthView.setSelectedDay(dayOfMonth); + } + + mSelectedDay = day; + } + + /** + * Sets the listener to call when the user selects a day. + * + * @param listener The listener to call. + */ + public void setOnDaySelectedListener(OnDaySelectedListener listener) { + mOnDaySelectedListener = listener; + } + + void setCalendarTextColor(ColorStateList calendarTextColor) { + mCalendarTextColor = calendarTextColor; + } + + void setDaySelectorColor(ColorStateList selectorColor) { + mDaySelectorColor = selectorColor; + } + + void setMonthTextAppearance(int resId) { + mMonthTextAppearance = resId; + } + + void setDayOfWeekTextAppearance(int resId) { + mDayOfWeekTextAppearance = resId; + } + + int getDayOfWeekTextAppearance() { + return mDayOfWeekTextAppearance; + } + + void setDayTextAppearance(int resId) { + mDayTextAppearance = resId; + } + + int getDayTextAppearance() { + return mDayTextAppearance; + } + + @Override + public int getCount() { + final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR); + final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH); + return diffMonth + MONTHS_IN_YEAR * diffYear + 1; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + private int getMonthForPosition(int position) { + return position % MONTHS_IN_YEAR + mMinDate.get(Calendar.MONTH); + } + + private int getYearForPosition(int position) { + return position / MONTHS_IN_YEAR + mMinDate.get(Calendar.YEAR); + } + + private int getPositionForDay(Calendar day) { + final int yearOffset = (day.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR)); + final int monthOffset = (day.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH)); + return yearOffset * MONTHS_IN_YEAR + monthOffset; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + final SimpleMonthView v = new SimpleMonthView(container.getContext()); + v.setOnDayClickListener(mOnDayClickListener); + v.setMonthTextAppearance(mMonthTextAppearance); + v.setDayOfWeekTextAppearance(mDayOfWeekTextAppearance); + v.setDayTextAppearance(mDayTextAppearance); + + if (mDaySelectorColor != null) { + v.setDaySelectorColor(mDaySelectorColor); + } + + if (mDayHighlightColor != null) { + v.setDayHighlightColor(mDayHighlightColor); + } + + if (mCalendarTextColor != null) { + v.setMonthTextColor(mCalendarTextColor); + v.setDayOfWeekTextColor(mCalendarTextColor); + v.setDayTextColor(mCalendarTextColor); + } + + final int month = getMonthForPosition(position); + final int year = getYearForPosition(position); + + final int selectedDay; + if (mSelectedDay.get(Calendar.MONTH) == month) { + selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH); + } else { + selectedDay = -1; + } + + final int enabledDayRangeStart; + if (mMinDate.get(Calendar.MONTH) == month && mMinDate.get(Calendar.YEAR) == year) { + enabledDayRangeStart = mMinDate.get(Calendar.DAY_OF_MONTH); + } else { + enabledDayRangeStart = 1; + } + + final int enabledDayRangeEnd; + if (mMaxDate.get(Calendar.MONTH) == month && mMaxDate.get(Calendar.YEAR) == year) { + enabledDayRangeEnd = mMaxDate.get(Calendar.DAY_OF_MONTH); + } else { + enabledDayRangeEnd = 31; + } + + v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek, + enabledDayRangeStart, enabledDayRangeEnd); + + mItems.put(position, v); + + container.addView(v); + + return v; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView(mItems.get(position)); + + mItems.remove(position); + } + + @Override + public int getItemPosition(Object object) { + final int index = mItems.indexOfValue((SimpleMonthView) object); + if (index < 0) { + return mItems.keyAt(index); + } + return -1; + } + + @Override + public CharSequence getPageTitle(int position) { + final SimpleMonthView v = mItems.get(position); + if (v != null) { + return v.getTitle(); + } + return null; + } + + private boolean isCalendarInRange(Calendar value) { + return value.compareTo(mMinDate) >= 0 && value.compareTo(mMaxDate) <= 0; + } + + private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() { + @Override + public void onDayClick(SimpleMonthView view, Calendar day) { + if (day != null && isCalendarInRange(day)) { + setSelectedDay(day); + + if (mOnDaySelectedListener != null) { + mOnDaySelectedListener.onDaySelected(DayPickerAdapter.this, day); + } + } + } + }; + + public interface OnDaySelectedListener { + public void onDaySelected(DayPickerAdapter view, Calendar day); + } +} diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index 65af45d..a7ae926 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -16,87 +16,177 @@ package android.widget; +import com.android.internal.widget.ViewPager; +import com.android.internal.R; + import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.os.Bundle; -import android.util.Log; +import android.content.res.TypedArray; +import android.util.AttributeSet; import android.util.MathUtils; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; +import libcore.icu.LocaleData; + /** * This displays a list of months in a calendar format with selectable days. */ -class DayPickerView extends ListView implements AbsListView.OnScrollListener { - private static final String TAG = "DayPickerView"; +class DayPickerView extends ViewPager { + private static final int DEFAULT_START_YEAR = 1900; + private static final int DEFAULT_END_YEAR = 2100; - // How long the GoTo fling animation should last - private static final int GOTO_SCROLL_DURATION = 250; + private final Calendar mSelectedDay = Calendar.getInstance(); + private final Calendar mMinDate = Calendar.getInstance(); + private final Calendar mMaxDate = Calendar.getInstance(); - // How long to wait after receiving an onScrollStateChanged notification before acting on it - private static final int SCROLL_CHANGE_DELAY = 40; + private final DayPickerAdapter mAdapter; - // so that the top line will be under the separator - private static final int LIST_TOP_OFFSET = -1; + /** Temporary calendar used for date calculations. */ + private Calendar mTempCalendar; - private final SimpleMonthAdapter mAdapter = new SimpleMonthAdapter(getContext()); + private OnDaySelectedListener mOnDaySelectedListener; - private final ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(this); + public DayPickerView(Context context) { + this(context, null); + } - private SimpleDateFormat mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault()); + public DayPickerView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.calendarViewStyle); + } - // highlighted time - private Calendar mSelectedDay = Calendar.getInstance(); - private Calendar mTempDay = Calendar.getInstance(); - private Calendar mMinDate = Calendar.getInstance(); - private Calendar mMaxDate = Calendar.getInstance(); + public DayPickerView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } - private Calendar mTempCalendar; + public DayPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - private OnDaySelectedListener mOnDaySelectedListener; + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.CalendarView, defStyleAttr, defStyleRes); - // which month should be displayed/highlighted [0-11] - private int mCurrentMonthDisplayed; - // used for tracking what state listview is in - private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; - // used for tracking what state listview is in - private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; + final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, + LocaleData.get(Locale.getDefault()).firstDayOfWeek); - private boolean mPerformingScroll; + final String minDate = a.getString(R.styleable.CalendarView_minDate); + final String maxDate = a.getString(R.styleable.CalendarView_maxDate); - public DayPickerView(Context context) { - super(context); + final int monthTextAppearanceResId = a.getResourceId( + R.styleable.CalendarView_monthTextAppearance, + R.style.TextAppearance_Material_Widget_Calendar_Month); + final int dayOfWeekTextAppearanceResId = a.getResourceId( + R.styleable.CalendarView_weekDayTextAppearance, + R.style.TextAppearance_Material_Widget_Calendar_DayOfWeek); + final int dayTextAppearanceResId = a.getResourceId( + R.styleable.CalendarView_dateTextAppearance, + R.style.TextAppearance_Material_Widget_Calendar_Day); + + final ColorStateList daySelectorColor = a.getColorStateList( + R.styleable.CalendarView_daySelectorColor); + + a.recycle(); + + // Set up adapter. + mAdapter = new DayPickerAdapter(context); + mAdapter.setMonthTextAppearance(monthTextAppearanceResId); + mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId); + mAdapter.setDayTextAppearance(dayTextAppearanceResId); + mAdapter.setDaySelectorColor(daySelectorColor); setAdapter(mAdapter); - setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - setDrawSelectorOnTop(false); - setUpListView(); - goTo(mSelectedDay.getTimeInMillis(), false, false, true); + // Set up min and max dates. + final Calendar tempDate = Calendar.getInstance(); + if (!CalendarView.parseDate(minDate, tempDate)) { + tempDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1); + } + final long minDateMillis = tempDate.getTimeInMillis(); + + if (!CalendarView.parseDate(maxDate, tempDate)) { + tempDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31); + } + final long maxDateMillis = tempDate.getTimeInMillis(); + + if (maxDateMillis < minDateMillis) { + throw new IllegalArgumentException("maxDate must be >= minDate"); + } + + final long setDateMillis = MathUtils.constrain( + System.currentTimeMillis(), minDateMillis, maxDateMillis); - mAdapter.setOnDaySelectedListener(mProxyOnDaySelectedListener); + setFirstDayOfWeek(firstDayOfWeek); + setMinDate(minDateMillis); + setMaxDate(maxDateMillis); + setDate(setDateMillis, false); + + // Proxy selection callbacks to our own listener. + mAdapter.setOnDaySelectedListener(new DayPickerAdapter.OnDaySelectedListener() { + @Override + public void onDaySelected(DayPickerAdapter adapter, Calendar day) { + if (mOnDaySelectedListener != null) { + mOnDaySelectedListener.onDaySelected(DayPickerView.this, day); + } + } + }); + } + + public void setDayOfWeekTextAppearance(int resId) { + mAdapter.setDayOfWeekTextAppearance(resId); + } + + public int getDayOfWeekTextAppearance() { + return mAdapter.getDayOfWeekTextAppearance(); + } + + public void setDayTextAppearance(int resId) { + mAdapter.setDayTextAppearance(resId); + } + + public int getDayTextAppearance() { + return mAdapter.getDayTextAppearance(); } /** * Sets the currently selected date to the specified timestamp. Jumps * immediately to the new date. To animate to the new date, use - * {@link #setDate(long, boolean, boolean)}. + * {@link #setDate(long, boolean)}. * - * @param timeInMillis + * @param timeInMillis the target day in milliseconds */ public void setDate(long timeInMillis) { - setDate(timeInMillis, false, true); + setDate(timeInMillis, false); } - public void setDate(long timeInMillis, boolean animate, boolean forceScroll) { - goTo(timeInMillis, animate, true, forceScroll); + /** + * Sets the currently selected date to the specified timestamp. Jumps + * immediately to the new date, optionally animating the transition. + * + * @param timeInMillis the target day in milliseconds + * @param animate whether to smooth scroll to the new position + */ + public void setDate(long timeInMillis, boolean animate) { + setDate(timeInMillis, animate, true); + } + + /** + * Moves to the month containing the specified day, optionally setting the + * day as selected. + * + * @param timeInMillis the target day in milliseconds + * @param animate whether to smooth scroll to the new position + * @param setSelected whether to set the specified day as selected + */ + private void setDate(long timeInMillis, boolean animate, boolean setSelected) { + // Set the selected day + if (setSelected) { + mSelectedDay.setTimeInMillis(timeInMillis); + } + + final int position = getPositionFromDay(timeInMillis); + if (position != getCurrentItem()) { + setCurrentItem(position, animate); + } } public long getDate() { @@ -137,7 +227,7 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { // Changing the min/max date changes the selection position since we // don't really have stable IDs. Jumps immediately to the new position. - goTo(mSelectedDay.getTimeInMillis(), false, false, true); + setDate(mSelectedDay.getTimeInMillis(), false, false); } /** @@ -149,30 +239,9 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { mOnDaySelectedListener = listener; } - /* - * Sets all the required fields for the list view. Override this method to - * set a different list view behavior. - */ - private void setUpListView() { - // Transparent background on scroll - setCacheColorHint(0); - // No dividers - setDivider(null); - // Items are clickable - setItemsCanFocus(true); - // The thumb gets in the way, so disable it - setFastScrollEnabled(false); - setVerticalScrollBarEnabled(false); - setOnScrollListener(this); - setFadingEdgeLength(0); - // Make the scrolling behavior nicer - setFriction(ViewConfiguration.getScrollFriction()); - } - private int getDiffMonths(Calendar start, Calendar end) { final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR); - final int diffMonths = end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears; - return diffMonths; + return end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears; } private int getPositionFromDay(long timeInMillis) { @@ -190,366 +259,13 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { } /** - * This moves to the specified time in the view. If the time is not already - * in range it will move the list so that the first of the month containing - * the time is at the top of the view. If the new time is already in view - * the list will not be scrolled unless forceScroll is true. This time may - * optionally be highlighted as selected as well. - * - * @param day The day to move to - * @param animate Whether to scroll to the given time or just redraw at the - * new location - * @param setSelected Whether to set the given time as selected - * @param forceScroll Whether to recenter even if the time is already - * visible - * @return Whether or not the view animated to the new location - */ - private boolean goTo(long day, boolean animate, boolean setSelected, boolean forceScroll) { - - // Set the selected day - if (setSelected) { - mSelectedDay.setTimeInMillis(day); - } - - mTempDay.setTimeInMillis(day); - final int position = getPositionFromDay(day); - - View child; - int i = 0; - int top = 0; - // Find a child that's completely in the view - do { - child = getChildAt(i++); - if (child == null) { - break; - } - top = child.getTop(); - } while (top < 0); - - // Compute the first and last position visible - int selectedPosition; - if (child != null) { - selectedPosition = getPositionForView(child); - } else { - selectedPosition = 0; - } - - if (setSelected) { - mAdapter.setSelectedDay(mSelectedDay); - } - - // Check if the selected day is now outside of our visible range - // and if so scroll to the month that contains it - if (position != selectedPosition || forceScroll) { - setMonthDisplayed(mTempDay); - mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; - if (animate) { - smoothScrollToPositionFromTop( - position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION); - return true; - } else { - postSetSelection(position); - } - } else if (setSelected) { - setMonthDisplayed(mSelectedDay); - } - return false; - } - - public void postSetSelection(final int position) { - clearFocus(); - post(new Runnable() { - - @Override - public void run() { - setSelection(position); - } - }); - onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE); - } - - /** - * Updates the title and selected month if the view has moved to a new - * month. - */ - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - SimpleMonthView child = (SimpleMonthView) view.getChildAt(0); - if (child == null) { - return; - } - - mPreviousScrollState = mCurrentScrollState; - } - - /** - * Sets the month displayed at the top of this view based on time. Override - * to add custom events when the title is changed. - */ - protected void setMonthDisplayed(Calendar date) { - if (mCurrentMonthDisplayed != date.get(Calendar.MONTH)) { - mCurrentMonthDisplayed = date.get(Calendar.MONTH); - invalidateViews(); - } - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - // use a post to prevent re-entering onScrollStateChanged before it - // exits - mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); - } - - void setCalendarTextColor(ColorStateList colors) { - mAdapter.setCalendarTextColor(colors); - } - - void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) { - mAdapter.setCalendarDayBackgroundColor(dayBackgroundColor); - } - - void setCalendarTextAppearance(int resId) { - mAdapter.setCalendarTextAppearance(resId); - } - - protected class ScrollStateRunnable implements Runnable { - private int mNewState; - private View mParent; - - ScrollStateRunnable(View view) { - mParent = view; - } - - /** - * Sets up the runnable with a short delay in case the scroll state - * immediately changes again. - * - * @param view The list view that changed state - * @param scrollState The new state it changed to - */ - public void doScrollStateChange(AbsListView view, int scrollState) { - mParent.removeCallbacks(this); - mNewState = scrollState; - mParent.postDelayed(this, SCROLL_CHANGE_DELAY); - } - - @Override - public void run() { - mCurrentScrollState = mNewState; - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, - "new scroll state: " + mNewState + " old state: " + mPreviousScrollState); - } - // Fix the position after a scroll or a fling ends - if (mNewState == OnScrollListener.SCROLL_STATE_IDLE - && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE - && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { - mPreviousScrollState = mNewState; - int i = 0; - View child = getChildAt(i); - while (child != null && child.getBottom() <= 0) { - child = getChildAt(++i); - } - if (child == null) { - // The view is no longer visible, just return - return; - } - int firstPosition = getFirstVisiblePosition(); - int lastPosition = getLastVisiblePosition(); - boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1; - final int top = child.getTop(); - final int bottom = child.getBottom(); - final int midpoint = getHeight() / 2; - if (scroll && top < LIST_TOP_OFFSET) { - if (bottom > midpoint) { - smoothScrollBy(top, GOTO_SCROLL_DURATION); - } else { - smoothScrollBy(bottom, GOTO_SCROLL_DURATION); - } - } - } else { - mPreviousScrollState = mNewState; - } - } - } - - /** * Gets the position of the view that is most prominently displayed within the list view. */ public int getMostVisiblePosition() { - final int firstPosition = getFirstVisiblePosition(); - final int height = getHeight(); - - int maxDisplayedHeight = 0; - int mostVisibleIndex = 0; - int i=0; - int bottom = 0; - while (bottom < height) { - View child = getChildAt(i); - if (child == null) { - break; - } - bottom = child.getBottom(); - int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop()); - if (displayedHeight > maxDisplayedHeight) { - mostVisibleIndex = i; - maxDisplayedHeight = displayedHeight; - } - i++; - } - return firstPosition + mostVisibleIndex; - } - - /** - * Attempts to return the date that has accessibility focus. - * - * @return The date that has accessibility focus, or {@code null} if no date - * has focus. - */ - private Calendar findAccessibilityFocus() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child instanceof SimpleMonthView) { - final Calendar focus = ((SimpleMonthView) child).getAccessibilityFocus(); - if (focus != null) { - return focus; - } - } - } - - return null; - } - - /** - * Attempts to restore accessibility focus to a given date. No-op if - * {@code day} is {@code null}. - * - * @param day The date that should receive accessibility focus - * @return {@code true} if focus was restored - */ - private boolean restoreAccessibilityFocus(Calendar day) { - if (day == null) { - return false; - } - - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child instanceof SimpleMonthView) { - if (((SimpleMonthView) child).restoreAccessibilityFocus(day)) { - return true; - } - } - } - - return false; - } - - @Override - protected void layoutChildren() { - final Calendar focusedDay = findAccessibilityFocus(); - super.layoutChildren(); - if (mPerformingScroll) { - mPerformingScroll = false; - } else { - restoreAccessibilityFocus(focusedDay); - } - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault()); - } - - /** @hide */ - @Override - public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { - super.onInitializeAccessibilityEventInternal(event); - event.setItemCount(-1); - } - - private String getMonthAndYearString(Calendar day) { - final StringBuilder sbuf = new StringBuilder(); - sbuf.append(day.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())); - sbuf.append(" "); - sbuf.append(mYearFormat.format(day.getTime())); - return sbuf.toString(); - } - - /** - * Necessary for accessibility, to ensure we support "scrolling" forward and backward - * in the month list. - * - * @hide - */ - @Override - public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfoInternal(info); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); - } - - /** - * When scroll forward/backward events are received, announce the newly scrolled-to month. - * - * @hide - */ - @Override - public boolean performAccessibilityActionInternal(int action, Bundle arguments) { - if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && - action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { - return super.performAccessibilityActionInternal(action, arguments); - } - - // Figure out what month is showing. - final int firstVisiblePosition = getFirstVisiblePosition(); - final int month = firstVisiblePosition % 12; - final int year = firstVisiblePosition / 12 + mMinDate.get(Calendar.YEAR); - final Calendar day = Calendar.getInstance(); - day.set(year, month, 1); - - // Scroll either forward or backward one month. - if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) { - day.add(Calendar.MONTH, 1); - if (day.get(Calendar.MONTH) == 12) { - day.set(Calendar.MONTH, 0); - day.add(Calendar.YEAR, 1); - } - } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { - View firstVisibleView = getChildAt(0); - // If the view is fully visible, jump one month back. Otherwise, we'll just jump - // to the first day of first visible month. - if (firstVisibleView != null && firstVisibleView.getTop() >= -1) { - // There's an off-by-one somewhere, so the top of the first visible item will - // actually be -1 when it's at the exact top. - day.add(Calendar.MONTH, -1); - if (day.get(Calendar.MONTH) == -1) { - day.set(Calendar.MONTH, 11); - day.add(Calendar.YEAR, -1); - } - } - } - - // Go to that month. - announceForAccessibility(getMonthAndYearString(day)); - goTo(day.getTimeInMillis(), true, false, true); - mPerformingScroll = true; - return true; + return getCurrentItem(); } public interface OnDaySelectedListener { public void onDaySelected(DayPickerView view, Calendar day); } - - private final SimpleMonthAdapter.OnDaySelectedListener - mProxyOnDaySelectedListener = new SimpleMonthAdapter.OnDaySelectedListener() { - @Override - public void onDaySelected(SimpleMonthAdapter adapter, Calendar day) { - if (mOnDaySelectedListener != null) { - mOnDaySelectedListener.onDaySelected(DayPickerView.this, day); - } - } - }; } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index d93b212..6e24837 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -77,14 +77,14 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.ActionMode; import android.view.ActionMode.Callback; -import android.view.RenderNode; +import android.view.DisplayListCanvas; import android.view.DragEvent; import android.view.Gravity; -import android.view.HardwareCanvas; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.RenderNode; import android.view.View; import android.view.View.DragShadowBuilder; import android.view.View.OnClickListener; @@ -236,12 +236,17 @@ public class Editor { } ParcelableParcel saveInstanceState() { - // For now there is only undo state. - return (ParcelableParcel) mUndoManager.saveInstanceState(); + ParcelableParcel state = new ParcelableParcel(getClass().getClassLoader()); + Parcel parcel = state.getParcel(); + mUndoManager.saveInstanceState(parcel); + mUndoInputFilter.saveInstanceState(parcel); + return state; } void restoreInstanceState(ParcelableParcel state) { - mUndoManager.restoreInstanceState(state); + Parcel parcel = state.getParcel(); + mUndoManager.restoreInstanceState(parcel, state.getClassLoader()); + mUndoInputFilter.restoreInstanceState(parcel); // Re-associate this object as the owner of undo state. mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this); } @@ -309,7 +314,7 @@ public class Editor { mTextView.setHasTransientState(false); // We had an active selection from before, start the selection mode. - startSelectionActionMode(); + startSelectionActionModeWithSelection(); } getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true); @@ -976,14 +981,15 @@ public class Editor { } public boolean performLongClick(boolean handled) { - // Long press in empty space moves cursor and shows the Paste affordance if available. + // Long press in empty space moves cursor and starts the selection action mode. if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) && mInsertionControllerEnabled) { final int offset = mTextView.getOffsetForPosition(mLastDownPositionX, mLastDownPositionY); stopSelectionActionMode(); Selection.setSelection((Spannable) mTextView.getText(), offset); - getInsertionController().showWithActionPopup(); + getInsertionController().show(); + startSelectionActionModeWithoutSelection(); handled = true; } @@ -995,18 +1001,19 @@ public class Editor { CharSequence selectedText = mTextView.getTransformedText(start, end); ClipData data = ClipData.newPlainText(null, selectedText); DragLocalState localState = new DragLocalState(mTextView, start, end); - mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0); + mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, + View.DRAG_FLAG_GLOBAL); stopSelectionActionMode(); } else { stopSelectionActionMode(); - startSelectionActionMode(); + startSelectionActionModeWithSelection(); } handled = true; } // Start a new selection if (!handled) { - handled = startSelectionActionMode(); + handled = startSelectionActionModeWithSelection(); } return handled; @@ -1508,18 +1515,18 @@ public class Editor { // Rebuild display list if it is invalid if (blockDisplayListIsInvalid) { - final HardwareCanvas hardwareCanvas = blockDisplayList.start( + final DisplayListCanvas displayListCanvas = blockDisplayList.start( right - left, bottom - top); try { // drawText is always relative to TextView's origin, this translation // brings this range of text back to the top left corner of the viewport - hardwareCanvas.translate(-left, -top); - layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine); + displayListCanvas.translate(-left, -top); + layout.drawText(displayListCanvas, blockBeginLine, blockEndLine); mTextDisplayLists[blockIndex].isDirty = false; // No need to untranslate, previous context is popped after // drawDisplayList } finally { - blockDisplayList.end(hardwareCanvas); + blockDisplayList.end(displayListCanvas); // Same as drawDisplayList below, handled by our TextView's parent blockDisplayList.setClipToBounds(false); } @@ -1530,7 +1537,7 @@ public class Editor { blockDisplayList.setLeftTopRightBottom(left, top, right, bottom); } - ((HardwareCanvas) canvas).drawRenderNode(blockDisplayList, + ((DisplayListCanvas) canvas).drawRenderNode(blockDisplayList, 0 /* no child clipping, our TextView parent enforces it */); endOfPreviousBlock = blockEndLine; @@ -1657,7 +1664,21 @@ public class Editor { /** * @return true if the selection mode was actually started. */ - boolean startSelectionActionMode() { + private boolean startSelectionActionModeWithoutSelection() { + if (mSelectionActionMode != null) { + // Selection action mode is already started + // TODO: revisit invocations to minimize this case. + return false; + } + ActionMode.Callback actionModeCallback = new SelectionActionModeCallback(); + mSelectionActionMode = mTextView.startActionMode(actionModeCallback); + return mSelectionActionMode != null; + } + + /** + * @return true if the selection mode was actually started. + */ + boolean startSelectionActionModeWithSelection() { if (mSelectionActionMode != null) { // Selection action mode is already started return false; @@ -3254,7 +3275,7 @@ public class Editor { if (isTopLeftVisible || isBottomRightVisible) { characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; } - if (!isTopLeftVisible || !isTopLeftVisible) { + if (!isTopLeftVisible || !isBottomRightVisible) { characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; } if (isRtl) { @@ -3320,18 +3341,14 @@ public class Editor { private float mIdealVerticalOffset; // Parent's (TextView) previous position in window private int mLastParentX, mLastParentY; - // Transient action popup window for Paste and Replace actions - protected ActionPopupWindow mActionPopupWindow; // Previous text character offset private int mPreviousOffset = -1; // Previous text character offset private boolean mPositionHasChanged = true; - // Used to delay the appearance of the action popup window - private Runnable mActionPopupShower; // Minimum touch target size for handles private int mMinSize; // Indicates the line of text that the handle is on. - protected int mLine = -1; + protected int mPrevLine = -1; public HandleView(Drawable drawableLtr, Drawable drawableRtl) { super(mTextView.getContext()); @@ -3429,8 +3446,6 @@ public class Editor { // Make sure the offset is always considered new, even when focusing at same position mPreviousOffset = -1; positionAtCursorOffset(getCurrentCursorOffset(), false); - - hideActionPopupWindow(); } protected void dismiss() { @@ -3445,31 +3460,6 @@ public class Editor { getPositionListener().removeSubscriber(this); } - void showActionPopupWindow(int delay) { - if (mActionPopupWindow == null) { - mActionPopupWindow = new ActionPopupWindow(); - } - if (mActionPopupShower == null) { - mActionPopupShower = new Runnable() { - public void run() { - mActionPopupWindow.show(); - } - }; - } else { - mTextView.removeCallbacks(mActionPopupShower); - } - mTextView.postDelayed(mActionPopupShower, delay); - } - - protected void hideActionPopupWindow() { - if (mActionPopupShower != null) { - mTextView.removeCallbacks(mActionPopupShower); - } - if (mActionPopupWindow != null) { - mActionPopupWindow.hide(); - } - } - public boolean isShowing() { return mContainer.isShowing(); } @@ -3509,7 +3499,7 @@ public class Editor { addPositionToTouchUpFilter(offset); } final int line = layout.getLineForOffset(offset); - mLine = line; + mPrevLine = line; mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX - getHorizontalOffset() + getCursorOffset()); @@ -3670,20 +3660,16 @@ public class Editor { return mIsDragging; } - void onHandleMoved() { - hideActionPopupWindow(); - } + void onHandleMoved() {} - public void onDetached() { - hideActionPopupWindow(); - } + public void onDetached() {} } private class InsertionHandleView extends HandleView { private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000; private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds - // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow + // Used to detect taps on the insertion handle, which will affect the selection action mode private float mDownPositionX, mDownPositionY; private Runnable mHider; @@ -3698,17 +3684,12 @@ public class Editor { final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - TextView.LAST_CUT_OR_COPY_TIME; if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) { - showActionPopupWindow(0); + startSelectionActionModeWithoutSelection(); } hideAfterDelay(); } - public void showWithActionPopup() { - show(); - showActionPopupWindow(0); - } - private void hideAfterDelay() { if (mHider == null) { mHider = new Runnable() { @@ -3770,11 +3751,11 @@ public class Editor { final int touchSlop = viewConfiguration.getScaledTouchSlop(); if (distanceSquared < touchSlop * touchSlop) { - if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) { - // Tapping on the handle dismisses the displayed action popup - mActionPopupWindow.hide(); + // Tapping on the handle toggles the selection action mode. + if (mSelectionActionMode != null) { + mSelectionActionMode.finish(); } else { - showWithActionPopup(); + startSelectionActionModeWithoutSelection(); } } } @@ -3858,19 +3839,22 @@ public class Editor { public void updatePosition(float x, float y) { final int trueOffset = mTextView.getOffsetForPosition(x, y); final int currLine = mTextView.getLineAtCoordinate(y); - int offset = trueOffset; - boolean positionCursor = false; + // Don't select white space on different lines. + if (isWhitespaceLine(mPrevLine, currLine, trueOffset)) return; + + boolean positionCursor = false; + int offset = trueOffset; int end = getWordEnd(offset, true); int start = getWordStart(offset); if (offset < mPrevOffset) { // User is increasing the selection. - if (!mInWord || currLine < mLine) { + if (!mInWord || currLine < mPrevLine) { // We're not in a word, or we're on a different line so we'll expand by // word. First ensure the user has at least entered the next word. int offsetToWord = Math.min((end - start) / 2, 2); - if (offset <= end - offsetToWord || currLine < mLine) { + if (offset <= end - offsetToWord || currLine < mPrevLine) { offset = start; } else { offset = mPrevOffset; @@ -3882,7 +3866,7 @@ public class Editor { positionCursor = true; } else if (offset - mTouchWordOffset > mPrevOffset) { // User is shrinking the selection. - if (currLine > mLine) { + if (currLine > mPrevLine) { // We're on a different line, so we'll snap to word boundaries. offset = end; } @@ -3897,7 +3881,7 @@ public class Editor { final int selectionEnd = mTextView.getSelectionEnd(); if (offset >= selectionEnd) { // We can't cross the handles so let's just constrain the Y value. - int alteredOffset = mTextView.getOffsetAtCoordinate(mLine, x); + int alteredOffset = mTextView.getOffsetAtCoordinate(mPrevLine, x); if (alteredOffset >= selectionEnd) { // Can't pass the other drag handle. offset = Math.max(0, selectionEnd - 1); @@ -3909,10 +3893,6 @@ public class Editor { } } - public ActionPopupWindow getActionPopupWindow() { - return mActionPopupWindow; - } - @Override public boolean onTouchEvent(MotionEvent event) { boolean superResult = super.onTouchEvent(event); @@ -3962,6 +3942,10 @@ public class Editor { public void updatePosition(float x, float y) { final int trueOffset = mTextView.getOffsetForPosition(x, y); final int currLine = mTextView.getLineAtCoordinate(y); + + // Don't select white space on different lines. + if (isWhitespaceLine(mPrevLine, currLine, trueOffset)) return; + int offset = trueOffset; boolean positionCursor = false; @@ -3970,11 +3954,11 @@ public class Editor { if (offset > mPrevOffset) { // User is increasing the selection. - if (!mInWord || currLine > mLine) { + if (!mInWord || currLine > mPrevLine) { // We're not in a word, or we're on a different line so we'll expand by // word. First ensure the user has at least entered the next word. int midPoint = Math.min((end - start) / 2, 2); - if (offset >= start + midPoint || currLine > mLine) { + if (offset >= start + midPoint || currLine > mPrevLine) { offset = end; } else { offset = mPrevOffset; @@ -3986,7 +3970,7 @@ public class Editor { positionCursor = true; } else if (offset + mTouchWordOffset < mPrevOffset) { // User is shrinking the selection. - if (currLine > mLine) { + if (currLine > mPrevLine) { // We're on a different line, so we'll snap to word boundaries. offset = getWordStart(offset); } @@ -4000,7 +3984,7 @@ public class Editor { final int selectionStart = mTextView.getSelectionStart(); if (offset <= selectionStart) { // We can't cross the handles so let's just constrain the Y value. - int alteredOffset = mTextView.getOffsetAtCoordinate(mLine, x); + int alteredOffset = mTextView.getOffsetAtCoordinate(mPrevLine, x); int length = mTextView.getText().length(); if (alteredOffset <= selectionStart) { // Can't pass the other drag handle. @@ -4013,10 +3997,6 @@ public class Editor { } } - public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) { - mActionPopupWindow = actionPopupWindow; - } - @Override public boolean onTouchEvent(MotionEvent event) { boolean superResult = super.onTouchEvent(event); @@ -4029,6 +4009,36 @@ public class Editor { } /** + * Checks whether selection is happening on a different line than previous and + * if that line only contains whitespace up to the touch location. + * + * @param prevLine The previous line the selection was on. + * @param currLine The current line being selected. + * @param offset The offset in the text where the touch occurred. + * @return Whether or not it was just a white space line being selected. + */ + private boolean isWhitespaceLine(int prevLine, int currLine, int offset) { + if (prevLine == currLine) { + // Same line; don't care. + return false; + } + CharSequence text = mTextView.getText(); + if (offset == text.length()) { + // No character at the last position. + return false; + } + int lineEndOffset = mTextView.getLayout().getLineEnd(currLine); + for (int cp, i = offset; i < lineEndOffset; i += Character.charCount(cp)) { + cp = Character.codePointAt(text, i); + if (!Character.isSpaceChar(cp) && !Character.isWhitespace(cp)) { + // There are non white space chars on the line. + return false; + } + } + return true; + } + + /** * A CursorController instance can be used to control a cursor in the text. */ private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener { @@ -4059,10 +4069,6 @@ public class Editor { getHandle().show(); } - public void showWithActionPopup() { - getHandle().showWithActionPopup(); - } - public void hide() { if (mHandle != null) { mHandle.hide(); @@ -4112,6 +4118,8 @@ public class Editor { private int mStartOffset = -1; // Indicates whether the user is selecting text and using the drag accelerator. private boolean mDragAcceleratorActive; + // Indicates the line of text the drag accelerator is on. + private int mPrevLine = -1; SelectionModifierCursorController() { resetTouchOffsets(); @@ -4149,11 +4157,6 @@ public class Editor { mStartHandle.show(); mEndHandle.show(); - // Make sure both left and right handles share the same ActionPopupWindow (so that - // moving any of the handles hides the action popup). - mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION); - mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow()); - hideInsertionPointCursorController(); } @@ -4203,12 +4206,14 @@ public class Editor { boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop; if (stayedInArea && isPositionOnText(x, y)) { - startSelectionActionMode(); + startSelectionActionModeWithSelection(); mDiscardNextActionUp = true; } } } + // New selection, reset line. + mPrevLine = mTextView.getLineAtCoordinate(y); mDownPositionX = x; mDownPositionY = y; mGestureStayedInTapRegion = true; @@ -4265,6 +4270,13 @@ public class Editor { if (my > fingerOffset) my -= fingerOffset; offset = mTextView.getOffsetForPosition(mx, my); + int currLine = mTextView.getLineAtCoordinate(my); + + // Don't select white space on different lines. + if (isWhitespaceLine(mPrevLine, currLine, offset)) return; + + mPrevLine = currLine; + // Perform the check for closeness at edge of view, if we're very close // don't adjust the offset to be in front of the finger - otherwise the // user can't select words at the edge. @@ -4576,20 +4588,30 @@ public class Editor { // Whether the current filter pass is directly caused by an end-user text edit. private boolean mIsUserEdit; - // Whether this is the first pass through the filter for a given end-user text edit. - private boolean mFirstFilterPass; + // Whether the text field is handling an IME composition. Must be parceled in case the user + // rotates the screen during composition. + private boolean mHasComposition; public UndoInputFilter(Editor editor) { mEditor = editor; } + public void saveInstanceState(Parcel parcel) { + parcel.writeInt(mIsUserEdit ? 1 : 0); + parcel.writeInt(mHasComposition ? 1 : 0); + } + + public void restoreInstanceState(Parcel parcel) { + mIsUserEdit = parcel.readInt() != 0; + mHasComposition = parcel.readInt() != 0; + } + /** * Signals that a user-triggered edit is starting. */ public void beginBatchEdit() { if (DEBUG_UNDO) Log.d(TAG, "beginBatchEdit"); mIsUserEdit = true; - mFirstFilterPass = true; } public void endBatchEdit() { @@ -4610,17 +4632,67 @@ public class Editor { return null; } + // Check for and handle IME composition edits. + if (handleCompositionEdit(source, start, end, dstart)) { + return null; + } + + // Handle keyboard edits. + handleKeyboardEdit(source, start, end, dest, dstart, dend); + return null; + } + + /** + * Returns true iff the edit was handled, either because it should be ignored or because + * this function created an undo operation for it. + */ + private boolean handleCompositionEdit(CharSequence source, int start, int end, int dstart) { + // Ignore edits while the user is composing. + if (isComposition(source)) { + mHasComposition = true; + return true; + } + final boolean hadComposition = mHasComposition; + mHasComposition = false; + + // Check for the transition out of the composing state. + if (hadComposition) { + // If there was no text the user canceled composition. Ignore the edit. + if (start == end) { + return true; + } + + // Otherwise the user inserted the composition. + String newText = TextUtils.substring(source, start, end); + EditOperation edit = new EditOperation(mEditor, "", dstart, newText); + recordEdit(edit, false /* forceMerge */); + return true; + } + + // This was neither a composition event nor a transition out of composing. + return false; + } + + private void handleKeyboardEdit(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { // An application may install a TextWatcher to provide additional modifications after // the initial input filters run (e.g. a credit card formatter that adds spaces to a // string). This results in multiple filter() calls for what the user considers to be // a single operation. Always undo the whole set of changes in one step. - final boolean forceMerge = !mFirstFilterPass; - mFirstFilterPass = false; + final boolean forceMerge = isInTextWatcher(); // Build a new operation with all the information from this edit. - EditOperation edit = new EditOperation(mEditor, forceMerge, - source, start, end, dest, dstart, dend); + String newText = TextUtils.substring(source, start, end); + String oldText = TextUtils.substring(dest, dstart, dend); + EditOperation edit = new EditOperation(mEditor, oldText, dstart, newText); + recordEdit(edit, forceMerge); + } + /** + * Fetches the last undo operation and checks to see if a new edit should be merged into it. + * If forceMerge is true then the new edit is always merged. + */ + private void recordEdit(EditOperation edit, boolean forceMerge) { // Fetch the last edit operation and attempt to merge in the new edit. final UndoManager um = mEditor.mUndoManager; um.beginUpdate("Edit text"); @@ -4630,6 +4702,11 @@ public class Editor { // Add this as the first edit. if (DEBUG_UNDO) Log.d(TAG, "filter: adding first op " + edit); um.addOperation(edit, UndoManager.MERGE_MODE_NONE); + } else if (forceMerge) { + // Forced merges take priority because they could be the result of a non-user-edit + // change and this case should not create a new undo operation. + if (DEBUG_UNDO) Log.d(TAG, "filter: force merge " + edit); + lastEdit.forceMergeWith(edit); } else if (!mIsUserEdit) { // An application directly modified the Editable outside of a text edit. Treat this // as a new change and don't attempt to merge. @@ -4646,7 +4723,6 @@ public class Editor { um.addOperation(edit, UndoManager.MERGE_MODE_NONE); } um.endUpdate(); - return null; // Text not changed. } private boolean canUndoEdit(CharSequence source, int start, int end, @@ -4678,6 +4754,23 @@ public class Editor { return true; } + + private boolean isComposition(CharSequence source) { + if (!(source instanceof Spannable)) { + return false; + } + // This is a composition edit if the source has a non-zero-length composing span. + Spannable text = (Spannable) source; + int composeBegin = EditableInputConnection.getComposingSpanStart(text); + int composeEnd = EditableInputConnection.getComposingSpanEnd(text); + return composeBegin < composeEnd; + } + + private boolean isInTextWatcher() { + CharSequence text = mEditor.mTextView.getText(); + return (text instanceof SpannableStringBuilder) + && ((SpannableStringBuilder) text).getTextWatcherDepth() > 0; + } } /** @@ -4689,7 +4782,6 @@ public class Editor { private static final int TYPE_REPLACE = 2; private int mType; - private boolean mForceMerge; private String mOldText; private int mOldTextStart; private String mNewText; @@ -4699,17 +4791,13 @@ public class Editor { private int mNewCursorPos; /** - * Constructs an edit operation from a text input operation that replaces the range - * (dstart, dend) of dest with (start, end) of source. See {@link InputFilter#filter}. - * If forceMerge is true then always forcibly merge this operation with any previous one. + * Constructs an edit operation from a text input operation on editor that replaces the + * oldText starting at dstart with newText. */ - public EditOperation(Editor editor, boolean forceMerge, - CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { + public EditOperation(Editor editor, String oldText, int dstart, String newText) { super(editor.mUndoOwner); - mForceMerge = forceMerge; - - mOldText = dest.subSequence(dstart, dend).toString(); - mNewText = source.subSequence(start, end).toString(); + mOldText = oldText; + mNewText = newText; // Determine the type of the edit and store where it occurred. Avoid storing // irrevelant data (e.g. mNewTextStart for a delete) because that makes the @@ -4728,13 +4816,12 @@ public class Editor { // Store cursor data. mOldCursorPos = editor.mTextView.getSelectionStart(); - mNewCursorPos = dstart + (end - start); + mNewCursorPos = dstart + mNewText.length(); } public EditOperation(Parcel src, ClassLoader loader) { super(src, loader); mType = src.readInt(); - mForceMerge = src.readInt() != 0; mOldText = src.readString(); mOldTextStart = src.readInt(); mNewText = src.readString(); @@ -4746,7 +4833,6 @@ public class Editor { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); - dest.writeInt(mForceMerge ? 1 : 0); dest.writeString(mOldText); dest.writeInt(mOldTextStart); dest.writeString(mNewText); @@ -4798,10 +4884,6 @@ public class Editor { Log.d(TAG, "mergeWith old " + this); Log.d(TAG, "mergeWith new " + edit); } - if (edit.mForceMerge) { - forceMergeWith(edit); - return true; - } switch (mType) { case TYPE_INSERT: return mergeInsertWith(edit); @@ -4859,7 +4941,7 @@ public class Editor { * Forcibly creates a single merged edit operation by simulating the entire text * contents being replaced. */ - private void forceMergeWith(EditOperation edit) { + public void forceMergeWith(EditOperation edit) { if (DEBUG_UNDO) Log.d(TAG, "forceMerge"); Editor editor = getOwnerData(); diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 133e102..21213ac 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -753,13 +753,13 @@ class FastScroller { final View track = mTrackImage; final View thumb = mThumbImage; final Rect container = mContainerRect; - final int containerWidth = container.width(); - final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(containerWidth, MeasureSpec.AT_MOST); + final int maxWidth = container.width(); + final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST); final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); track.measure(widthMeasureSpec, heightMeasureSpec); final int trackWidth = track.getMeasuredWidth(); - final int thumbHalfHeight = thumb == null ? 0 : thumb.getHeight() / 2; + final int thumbHalfHeight = thumb.getHeight() / 2; final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2; final int right = left + trackWidth; final int top = container.top + thumbHalfHeight; diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index f6d198b..0602944 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -53,8 +53,6 @@ import com.android.internal.R; * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()} * is set to true. * - * @attr ref android.R.styleable#FrameLayout_foreground - * @attr ref android.R.styleable#FrameLayout_foregroundGravity * @attr ref android.R.styleable#FrameLayout_measureAllChildren */ @RemoteView @@ -64,13 +62,6 @@ public class FrameLayout extends ViewGroup { @ViewDebug.ExportedProperty(category = "measurement") boolean mMeasureAllChildren = false; - @ViewDebug.ExportedProperty(category = "drawing") - private Drawable mForeground; - private ColorStateList mForegroundTintList = null; - private PorterDuff.Mode mForegroundTintMode = null; - private boolean mHasForegroundTint = false; - private boolean mHasForegroundTintMode = false; - @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingLeft = 0; @@ -85,15 +76,6 @@ public class FrameLayout extends ViewGroup { private final Rect mSelfBounds = new Rect(); private final Rect mOverlayBounds = new Rect(); - - @ViewDebug.ExportedProperty(category = "drawing") - private int mForegroundGravity = Gravity.FILL; - - /** {@hide} */ - @ViewDebug.ExportedProperty(category = "drawing") - protected boolean mForegroundInPadding = true; - - boolean mForegroundBoundsChanged = false; private final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1); @@ -115,48 +97,12 @@ public class FrameLayout extends ViewGroup { final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.FrameLayout, defStyleAttr, defStyleRes); - - mForegroundGravity = a.getInt( - com.android.internal.R.styleable.FrameLayout_foregroundGravity, mForegroundGravity); - - final Drawable d = a.getDrawable(com.android.internal.R.styleable.FrameLayout_foreground); - if (d != null) { - setForeground(d); - } if (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) { setMeasureAllChildren(true); } - if (a.hasValue(R.styleable.FrameLayout_foregroundTintMode)) { - mForegroundTintMode = Drawable.parseTintMode(a.getInt( - R.styleable.FrameLayout_foregroundTintMode, -1), mForegroundTintMode); - mHasForegroundTintMode = true; - } - - if (a.hasValue(R.styleable.FrameLayout_foregroundTint)) { - mForegroundTintList = a.getColorStateList(R.styleable.FrameLayout_foregroundTint); - mHasForegroundTint = true; - } - - mForegroundInPadding = a.getBoolean(R.styleable.FrameLayout_foregroundInsidePadding, true); - a.recycle(); - - applyForegroundTint(); - } - - /** - * Describes how the foreground is positioned. - * - * @return foreground gravity. - * - * @see #setForegroundGravity(int) - * - * @attr ref android.R.styleable#FrameLayout_foregroundGravity - */ - public int getForegroundGravity() { - return mForegroundGravity; } /** @@ -166,25 +112,18 @@ public class FrameLayout extends ViewGroup { * * @see #getForegroundGravity() * - * @attr ref android.R.styleable#FrameLayout_foregroundGravity + * @attr ref android.R.styleable#View_foregroundGravity */ @android.view.RemotableViewMethod public void setForegroundGravity(int foregroundGravity) { - if (mForegroundGravity != foregroundGravity) { - if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { - foregroundGravity |= Gravity.START; - } - - if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { - foregroundGravity |= Gravity.TOP; - } - - mForegroundGravity = foregroundGravity; - + if (getForegroundGravity() != foregroundGravity) { + super.setForegroundGravity(foregroundGravity); - if (mForegroundGravity == Gravity.FILL && mForeground != null) { + // calling get* again here because the set above may apply default constraints + final Drawable foreground = getForeground(); + if (getForegroundGravity() == Gravity.FILL && foreground != null) { Rect padding = new Rect(); - if (mForeground.getPadding(padding)) { + if (foreground.getPadding(padding)) { mForegroundPaddingLeft = padding.left; mForegroundPaddingTop = padding.top; mForegroundPaddingRight = padding.right; @@ -201,53 +140,6 @@ public class FrameLayout extends ViewGroup { } } - @Override - protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) { - super.onVisibilityChanged(changedView, visibility); - - final Drawable dr = mForeground; - if (dr != null) { - final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE; - if (visible != dr.isVisible()) { - dr.setVisible(visible, false); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || (who == mForeground); - } - - @Override - public void jumpDrawablesToCurrentState() { - super.jumpDrawablesToCurrentState(); - if (mForeground != null) mForeground.jumpToCurrentState(); - } - - /** - * {@inheritDoc} - */ - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - if (mForeground != null && mForeground.isStateful()) { - mForeground.setState(getDrawableState()); - } - } - - @Override - public void drawableHotspotChanged(float x, float y) { - super.drawableHotspotChanged(x, y); - - if (mForeground != null) { - mForeground.setHotspot(x, y); - } - } - /** * Returns a set of layout parameters with a width of * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}, @@ -258,161 +150,23 @@ public class FrameLayout extends ViewGroup { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } - /** - * Supply a Drawable that is to be rendered on top of all of the child - * views in the frame layout. Any padding in the Drawable will be taken - * into account by ensuring that the children are inset to be placed - * inside of the padding area. - * - * @param d The Drawable to be drawn on top of the children. - * - * @attr ref android.R.styleable#FrameLayout_foreground - */ - public void setForeground(Drawable d) { - if (mForeground != d) { - if (mForeground != null) { - mForeground.setCallback(null); - unscheduleDrawable(mForeground); - } - - mForeground = d; - mForegroundPaddingLeft = 0; - mForegroundPaddingTop = 0; - mForegroundPaddingRight = 0; - mForegroundPaddingBottom = 0; - - if (d != null) { - setWillNotDraw(false); - d.setCallback(this); - d.setLayoutDirection(getLayoutDirection()); - if (d.isStateful()) { - d.setState(getDrawableState()); - } - applyForegroundTint(); - if (mForegroundGravity == Gravity.FILL) { - Rect padding = new Rect(); - if (d.getPadding(padding)) { - mForegroundPaddingLeft = padding.left; - mForegroundPaddingTop = padding.top; - mForegroundPaddingRight = padding.right; - mForegroundPaddingBottom = padding.bottom; - } - } - } else { - setWillNotDraw(true); - } - requestLayout(); - invalidate(); - } - } - - /** - * Returns the drawable used as the foreground of this FrameLayout. The - * foreground drawable, if non-null, is always drawn on top of the children. - * - * @return A Drawable or null if no foreground was set. - */ - public Drawable getForeground() { - return mForeground; - } - - /** - * Applies a tint to the foreground drawable. Does not modify the current - * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. - * <p> - * Subsequent calls to {@link #setForeground(Drawable)} will automatically - * mutate the drawable and apply the specified tint and tint mode using - * {@link Drawable#setTintList(ColorStateList)}. - * - * @param tint the tint to apply, may be {@code null} to clear tint - * - * @attr ref android.R.styleable#FrameLayout_foregroundTint - * @see #getForegroundTintList() - * @see Drawable#setTintList(ColorStateList) - */ - public void setForegroundTintList(@Nullable ColorStateList tint) { - mForegroundTintList = tint; - mHasForegroundTint = true; - - applyForegroundTint(); - } - - /** - * @return the tint applied to the foreground drawable - * @attr ref android.R.styleable#FrameLayout_foregroundTint - * @see #setForegroundTintList(ColorStateList) - */ - @Nullable - public ColorStateList getForegroundTintList() { - return mForegroundTintList; - } - - /** - * Specifies the blending mode used to apply the tint specified by - * {@link #setForegroundTintList(ColorStateList)}} to the foreground drawable. - * The default mode is {@link PorterDuff.Mode#SRC_IN}. - * - * @param tintMode the blending mode used to apply the tint, may be - * {@code null} to clear tint - * @attr ref android.R.styleable#FrameLayout_foregroundTintMode - * @see #getForegroundTintMode() - * @see Drawable#setTintMode(PorterDuff.Mode) - */ - public void setForegroundTintMode(@Nullable PorterDuff.Mode tintMode) { - mForegroundTintMode = tintMode; - mHasForegroundTintMode = true; - - applyForegroundTint(); - } - - /** - * @return the blending mode used to apply the tint to the foreground - * drawable - * @attr ref android.R.styleable#FrameLayout_foregroundTintMode - * @see #setForegroundTintMode(PorterDuff.Mode) - */ - @Nullable - public PorterDuff.Mode getForegroundTintMode() { - return mForegroundTintMode; - } - - private void applyForegroundTint() { - if (mForeground != null && (mHasForegroundTint || mHasForegroundTintMode)) { - mForeground = mForeground.mutate(); - - if (mHasForegroundTint) { - mForeground.setTintList(mForegroundTintList); - } - - if (mHasForegroundTintMode) { - mForeground.setTintMode(mForegroundTintMode); - } - - // The drawable (or one of its children) may not have been - // stateful before applying the tint, so let's try again. - if (mForeground.isStateful()) { - mForeground.setState(getDrawableState()); - } - } - } - int getPaddingLeftWithForeground() { - return mForegroundInPadding ? Math.max(mPaddingLeft, mForegroundPaddingLeft) : + return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) : mPaddingLeft + mForegroundPaddingLeft; } int getPaddingRightWithForeground() { - return mForegroundInPadding ? Math.max(mPaddingRight, mForegroundPaddingRight) : + return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) : mPaddingRight + mForegroundPaddingRight; } private int getPaddingTopWithForeground() { - return mForegroundInPadding ? Math.max(mPaddingTop, mForegroundPaddingTop) : + return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) : mPaddingTop + mForegroundPaddingTop; } private int getPaddingBottomWithForeground() { - return mForegroundInPadding ? Math.max(mPaddingBottom, mForegroundPaddingBottom) : + return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) : mPaddingBottom + mForegroundPaddingBottom; } @@ -527,8 +281,6 @@ public class FrameLayout extends ViewGroup { final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); - mForegroundBoundsChanged = true; - for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { @@ -585,62 +337,6 @@ public class FrameLayout extends ViewGroup { } /** - * {@inheritDoc} - */ - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mForegroundBoundsChanged = true; - } - - /** - * {@inheritDoc} - */ - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - if (mForeground != null) { - final Drawable foreground = mForeground; - - if (mForegroundBoundsChanged) { - mForegroundBoundsChanged = false; - final Rect selfBounds = mSelfBounds; - final Rect overlayBounds = mOverlayBounds; - - final int w = mRight-mLeft; - final int h = mBottom-mTop; - - if (mForegroundInPadding) { - selfBounds.set(0, 0, w, h); - } else { - selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom); - } - - final int layoutDirection = getLayoutDirection(); - Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), - foreground.getIntrinsicHeight(), selfBounds, overlayBounds, - layoutDirection); - foreground.setBounds(overlayBounds); - } - - foreground.draw(canvas); - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean gatherTransparentRegion(Region region) { - boolean opaque = super.gatherTransparentRegion(region); - if (region != null && mForeground != null) { - applyDrawableToTransparentRegion(mForeground, region); - } - return opaque; - } - - /** * Sets whether to consider all children, or just those in * the VISIBLE or INVISIBLE state, when measuring. Defaults to false. * diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index af5a8bf..b187c1c 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -1209,13 +1209,13 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: - if (movePrevious()) { + if (moveDirection(-1)) { playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); return true; } break; case KeyEvent.KEYCODE_DPAD_RIGHT: - if (moveNext()) { + if (moveDirection(1)) { playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); return true; } @@ -1255,18 +1255,12 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList return super.onKeyUp(keyCode, event); } - boolean movePrevious() { - if (mItemCount > 0 && mSelectedPosition > 0) { - scrollToChild(mSelectedPosition - mFirstPosition - 1); - return true; - } else { - return false; - } - } + boolean moveDirection(int direction) { + direction = isLayoutRtl() ? -direction : direction; + int targetPosition = mSelectedPosition + direction; - boolean moveNext() { - if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) { - scrollToChild(mSelectedPosition - mFirstPosition + 1); + if (mItemCount > 0 && targetPosition >= 0 && targetPosition < mItemCount) { + scrollToChild(targetPosition - mFirstPosition); return true; } else { return false; diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index d85bbb9..310412f 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -567,13 +567,11 @@ public class ListPopupWindow { public void show() { int height = buildDropDown(); - int widthSpec = 0; - int heightSpec = 0; - boolean noInputMethod = isInputMethodNotNeeded(); mPopup.setAllowScrollingAnchorParent(!noInputMethod); if (mPopup.isShowing()) { + final int widthSpec; if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { // The call to PopupWindow's update method below can accept -1 for any // value you do not want to update. @@ -584,19 +582,19 @@ public class ListPopupWindow { widthSpec = mDropDownWidth; } + final int heightSpec; if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { // The call to PopupWindow's update method below can accept -1 for any // value you do not want to update. heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT; if (noInputMethod) { - mPopup.setWindowLayoutMode( - mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? - ViewGroup.LayoutParams.MATCH_PARENT : 0, 0); + mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? + ViewGroup.LayoutParams.MATCH_PARENT : 0); + mPopup.setHeight(0); } else { - mPopup.setWindowLayoutMode( - mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? - ViewGroup.LayoutParams.MATCH_PARENT : 0, - ViewGroup.LayoutParams.MATCH_PARENT); + mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? + ViewGroup.LayoutParams.MATCH_PARENT : 0); + mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT); } } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { heightSpec = height; @@ -604,32 +602,37 @@ public class ListPopupWindow { heightSpec = mDropDownHeight; } + mPopup.setWidth(widthSpec); + mPopup.setHeight(heightSpec); mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); mPopup.update(getAnchorView(), mDropDownHorizontalOffset, - mDropDownVerticalOffset, widthSpec, heightSpec); + mDropDownVerticalOffset, -1, -1); } else { + final int widthSpec; if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { widthSpec = ViewGroup.LayoutParams.MATCH_PARENT; } else { if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { - mPopup.setWidth(getAnchorView().getWidth()); + widthSpec = getAnchorView().getWidth(); } else { - mPopup.setWidth(mDropDownWidth); + widthSpec = mDropDownWidth; } } + final int heightSpec; if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { heightSpec = ViewGroup.LayoutParams.MATCH_PARENT; } else { if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { - mPopup.setHeight(height); + heightSpec = height; } else { - mPopup.setHeight(mDropDownHeight); + heightSpec = mDropDownHeight; } } - mPopup.setWindowLayoutMode(widthSpec, heightSpec); + mPopup.setWidth(widthSpec); + mPopup.setHeight(heightSpec); mPopup.setClipToScreenEnabled(true); // use outside touchable to dismiss drop down when touching outside of it, so diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index f676254..8792323 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -46,6 +46,7 @@ import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; import java.lang.ref.WeakReference; @@ -126,10 +127,10 @@ public class PopupWindow { private OnTouchListener mTouchInterceptor; private int mWidthMode; - private int mWidth; + private int mWidth = LayoutParams.WRAP_CONTENT; private int mLastWidth; private int mHeightMode; - private int mHeight; + private int mHeight = LayoutParams.WRAP_CONTENT; private int mLastHeight; private int mPopupWidth; @@ -907,17 +908,19 @@ public class PopupWindow { * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute * height. + * + * @deprecated Use {@link #setWidth(int)} and {@link #setHeight(int)}. */ + @Deprecated public void setWindowLayoutMode(int widthSpec, int heightSpec) { mWidthMode = widthSpec; mHeightMode = heightSpec; } /** - * <p>Return this popup's height MeasureSpec</p> + * Returns the popup's height MeasureSpec. * * @return the height MeasureSpec of the popup - * * @see #setHeight(int) */ public int getHeight() { @@ -925,13 +928,12 @@ public class PopupWindow { } /** - * <p>Change the popup's height MeasureSpec</p> - * - * <p>If the popup is showing, calling this method will take effect only - * the next time the popup is shown.</p> + * Sets the popup's height MeasureSpec. + * <p> + * If the popup is showing, calling this method will take effect the next + * time the popup is shown. * * @param height the height MeasureSpec of the popup - * * @see #getHeight() * @see #isShowing() */ @@ -940,10 +942,9 @@ public class PopupWindow { } /** - * <p>Return this popup's width MeasureSpec</p> + * Returns the popup's width MeasureSpec. * * @return the width MeasureSpec of the popup - * * @see #setWidth(int) */ public int getWidth() { @@ -951,13 +952,12 @@ public class PopupWindow { } /** - * <p>Change the popup's width MeasureSpec</p> - * - * <p>If the popup is showing, calling this method will take effect only - * the next time the popup is shown.</p> + * Sets the popup's width MeasureSpec. + * <p> + * If the popup is showing, calling this method will take effect the next + * time the popup is shown. * * @param width the width MeasureSpec of the popup - * * @see #getWidth() * @see #isShowing() */ @@ -1658,10 +1658,17 @@ public class PopupWindow { /** * Updates the state of the popup window, if it is currently being displayed, - * from the currently set state. This includes: - * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)}, - * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)}, - * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}. + * from the currently set state. + * <p> + * This includes: + * <ul> + * <li>{@link #setClippingEnabled(boolean)}</li> + * <li>{@link #setFocusable(boolean)}</li> + * <li>{@link #setIgnoreCheekPress()}</li> + * <li>{@link #setInputMethodMode(int)}</li> + * <li>{@link #setTouchable(boolean)}</li> + * <li>{@link #setAnimationStyle(int)}</li> + * </ul> */ public void update() { if (!isShowing() || mContentView == null) { @@ -1692,12 +1699,13 @@ public class PopupWindow { } /** - * <p>Updates the dimension of the popup window. Calling this function - * also updates the window with the current popup state as described - * for {@link #update()}.</p> + * Updates the dimension of the popup window. + * <p> + * Calling this function also updates the window with the current popup + * state as described for {@link #update()}. * - * @param width the new width - * @param height the new height + * @param width the new width, must be >= 0 or -1 to ignore + * @param height the new height, must be >= 0 or -1 to ignore */ public void update(int width, int height) { final WindowManager.LayoutParams p = @@ -1706,40 +1714,43 @@ public class PopupWindow { } /** - * <p>Updates the position and the dimension of the popup window. Width and - * height can be set to -1 to update location only. Calling this function - * also updates the window with the current popup state as - * described for {@link #update()}.</p> + * Updates the position and the dimension of the popup window. + * <p> + * Width and height can be set to -1 to update location only. Calling this + * function also updates the window with the current popup state as + * described for {@link #update()}. * * @param x the new x location * @param y the new y location - * @param width the new width, can be -1 to ignore - * @param height the new height, can be -1 to ignore + * @param width the new width, must be >= 0 or -1 to ignore + * @param height the new height, must be >= 0 or -1 to ignore */ public void update(int x, int y, int width, int height) { update(x, y, width, height, false); } /** - * <p>Updates the position and the dimension of the popup window. Width and - * height can be set to -1 to update location only. Calling this function - * also updates the window with the current popup state as - * described for {@link #update()}.</p> + * Updates the position and the dimension of the popup window. + * <p> + * Width and height can be set to -1 to update location only. Calling this + * function also updates the window with the current popup state as + * described for {@link #update()}. * * @param x the new x location * @param y the new y location - * @param width the new width, can be -1 to ignore - * @param height the new height, can be -1 to ignore - * @param force reposition the window even if the specified position - * already seems to correspond to the LayoutParams + * @param width the new width, must be >= 0 or -1 to ignore + * @param height the new height, must be >= 0 or -1 to ignore + * @param force {@code true} to reposition the window even if the specified + * position already seems to correspond to the LayoutParams, + * {@code false} to only reposition if needed */ public void update(int x, int y, int width, int height, boolean force) { - if (width != -1) { + if (width >= 0) { mLastWidth = width; setWidth(width); } - if (height != -1) { + if (height >= 0) { mLastHeight = height; setHeight(height); } @@ -1794,32 +1805,34 @@ public class PopupWindow { } /** - * <p>Updates the position and the dimension of the popup window. Calling this - * function also updates the window with the current popup state as described - * for {@link #update()}.</p> + * Updates the position and the dimension of the popup window. + * <p> + * Calling this function also updates the window with the current popup + * state as described for {@link #update()}. * * @param anchor the popup's anchor view - * @param width the new width, can be -1 to ignore - * @param height the new height, can be -1 to ignore + * @param width the new width, must be >= 0 or -1 to ignore + * @param height the new height, must be >= 0 or -1 to ignore */ public void update(View anchor, int width, int height) { update(anchor, false, 0, 0, true, width, height); } /** - * <p>Updates the position and the dimension of the popup window. Width and - * height can be set to -1 to update location only. Calling this function - * also updates the window with the current popup state as - * described for {@link #update()}.</p> - * - * <p>If the view later scrolls to move <code>anchor</code> to a different - * location, the popup will be moved correspondingly.</p> + * Updates the position and the dimension of the popup window. + * <p> + * Width and height can be set to -1 to update location only. Calling this + * function also updates the window with the current popup state as + * described for {@link #update()}. + * <p> + * If the view later scrolls to move {@code anchor} to a different + * location, the popup will be moved correspondingly. * * @param anchor the popup's anchor view * @param xoff x offset from the view's left edge * @param yoff y offset from the view's bottom edge - * @param width the new width, can be -1 to ignore - * @param height the new height, can be -1 to ignore + * @param width the new width, must be >= 0 or -1 to ignore + * @param height the new height, must be >= 0 or -1 to ignore */ public void update(View anchor, int xoff, int yoff, int width, int height) { update(anchor, true, xoff, yoff, true, width, height); diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java index 28b4db2..20aa972 100644 --- a/core/java/android/widget/RadialTimePickerView.java +++ b/core/java/android/widget/RadialTimePickerView.java @@ -641,7 +641,7 @@ public class RadialTimePickerView extends View { mCircleRadius = Math.min(mXCenter, mYCenter); mMinHypotenuseForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; - mMaxHypotenuseForOuterNumber = mCircleRadius - mTextInset[HOURS] - mSelectorRadius; + mMaxHypotenuseForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; mHalfwayHypotenusePoint = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; calculatePositionsHours(); @@ -1144,30 +1144,31 @@ public class RadialTimePickerView extends View { private void adjustPicker(int step) { final int stepSize; - final int initialValue; + final int initialStep; final int maxValue; final int minValue; if (mShowHours) { - stepSize = DEGREES_FOR_ONE_HOUR; - initialValue = getCurrentHour() % 12; + stepSize = 1; + final int currentHour24 = getCurrentHour(); if (mIs24HourMode) { - maxValue = 23; + initialStep = currentHour24; minValue = 0; + maxValue = 23; } else { - maxValue = 12; + initialStep = hour24To12(currentHour24); minValue = 1; + maxValue = 12; } } else { - stepSize = DEGREES_FOR_ONE_MINUTE; - initialValue = getCurrentMinute(); - - maxValue = 55; + stepSize = 5; + initialStep = getCurrentMinute() / stepSize; minValue = 0; + maxValue = 55; } - final int steppedValue = snapOnly30s(initialValue * stepSize, step) / stepSize; - final int clampedValue = MathUtils.constrain(steppedValue, minValue, maxValue); + final int nextValue = (initialStep + step) * stepSize; + final int clampedValue = MathUtils.constrain(nextValue, minValue, maxValue); if (mShowHours) { setCurrentHour(clampedValue); } else { diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java deleted file mode 100644 index c807d56..0000000 --- a/core/java/android/widget/SimpleMonthAdapter.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.widget; - -import com.android.internal.R; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.view.View; -import android.view.ViewGroup; -import android.widget.SimpleMonthView.OnDayClickListener; - -import java.util.Calendar; - -/** - * An adapter for a list of {@link android.widget.SimpleMonthView} items. - */ -class SimpleMonthAdapter extends BaseAdapter { - private final Calendar mMinDate = Calendar.getInstance(); - private final Calendar mMaxDate = Calendar.getInstance(); - - private final Context mContext; - - private Calendar mSelectedDay = Calendar.getInstance(); - private ColorStateList mCalendarTextColors = ColorStateList.valueOf(Color.BLACK); - private ColorStateList mCalendarDayBackgroundColor = ColorStateList.valueOf(Color.MAGENTA); - private OnDaySelectedListener mOnDaySelectedListener; - - private int mFirstDayOfWeek; - - public SimpleMonthAdapter(Context context) { - mContext = context; - } - - public void setRange(Calendar min, Calendar max) { - mMinDate.setTimeInMillis(min.getTimeInMillis()); - mMaxDate.setTimeInMillis(max.getTimeInMillis()); - - notifyDataSetInvalidated(); - } - - public void setFirstDayOfWeek(int firstDayOfWeek) { - mFirstDayOfWeek = firstDayOfWeek; - - notifyDataSetInvalidated(); - } - - public int getFirstDayOfWeek() { - return mFirstDayOfWeek; - } - - /** - * Updates the selected day and related parameters. - * - * @param day The day to highlight - */ - public void setSelectedDay(Calendar day) { - mSelectedDay = day; - - notifyDataSetChanged(); - } - - /** - * Sets the listener to call when the user selects a day. - * - * @param listener The listener to call. - */ - public void setOnDaySelectedListener(OnDaySelectedListener listener) { - mOnDaySelectedListener = listener; - } - - void setCalendarTextColor(ColorStateList colors) { - mCalendarTextColors = colors; - } - - void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) { - mCalendarDayBackgroundColor = dayBackgroundColor; - } - - /** - * Sets the text color, size, style, hint color, and highlight color from - * the specified TextAppearance resource. This is mostly copied from - * {@link TextView#setTextAppearance(Context, int)}. - */ - void setCalendarTextAppearance(int resId) { - final TypedArray a = mContext.obtainStyledAttributes(resId, R.styleable.TextAppearance); - - final ColorStateList textColor = a.getColorStateList(R.styleable.TextAppearance_textColor); - if (textColor != null) { - mCalendarTextColors = textColor; - } - - // TODO: Support font size, etc. - - a.recycle(); - } - - @Override - public int getCount() { - final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR); - final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH); - return diffMonth + 12 * diffYear + 1; - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @SuppressWarnings("unchecked") - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final SimpleMonthView v; - if (convertView != null) { - v = (SimpleMonthView) convertView; - } else { - v = new SimpleMonthView(mContext); - - // Set up the new view - final AbsListView.LayoutParams params = new AbsListView.LayoutParams( - AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.MATCH_PARENT); - v.setLayoutParams(params); - v.setClickable(true); - v.setOnDayClickListener(mOnDayClickListener); - - v.setMonthTextColor(mCalendarTextColors); - v.setDayOfWeekTextColor(mCalendarTextColors); - v.setDayTextColor(mCalendarTextColors); - - v.setDayBackgroundColor(mCalendarDayBackgroundColor); - } - - final int minMonth = mMinDate.get(Calendar.MONTH); - final int minYear = mMinDate.get(Calendar.YEAR); - final int currentMonth = position + minMonth; - final int month = currentMonth % 12; - final int year = currentMonth / 12 + minYear; - final int selectedDay; - if (isSelectedDayInMonth(year, month)) { - selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH); - } else { - selectedDay = -1; - } - - // Invokes requestLayout() to ensure that the recycled view is set with the appropriate - // height/number of weeks before being displayed. - v.reuse(); - - final int enabledDayRangeStart; - if (minMonth == month && minYear == year) { - enabledDayRangeStart = mMinDate.get(Calendar.DAY_OF_MONTH); - } else { - enabledDayRangeStart = 1; - } - - final int enabledDayRangeEnd; - if (mMaxDate.get(Calendar.MONTH) == month && mMaxDate.get(Calendar.YEAR) == year) { - enabledDayRangeEnd = mMaxDate.get(Calendar.DAY_OF_MONTH); - } else { - enabledDayRangeEnd = 31; - } - - v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek, - enabledDayRangeStart, enabledDayRangeEnd); - v.invalidate(); - - return v; - } - - private boolean isSelectedDayInMonth(int year, int month) { - return mSelectedDay.get(Calendar.YEAR) == year && mSelectedDay.get(Calendar.MONTH) == month; - } - - private boolean isCalendarInRange(Calendar value) { - return value.compareTo(mMinDate) >= 0 && value.compareTo(mMaxDate) <= 0; - } - - private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() { - @Override - public void onDayClick(SimpleMonthView view, Calendar day) { - if (day != null && isCalendarInRange(day)) { - setSelectedDay(day); - - if (mOnDaySelectedListener != null) { - mOnDaySelectedListener.onDaySelected(SimpleMonthAdapter.this, day); - } - } - } - }; - - public interface OnDaySelectedListener { - public void onDaySelected(SimpleMonthAdapter view, Calendar day); - } -} diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index 58ad515..4e5a39a 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -18,8 +18,8 @@ package android.widget; import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Align; @@ -29,8 +29,6 @@ import android.graphics.Typeface; import android.os.Bundle; import android.text.TextPaint; import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.format.Time; import android.util.AttributeSet; import android.util.IntArray; import android.util.StateSet; @@ -38,13 +36,13 @@ import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.internal.R; import com.android.internal.widget.ExploreByTouchHelper; import java.text.SimpleDateFormat; import java.util.Calendar; -import java.util.Formatter; import java.util.Locale; /** @@ -52,93 +50,80 @@ import java.util.Locale; * within the specified month. */ class SimpleMonthView extends View { - private static final int MIN_ROW_HEIGHT = 10; + private static final int DAYS_IN_WEEK = 7; + private static final int MAX_WEEKS_IN_MONTH = 6; private static final int DEFAULT_SELECTED_DAY = -1; private static final int DEFAULT_WEEK_START = Calendar.SUNDAY; - private static final int DEFAULT_NUM_DAYS = 7; - private static final int DEFAULT_NUM_ROWS = 6; - private static final int MAX_NUM_ROWS = 6; - private final Formatter mFormatter; - private final StringBuilder mStringBuilder; - - private final int mMonthTextSize; - private final int mDayOfWeekTextSize; - private final int mDayTextSize; - - /** Height of the header containing the month and day of week labels. */ - private final int mMonthHeaderHeight; + private static final String DEFAULT_TITLE_FORMAT = "MMMMy"; + private static final String DAY_OF_WEEK_FORMAT = "EEEEE"; private final TextPaint mMonthPaint = new TextPaint(); private final TextPaint mDayOfWeekPaint = new TextPaint(); private final TextPaint mDayPaint = new TextPaint(); + private final Paint mDaySelectorPaint = new Paint(); + private final Paint mDayHighlightPaint = new Paint(); - private final Paint mDayBackgroundPaint = new Paint(); + private final Calendar mCalendar = Calendar.getInstance(); + private final Calendar mDayLabelCalendar = Calendar.getInstance(); - /** Single-letter (when available) formatter for the day of week label. */ - private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault()); + private final MonthViewTouchHelper mTouchHelper; - // affects the padding on the sides of this view - private int mPadding = 0; + private final SimpleDateFormat mTitleFormatter; + private final SimpleDateFormat mDayOfWeekFormatter; - private String mDayOfWeekTypeface; - private String mMonthTypeface; + private CharSequence mTitle; private int mMonth; private int mYear; - // Quick reference to the width of this view, matches parent - private int mWidth; - - // The height this view should draw at in pixels, set by height param - private final int mRowHeight; + private int mPaddedWidth; + private int mPaddedHeight; - // If this view contains the today - private boolean mHasToday = false; + private final int mMonthHeight; + private final int mDayOfWeekHeight; + private final int mDayHeight; + private final int mCellWidth; + private final int mDaySelectorRadius; - // Which day is selected [0-6] or -1 if no day is selected + /** The day of month for the selected day, or -1 if no day is selected. */ private int mActivatedDay = -1; - // Which day is today [0-6] or -1 if no day is today + /** + * The day of month for today, or -1 if the today is not in the current + * month. + */ private int mToday = DEFAULT_SELECTED_DAY; - // Which day of the week to start on [0-6] + /** The first day of the week (ex. Calendar.SUNDAY). */ private int mWeekStart = DEFAULT_WEEK_START; - // How many days to display - private int mNumDays = DEFAULT_NUM_DAYS; - - // The number of days + a spot for week number if it is displayed - private int mNumCells = mNumDays; + /** The number of days (ex. 28) in the current month. */ + private int mDaysInMonth; - private int mDayOfWeekStart = 0; + /** + * The day of week (ex. Calendar.SUNDAY) for the first day of the current + * month. + */ + private int mDayOfWeekStart; - // First enabled day + /** The day of month for the first (inclusive) enabled day. */ private int mEnabledDayStart = 1; - // Last enabled day + /** The day of month for the last (inclusive) enabled day. */ private int mEnabledDayEnd = 31; - private final Calendar mCalendar = Calendar.getInstance(); - private final Calendar mDayLabelCalendar = Calendar.getInstance(); - - private final MonthViewTouchHelper mTouchHelper; - - private int mNumRows = DEFAULT_NUM_ROWS; + /** The number of week rows needed to display the current month. */ + private int mNumWeeks = MAX_WEEKS_IN_MONTH; - // Optional listener for handling day click actions + /** Optional listener for handling day click actions. */ private OnDayClickListener mOnDayClickListener; - // Whether to prevent setting the accessibility delegate - private boolean mLockAccessibilityDelegate; - - private int mNormalTextColor; - private int mDisabledTextColor; - private int mSelectedDayColor; - private ColorStateList mDayTextColor; + private int mTouchedDay = -1; + public SimpleMonthView(Context context) { this(context, null); } @@ -155,64 +140,123 @@ class SimpleMonthView extends View { super(context, attrs, defStyleAttr, defStyleRes); final Resources res = context.getResources(); - mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface); - mMonthTypeface = res.getString(R.string.sans_serif); - - mStringBuilder = new StringBuilder(50); - mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - - mDayTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size); - mMonthTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size); - mDayOfWeekTextSize = res.getDimensionPixelSize( - R.dimen.datepicker_month_day_label_text_size); - mMonthHeaderHeight = res.getDimensionPixelOffset( - R.dimen.datepicker_month_list_item_header_height); - - mRowHeight = Math.max(MIN_ROW_HEIGHT, - (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height) - - mMonthHeaderHeight) / MAX_NUM_ROWS); + mMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height); + mDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height); + mDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height); + mCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width); + mDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius); // Set up accessibility components. mTouchHelper = new MonthViewTouchHelper(this); setAccessibilityDelegate(mTouchHelper); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - mLockAccessibilityDelegate = true; - initPaints(); + final Locale locale = res.getConfiguration().locale; + final String titleFormat = DateFormat.getBestDateTimePattern(locale, DEFAULT_TITLE_FORMAT); + mTitleFormatter = new SimpleDateFormat(titleFormat, locale); + mDayOfWeekFormatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, locale); + + setClickable(true); + initPaints(res); + } + + /** + * Applies the specified text appearance resource to a paint, returning the + * text color if one is set in the text appearance. + * + * @param p the paint to modify + * @param resId the resource ID of the text appearance + * @return the text color, if available + */ + private ColorStateList applyTextAppearance(Paint p, int resId) { + final TypedArray ta = mContext.obtainStyledAttributes(null, + R.styleable.TextAppearance, 0, resId); + + final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily); + if (fontFamily != null) { + p.setTypeface(Typeface.create(fontFamily, 0)); + } + + p.setTextSize(ta.getDimensionPixelSize( + R.styleable.TextAppearance_textSize, (int) p.getTextSize())); + + final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor); + if (textColor != null) { + final int enabledColor = textColor.getColorForState(ENABLED_STATE_SET, 0); + p.setColor(enabledColor); + } + + ta.recycle(); + + return textColor; + } + + public void setMonthTextAppearance(int resId) { + applyTextAppearance(mMonthPaint, resId); + invalidate(); + } + + public void setDayOfWeekTextAppearance(int resId) { + applyTextAppearance(mDayOfWeekPaint, resId); + invalidate(); + } + + public void setDayTextAppearance(int resId) { + final ColorStateList textColor = applyTextAppearance(mDayPaint, resId); + if (textColor != null) { + mDayTextColor = textColor; + } + + invalidate(); + } + + public CharSequence getTitle() { + if (mTitle == null) { + mTitle = mTitleFormatter.format(mCalendar.getTime()); + } + return mTitle; } /** * Sets up the text and style properties for painting. */ - private void initPaints() { + private void initPaints(Resources res) { + final String monthTypeface = res.getString(R.string.date_picker_month_typeface); + final String dayOfWeekTypeface = res.getString(R.string.date_picker_day_of_week_typeface); + final String dayTypeface = res.getString(R.string.date_picker_day_typeface); + + final int monthTextSize = res.getDimensionPixelSize( + R.dimen.date_picker_month_text_size); + final int dayOfWeekTextSize = res.getDimensionPixelSize( + R.dimen.date_picker_day_of_week_text_size); + final int dayTextSize = res.getDimensionPixelSize( + R.dimen.date_picker_day_text_size); + mMonthPaint.setAntiAlias(true); - mMonthPaint.setTextSize(mMonthTextSize); - mMonthPaint.setTypeface(Typeface.create(mMonthTypeface, Typeface.BOLD)); + mMonthPaint.setTextSize(monthTextSize); + mMonthPaint.setTypeface(Typeface.create(monthTypeface, 0)); mMonthPaint.setTextAlign(Align.CENTER); mMonthPaint.setStyle(Style.FILL); mDayOfWeekPaint.setAntiAlias(true); - mDayOfWeekPaint.setTextSize(mDayOfWeekTextSize); - mDayOfWeekPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.BOLD)); + mDayOfWeekPaint.setTextSize(dayOfWeekTextSize); + mDayOfWeekPaint.setTypeface(Typeface.create(dayOfWeekTypeface, 0)); mDayOfWeekPaint.setTextAlign(Align.CENTER); mDayOfWeekPaint.setStyle(Style.FILL); - mDayBackgroundPaint.setAntiAlias(true); - mDayBackgroundPaint.setStyle(Style.FILL); + mDaySelectorPaint.setAntiAlias(true); + mDaySelectorPaint.setStyle(Style.FILL); + + mDayHighlightPaint.setAntiAlias(true); + mDayHighlightPaint.setStyle(Style.FILL); mDayPaint.setAntiAlias(true); - mDayPaint.setTextSize(mDayTextSize); + mDayPaint.setTextSize(dayTextSize); + mDayPaint.setTypeface(Typeface.create(dayTypeface, 0)); mDayPaint.setTextAlign(Align.CENTER); mDayPaint.setStyle(Style.FILL); } - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - mDayFormatter = new SimpleDateFormat("EEEEE", newConfig.locale); - } - void setMonthTextColor(ColorStateList monthTextColor) { final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0); mMonthPaint.setColor(enabledColor); @@ -230,20 +274,18 @@ class SimpleMonthView extends View { invalidate(); } - void setDayBackgroundColor(ColorStateList dayBackgroundColor) { + void setDaySelectorColor(ColorStateList dayBackgroundColor) { final int activatedColor = dayBackgroundColor.getColorForState( StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); - mDayBackgroundPaint.setColor(activatedColor); + mDaySelectorPaint.setColor(activatedColor); invalidate(); } - @Override - public void setAccessibilityDelegate(AccessibilityDelegate delegate) { - // Workaround for a JB MR1 issue where accessibility delegates on - // top-level ListView items are overwritten. - if (!mLockAccessibilityDelegate) { - super.setAccessibilityDelegate(delegate); - } + void setDayHighlightColor(ColorStateList dayHighlightColor) { + final int pressedColor = dayHighlightColor.getColorForState( + StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED), 0); + mDayHighlightPaint.setColor(pressedColor); + invalidate(); } public void setOnDayClickListener(OnDayClickListener listener) { @@ -253,30 +295,124 @@ class SimpleMonthView extends View { @Override public boolean dispatchHoverEvent(MotionEvent event) { // First right-of-refusal goes the touch exploration helper. - if (mTouchHelper.dispatchHoverEvent(event)) { - return true; - } - return super.dispatchHoverEvent(event); + return mTouchHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { - case MotionEvent.ACTION_UP: - final int day = getDayFromLocation(event.getX(), event.getY()); - if (day >= 0) { - onDayClick(day); + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + final int touchedDay = getDayAtLocation(event.getX(), event.getY()); + if (mTouchedDay != touchedDay) { + mTouchedDay = touchedDay; + invalidate(); } break; + + case MotionEvent.ACTION_UP: + final int clickedDay = getDayAtLocation(event.getX(), event.getY()); + onDayClicked(clickedDay); + // Fall through. + case MotionEvent.ACTION_CANCEL: + // Reset touched day on stream end. + mTouchedDay = -1; + invalidate(); + break; } return true; } @Override protected void onDraw(Canvas canvas) { - drawMonthTitle(canvas); - drawWeekDayLabels(canvas); + final int paddingLeft = getPaddingLeft(); + final int paddingTop = getPaddingTop(); + canvas.translate(paddingLeft, paddingTop); + + drawMonth(canvas); + drawDaysOfWeek(canvas); drawDays(canvas); + + canvas.translate(-paddingLeft, -paddingTop); + } + + private void drawMonth(Canvas canvas) { + final float x = mPaddedWidth / 2f; + + // Vertically centered within the month header height. + final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent(); + final float y = (mMonthHeight - lineHeight) / 2f; + + canvas.drawText(getTitle().toString(), x, y, mMonthPaint); + } + + private void drawDaysOfWeek(Canvas canvas) { + final float cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2); + + // Vertically centered within the cell height. + final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); + final float y = mMonthHeight + (mDayOfWeekHeight - lineHeight) / 2f; + + for (int i = 0; i < DAYS_IN_WEEK; i++) { + final int calendarDay = (i + mWeekStart) % DAYS_IN_WEEK; + mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); + + final String dayLabel = mDayOfWeekFormatter.format(mDayLabelCalendar.getTime()); + final float x = (2 * i + 1) * cellWidthHalf; + canvas.drawText(dayLabel, x, y, mDayOfWeekPaint); + } + } + + /** + * Draws the month days. + */ + private void drawDays(Canvas canvas) { + final int cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2); + + // Vertically centered within the cell height. + final float halfLineHeight = (mDayPaint.ascent() + mDayPaint.descent()) / 2; + float centerY = mMonthHeight + mDayOfWeekHeight + mDayHeight / 2f; + + for (int day = 1, j = findDayOffset(); day <= mDaysInMonth; day++) { + final int x = (2 * j + 1) * cellWidthHalf; + int stateMask = 0; + + if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { + stateMask |= StateSet.VIEW_STATE_ENABLED; + } + + final boolean isDayActivated = mActivatedDay == day; + if (isDayActivated) { + stateMask |= StateSet.VIEW_STATE_ACTIVATED; + + // Adjust the circle to be centered on the row. + canvas.drawCircle(x, centerY, mDaySelectorRadius, mDaySelectorPaint); + } else if (mTouchedDay == day) { + stateMask |= StateSet.VIEW_STATE_PRESSED; + + // Adjust the circle to be centered on the row. + canvas.drawCircle(x, centerY, mDaySelectorRadius, mDayHighlightPaint); + } + + final boolean isDayToday = mToday == day; + final int dayTextColor; + if (isDayToday && !isDayActivated) { + dayTextColor = mDaySelectorPaint.getColor(); + } else { + final int[] stateSet = StateSet.get(stateMask); + dayTextColor = mDayTextColor.getColorForState(stateSet, 0); + } + mDayPaint.setColor(dayTextColor); + + canvas.drawText("" + day, x, centerY - halfLineHeight, mDayPaint); + + j++; + + if (j == DAYS_IN_WEEK) { + j = 0; + centerY += mDayHeight; + } + } } private static boolean isValidDayOfWeek(int day) { @@ -288,18 +424,52 @@ class SimpleMonthView extends View { } /** - * Sets all the parameters for displaying this week. Parameters have a default value and - * will only update if a new value is included, except for focus month, which will always - * default to no focus month if no value is passed in. The only required parameter is the - * week start. + * Sets the selected day. * - * @param selectedDay the selected day of the month, or -1 for no selection. - * @param month the month. - * @param year the year. - * @param weekStart which day the week should start on. {@link Calendar#SUNDAY} through - * {@link Calendar#SATURDAY}. - * @param enabledDayStart the first enabled day. - * @param enabledDayEnd the last enabled day. + * @param dayOfMonth the selected day of the month, or {@code -1} to clear + * the selection + */ + public void setSelectedDay(int dayOfMonth) { + mActivatedDay = dayOfMonth; + + // Invalidate cached accessibility information. + mTouchHelper.invalidateRoot(); + invalidate(); + } + + /** + * Sets the first day of the week. + * + * @param weekStart which day the week should start on, valid values are + * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} + */ + public void setFirstDayOfWeek(int weekStart) { + if (isValidDayOfWeek(weekStart)) { + mWeekStart = weekStart; + } else { + mWeekStart = mCalendar.getFirstDayOfWeek(); + } + + // Invalidate cached accessibility information. + mTouchHelper.invalidateRoot(); + invalidate(); + } + + /** + * Sets all the parameters for displaying this week. + * <p> + * Parameters have a default value and will only update if a new value is + * included, except for focus month, which will always default to no focus + * month if no value is passed in. The only required parameter is the week + * start. + * + * @param selectedDay the selected day of the month, or -1 for no selection + * @param month the month + * @param year the year + * @param weekStart which day the week should start on, valid values are + * {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY} + * @param enabledDayStart the first enabled day + * @param enabledDayEnd the last enabled day */ void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart, int enabledDayEnd) { @@ -310,12 +480,6 @@ class SimpleMonthView extends View { } mYear = year; - // Figure out what day today is - final Time today = new Time(Time.getCurrentTimezone()); - today.setToNow(); - mHasToday = false; - mToday = -1; - mCalendar.set(Calendar.MONTH, mMonth); mCalendar.set(Calendar.YEAR, mYear); mCalendar.set(Calendar.DAY_OF_MONTH, 1); @@ -334,15 +498,20 @@ class SimpleMonthView extends View { mEnabledDayEnd = enabledDayEnd; } - mNumCells = getDaysInMonth(mMonth, mYear); - for (int i = 0; i < mNumCells; i++) { + // Figure out what day today is. + final Calendar today = Calendar.getInstance(); + mToday = -1; + mDaysInMonth = getDaysInMonth(mMonth, mYear); + for (int i = 0; i < mDaysInMonth; i++) { final int day = i + 1; if (sameDay(day, today)) { - mHasToday = true; mToday = day; } } - mNumRows = calculateNumRows(); + mNumWeeks = calculateNumRows(); + + // Invalidate the old title. + mTitle = null; // Invalidate cached accessibility information. mTouchHelper.invalidateRoot(); @@ -371,154 +540,118 @@ class SimpleMonthView extends View { } public void reuse() { - mNumRows = DEFAULT_NUM_ROWS; + mNumWeeks = MAX_WEEKS_IN_MONTH; requestLayout(); } private int calculateNumRows() { - int offset = findDayOffset(); - int dividend = (offset + mNumCells) / mNumDays; - int remainder = (offset + mNumCells) % mNumDays; - return (dividend + (remainder > 0 ? 1 : 0)); + final int offset = findDayOffset(); + final int dividend = (offset + mDaysInMonth) / DAYS_IN_WEEK; + final int remainder = (offset + mDaysInMonth) % DAYS_IN_WEEK; + return dividend + (remainder > 0 ? 1 : 0); } - private boolean sameDay(int day, Time today) { - return mYear == today.year && - mMonth == today.month && - day == today.monthDay; + private boolean sameDay(int day, Calendar today) { + return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH) + && day == today.get(Calendar.DAY_OF_MONTH); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows - + mMonthHeaderHeight); + final int preferredHeight = mDayHeight * mNumWeeks + mDayOfWeekHeight + mMonthHeight + + getPaddingTop() + getPaddingBottom(); + final int preferredWidth = mCellWidth * DAYS_IN_WEEK + + getPaddingStart() + getPaddingEnd(); + final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec); + final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec); + setMeasuredDimension(resolvedWidth, resolvedHeight); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mWidth = w; + mPaddedWidth = w - getPaddingLeft() - getPaddingRight(); + mPaddedHeight = w - getPaddingTop() - getPaddingBottom(); // Invalidate cached accessibility information. mTouchHelper.invalidateRoot(); } - private String getMonthAndYearString() { - int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_NO_MONTH_DAY; - mStringBuilder.setLength(0); - long millis = mCalendar.getTimeInMillis(); - return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags, - Time.getCurrentTimezone()).toString(); - } - - private void drawMonthTitle(Canvas canvas) { - final float x = (mWidth + 2 * mPadding) / 2f; - - // Centered on the upper half of the month header. - final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent(); - final float y = mMonthHeaderHeight * 0.25f - lineHeight / 2f; - - canvas.drawText(getMonthAndYearString(), x, y, mMonthPaint); - } - - private void drawWeekDayLabels(Canvas canvas) { - final float dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); - - // Centered on the lower half of the month header. - final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); - final float y = mMonthHeaderHeight * 0.75f - lineHeight / 2f; - - for (int i = 0; i < mNumDays; i++) { - final int calendarDay = (i + mWeekStart) % mNumDays; - mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); - - final String dayLabel = mDayFormatter.format(mDayLabelCalendar.getTime()); - final float x = (2 * i + 1) * dayWidthHalf + mPadding; - canvas.drawText(dayLabel, x, y, mDayOfWeekPaint); + private int findDayOffset() { + final int offset = mDayOfWeekStart - mWeekStart; + if (mDayOfWeekStart < mWeekStart) { + return offset + DAYS_IN_WEEK; } + return offset; } /** - * Draws the month days. + * Calculates the day of the month at the specified touch position. Returns + * the day of the month or -1 if the position wasn't in a valid day. + * + * @param x the x position of the touch event + * @param y the y position of the touch event + * @return the day of the month at (x, y) or -1 if the position wasn't in a + * valid day */ - private void drawDays(Canvas canvas) { - final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); - - // Centered within the row. - final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); - float y = mMonthHeaderHeight + (mRowHeight - lineHeight) / 2f; - - for (int day = 1, j = findDayOffset(); day <= mNumCells; day++) { - final int x = (2 * j + 1) * dayWidthHalf + mPadding; - int stateMask = 0; - - if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { - stateMask |= StateSet.VIEW_STATE_ENABLED; - } - - if (mActivatedDay == day) { - stateMask |= StateSet.VIEW_STATE_ACTIVATED; - - // Adjust the circle to be centered the row. - final float rowCenterY = y + lineHeight / 2; - canvas.drawCircle(x, rowCenterY, mRowHeight / 2, - mDayBackgroundPaint); - } - - final int[] stateSet = StateSet.get(stateMask); - final int dayTextColor = mDayTextColor.getColorForState(stateSet, 0); - mDayPaint.setColor(dayTextColor); - - final boolean isDayToday = mHasToday && mToday == day; - mDayPaint.setFakeBoldText(isDayToday); - - canvas.drawText(String.format("%d", day), x, y, mDayPaint); + private int getDayAtLocation(float x, float y) { + final int paddedX = (int) (x - getPaddingLeft() + 0.5f); + if (paddedX < 0 || paddedX >= mPaddedWidth) { + return -1; + } - j++; + final int headerHeight = mMonthHeight + mDayOfWeekHeight; + final int paddedY = (int) (y - getPaddingTop() + 0.5f); + if (paddedY < headerHeight || paddedY >= mPaddedHeight) { + return -1; + } - if (j == mNumDays) { - j = 0; - y += mRowHeight; - } + final int row = (paddedY - headerHeight) / mDayHeight; + final int col = (paddedX * DAYS_IN_WEEK) / mPaddedWidth; + final int index = col + row * DAYS_IN_WEEK; + final int day = index + 1 - findDayOffset(); + if (day < 1 || day > mDaysInMonth) { + return -1; } - } - private int findDayOffset() { - return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart) - - mWeekStart; + return day; } /** - * Calculates the day that the given x position is in, accounting for week - * number. Returns the day or -1 if the position wasn't in a day. + * Calculates the bounds of the specified day. * - * @param x The x position of the touch event - * @return The day number, or -1 if the position wasn't in a day + * @param day the day of the month + * @param outBounds the rect to populate with bounds */ - private int getDayFromLocation(float x, float y) { - int dayStart = mPadding; - if (x < dayStart || x > mWidth - mPadding) { - return -1; + private boolean getBoundsForDay(int day, Rect outBounds) { + if (day < 1 || day > mDaysInMonth) { + return false; } - // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels - int row = (int) (y - mMonthHeaderHeight) / mRowHeight; - int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)); - int day = column - findDayOffset() + 1; - day += row * mNumDays; - if (day < 1 || day > mNumCells) { - return -1; - } - return day; + final int index = day - 1 + findDayOffset(); + final int row = index / DAYS_IN_WEEK; + final int col = index % DAYS_IN_WEEK; + + final int headerHeight = mMonthHeight + mDayOfWeekHeight; + final int paddedY = row * mDayHeight + headerHeight; + final int paddedX = col * mPaddedWidth; + + final int y = paddedY + getPaddingTop(); + final int x = paddedX + getPaddingLeft(); + + final int cellHeight = mDayHeight; + final int cellWidth = mPaddedWidth / DAYS_IN_WEEK; + outBounds.set(x, y, (x + cellWidth), (y + cellHeight)); + + return true; } /** * Called when the user clicks on a day. Handles callbacks to the * {@link OnDayClickListener} if one is set. * - * @param day The day that was clicked + * @param day the day that was clicked */ - private void onDayClick(int day) { + private void onDayClicked(int day) { if (mOnDayClickListener != null) { Calendar date = Calendar.getInstance(); date.set(mYear, mMonth, day); @@ -530,44 +663,6 @@ class SimpleMonthView extends View { } /** - * @return The date that has accessibility focus, or {@code null} if no date - * has focus - */ - Calendar getAccessibilityFocus() { - final int day = mTouchHelper.getFocusedVirtualView(); - Calendar date = null; - if (day >= 0) { - date = Calendar.getInstance(); - date.set(mYear, mMonth, day); - } - return date; - } - - /** - * Clears accessibility focus within the view. No-op if the view does not - * contain accessibility focus. - */ - public void clearAccessibilityFocus() { - mTouchHelper.clearFocusedVirtualView(); - } - - /** - * Attempts to restore accessibility focus to the specified date. - * - * @param day The date which should receive focus - * @return {@code false} if the date is not valid for this month view, or - * {@code true} if the date received focus - */ - boolean restoreAccessibilityFocus(Calendar day) { - if ((day.get(Calendar.YEAR) != mYear) || (day.get(Calendar.MONTH) != mMonth) || - (day.get(Calendar.DAY_OF_MONTH) > mNumCells)) { - return false; - } - mTouchHelper.setFocusedVirtualView(day.get(Calendar.DAY_OF_MONTH)); - return true; - } - - /** * Provides a virtual view hierarchy for interfacing with an accessibility * service. */ @@ -581,24 +676,9 @@ class SimpleMonthView extends View { super(host); } - public void setFocusedVirtualView(int virtualViewId) { - getAccessibilityNodeProvider(SimpleMonthView.this).performAction( - virtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); - } - - public void clearFocusedVirtualView() { - final int focusedVirtualView = getFocusedVirtualView(); - if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) { - getAccessibilityNodeProvider(SimpleMonthView.this).performAction( - focusedVirtualView, - AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, - null); - } - } - @Override protected int getVirtualViewAt(float x, float y) { - final int day = getDayFromLocation(x, y); + final int day = getDayAtLocation(x, y); if (day >= 0) { return day; } @@ -607,7 +687,7 @@ class SimpleMonthView extends View { @Override protected void getVisibleVirtualViews(IntArray virtualViewIds) { - for (int day = 1; day <= mNumCells; day++) { + for (int day = 1; day <= mDaysInMonth; day++) { virtualViewIds.add(day); } } @@ -619,11 +699,20 @@ class SimpleMonthView extends View { @Override protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { - getItemBounds(virtualViewId, mTempRect); + final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect); + + if (!hasBounds) { + // The day is invalid, kill the node. + mTempRect.setEmpty(); + node.setContentDescription(""); + node.setBoundsInParent(mTempRect); + node.setVisibleToUser(false); + return; + } node.setContentDescription(getItemDescription(virtualViewId)); node.setBoundsInParent(mTempRect); - node.addAction(AccessibilityNodeInfo.ACTION_CLICK); + node.addAction(AccessibilityAction.ACTION_CLICK); if (virtualViewId == mActivatedDay) { node.setSelected(true); @@ -636,7 +725,7 @@ class SimpleMonthView extends View { Bundle arguments) { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: - onDayClick(virtualViewId); + onDayClicked(virtualViewId); return true; } @@ -644,26 +733,6 @@ class SimpleMonthView extends View { } /** - * Calculates the bounding rectangle of a given time object. - * - * @param day The day to calculate bounds for - * @param rect The rectangle in which to store the bounds - */ - private void getItemBounds(int day, Rect rect) { - final int offsetX = mPadding; - final int offsetY = mMonthHeaderHeight; - final int cellHeight = mRowHeight; - final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays); - final int index = ((day - 1) + findDayOffset()); - final int row = (index / mNumDays); - final int column = (index % mNumDays); - final int x = (offsetX + (column * cellWidth)); - final int y = (offsetY + (row * cellHeight)); - - rect.set(x, y, (x + cellWidth), (y + cellHeight)); - } - - /** * Generates a description for a given time object. Since this * description will be spoken, the components are ordered by descending * specificity as DAY MONTH YEAR. diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java index 4323851..aad0625 100644 --- a/core/java/android/widget/SuggestionsAdapter.java +++ b/core/java/android/widget/SuggestionsAdapter.java @@ -378,7 +378,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene // Lazily get the URL color from the current theme. TypedValue colorValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true); - mUrlColor = context.getResources().getColorStateList(colorValue.resourceId); + mUrlColor = context.getColorStateList(colorValue.resourceId); } SpannableString text = new SpannableString(url); diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java index 110d79b..c521f72 100644 --- a/core/java/android/widget/TabHost.java +++ b/core/java/android/widget/TabHost.java @@ -603,7 +603,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { // Donut apps get old color scheme tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4); - tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4)); + tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4)); } return tabIndicator; @@ -648,7 +648,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { // Donut apps get old color scheme tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4); - tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4)); + tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4)); } return tabIndicator; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 447e9ac..718ef93 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -111,8 +111,6 @@ import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyCharacterMap; import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewAssistStructure; @@ -2644,94 +2642,92 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Sets the text appearance from the specified style resource. + * <p> + * Use a framework-defined {@code TextAppearance} style like + * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} + * or see {@link android.R.styleable#TextAppearance TextAppearance} for the + * set of attributes that can be used in a custom style. + * + * @param resId the resource identifier of the style to apply + * @attr ref android.R.styleable#TextView_textAppearance + */ + @SuppressWarnings("deprecation") + public void setTextAppearance(@StyleRes int resId) { + setTextAppearance(mContext, resId); + } + + /** * Sets the text color, size, style, hint color, and highlight color * from the specified TextAppearance resource. + * + * @deprecated Use {@link #setTextAppearance(int)} instead. */ - public void setTextAppearance(Context context, @StyleRes int resid) { - TypedArray appearance = - context.obtainStyledAttributes(resid, - com.android.internal.R.styleable.TextAppearance); - - int color; - ColorStateList colors; - int ts; + @Deprecated + public void setTextAppearance(Context context, @StyleRes int resId) { + final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); - color = appearance.getColor( - com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0); - if (color != 0) { - setHighlightColor(color); + final int textColorHighlight = ta.getColor( + R.styleable.TextAppearance_textColorHighlight, 0); + if (textColorHighlight != 0) { + setHighlightColor(textColorHighlight); } - colors = appearance.getColorStateList(com.android.internal.R.styleable. - TextAppearance_textColor); - if (colors != null) { - setTextColor(colors); + final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor); + if (textColor != null) { + setTextColor(textColor); } - ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable. - TextAppearance_textSize, 0); - if (ts != 0) { - setRawTextSize(ts); + final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0); + if (textSize != 0) { + setRawTextSize(textSize); } - colors = appearance.getColorStateList(com.android.internal.R.styleable. - TextAppearance_textColorHint); - if (colors != null) { - setHintTextColor(colors); + final ColorStateList textColorHint = ta.getColorStateList( + R.styleable.TextAppearance_textColorHint); + if (textColorHint != null) { + setHintTextColor(textColorHint); } - colors = appearance.getColorStateList(com.android.internal.R.styleable. - TextAppearance_textColorLink); - if (colors != null) { - setLinkTextColor(colors); + final ColorStateList textColorLink = ta.getColorStateList( + R.styleable.TextAppearance_textColorLink); + if (textColorLink != null) { + setLinkTextColor(textColorLink); } - String familyName; - int typefaceIndex, styleIndex; - - familyName = appearance.getString(com.android.internal.R.styleable. - TextAppearance_fontFamily); - typefaceIndex = appearance.getInt(com.android.internal.R.styleable. - TextAppearance_typeface, -1); - styleIndex = appearance.getInt(com.android.internal.R.styleable. - TextAppearance_textStyle, -1); - - setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex); - - final int shadowcolor = appearance.getInt( - com.android.internal.R.styleable.TextAppearance_shadowColor, 0); - if (shadowcolor != 0) { - final float dx = appearance.getFloat( - com.android.internal.R.styleable.TextAppearance_shadowDx, 0); - final float dy = appearance.getFloat( - com.android.internal.R.styleable.TextAppearance_shadowDy, 0); - final float r = appearance.getFloat( - com.android.internal.R.styleable.TextAppearance_shadowRadius, 0); + final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily); + final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1); + final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1); + setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex); - setShadowLayer(r, dx, dy, shadowcolor); + final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0); + if (shadowColor != 0) { + final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0); + final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0); + final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0); + setShadowLayer(r, dx, dy, shadowColor); } - if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps, - false)) { + if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) { setTransformationMethod(new AllCapsTransformationMethod(getContext())); } - if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_elegantTextHeight)) { - setElegantTextHeight(appearance.getBoolean( - com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false)); + if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) { + setElegantTextHeight(ta.getBoolean( + R.styleable.TextAppearance_elegantTextHeight, false)); } - if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_letterSpacing)) { - setLetterSpacing(appearance.getFloat( - com.android.internal.R.styleable.TextAppearance_letterSpacing, 0)); + if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) { + setLetterSpacing(ta.getFloat( + R.styleable.TextAppearance_letterSpacing, 0)); } - if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)) { - setFontFeatureSettings(appearance.getString( - com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)); + if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) { + setFontFeatureSettings(ta.getString( + R.styleable.TextAppearance_fontFeatureSettings)); } - appearance.recycle(); + ta.recycle(); } /** @@ -5106,7 +5102,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // - onFocusChanged cannot start it when focus is given to a view with selected text (after // a screen rotation) since layout is not yet initialized at that point. if (mEditor != null && mEditor.mCreatedWithASelection) { - mEditor.startSelectionActionMode(); + mEditor.startSelectionActionModeWithSelection(); mEditor.mCreatedWithASelection = false; } @@ -5114,7 +5110,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can // not be set. Do the test here instead. if (this instanceof ExtractEditText && hasSelection() && mEditor != null) { - mEditor.startSelectionActionMode(); + mEditor.startSelectionActionModeWithSelection(); } unregisterForPreDraw(); @@ -8747,7 +8743,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Selection.setSelection((Spannable) text, start, end); // Make sure selection mode is engaged. if (mEditor != null) { - mEditor.startSelectionActionMode(); + mEditor.startSelectionActionModeWithSelection(); } return true; } @@ -8959,9 +8955,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * A custom implementation can add new entries in the default menu in its * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The - * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and - * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} - * or {@link android.R.id#paste} ids as parameters. + * default actions can also be removed from the menu using + * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, + * {@link android.R.id#cut}, {@link android.R.id#copy} or {@link android.R.id#paste} ids as + * parameters. * * Returning false from * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent @@ -9536,7 +9533,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // TODO: Add an option to configure this private static final float MARQUEE_DELTA_MAX = 0.07f; private static final int MARQUEE_DELAY = 1200; - private static final int MARQUEE_RESTART_DELAY = 1200; private static final int MARQUEE_DP_PER_SECOND = 30; private static final byte MARQUEE_STOPPED = 0x0; diff --git a/core/java/android/widget/TextViewWithCircularIndicator.java b/core/java/android/widget/TextViewWithCircularIndicator.java deleted file mode 100644 index d3c786c..0000000 --- a/core/java/android/widget/TextViewWithCircularIndicator.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.widget; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Typeface; -import android.util.AttributeSet; - -import com.android.internal.R; - -class TextViewWithCircularIndicator extends TextView { - private final Paint mCirclePaint = new Paint(); - private final String mItemIsSelectedText; - - public TextViewWithCircularIndicator(Context context) { - this(context, null); - } - - public TextViewWithCircularIndicator(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TextViewWithCircularIndicator(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public TextViewWithCircularIndicator(Context context, AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - final Resources res = context.getResources(); - mItemIsSelectedText = res.getString(R.string.item_is_selected); - - init(); - } - - private void init() { - mCirclePaint.setTypeface(Typeface.create(mCirclePaint.getTypeface(), Typeface.BOLD)); - mCirclePaint.setAntiAlias(true); - mCirclePaint.setTextAlign(Paint.Align.CENTER); - mCirclePaint.setStyle(Paint.Style.FILL); - } - - public void setCircleColor(int color) { - mCirclePaint.setColor(color); - invalidate(); - } - - @Override - public void onDraw(Canvas canvas) { - if (isActivated()) { - final int width = getWidth(); - final int height = getHeight(); - final int radius = Math.min(width, height) / 2; - canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint); - } - - super.onDraw(canvas); - } - - @Override - public CharSequence getContentDescription() { - final CharSequence itemText = getText(); - if (isActivated()) { - return String.format(mItemIsSelectedText, itemText); - } else { - return itemText; - } - } -}
\ No newline at end of file diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java index 6f0465f..7bd502e 100644 --- a/core/java/android/widget/YearPickerView.java +++ b/core/java/android/widget/YearPickerView.java @@ -17,10 +17,9 @@ package android.widget; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.Resources; import android.util.AttributeSet; -import android.util.StateSet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; @@ -32,23 +31,14 @@ import com.android.internal.R; /** * Displays a selectable list of years. */ -class YearPickerView extends ListView implements AdapterView.OnItemClickListener, - OnDateChangedListener { - private final Calendar mMinDate = Calendar.getInstance(); - private final Calendar mMaxDate = Calendar.getInstance(); - +class YearPickerView extends ListView { private final YearAdapter mAdapter; private final int mViewSize; private final int mChildSize; - private DatePickerController mController; - - private int mSelectedPosition = -1; - private int mYearActivatedColor; + private OnYearSelectedListener mOnYearSelectedListener; - public YearPickerView(Context context) { - this(context, null); - } + private long mCurrentTimeMillis; public YearPickerView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.listViewStyle); @@ -69,104 +59,187 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener mViewSize = res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height); mChildSize = res.getDimensionPixelOffset(R.dimen.datepicker_year_label_height); - setVerticalFadingEdgeEnabled(true); - setFadingEdgeLength(mChildSize / 3); - - final int paddingTop = res.getDimensionPixelSize( - R.dimen.datepicker_year_picker_padding_top); - setPadding(0, paddingTop, 0, 0); + setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + final int year = mAdapter.getYearForPosition(position); + mAdapter.setSelection(year); - setOnItemClickListener(this); - setDividerHeight(0); + if (mOnYearSelectedListener != null) { + mOnYearSelectedListener.onYearChanged(YearPickerView.this, year); + } + } + }); - mAdapter = new YearAdapter(getContext(), R.layout.year_label_text_view); + mAdapter = new YearAdapter(getContext()); setAdapter(mAdapter); } - public void setRange(Calendar min, Calendar max) { - mMinDate.setTimeInMillis(min.getTimeInMillis()); - mMaxDate.setTimeInMillis(max.getTimeInMillis()); + public void setOnYearSelectedListener(OnYearSelectedListener listener) { + mOnYearSelectedListener = listener; + } - updateAdapterData(); + public void setDate(long currentTimeMillis) { + mCurrentTimeMillis = currentTimeMillis; } - public void init(DatePickerController controller) { - mController = controller; - mController.registerOnDateChangedListener(this); + /** + * Sets the currently selected year. Jumps immediately to the new year. + * + * @param year the target year + */ + public void setYear(final int year) { + mAdapter.setSelection(year); - updateAdapterData(); + post(new Runnable() { + @Override + public void run() { + final int position = mAdapter.getPositionForYear(year); + if (position >= 0 && position < getCount()) { + setSelectionCentered(position); + } + } + }); + } - onDateChanged(); + public void setSelectionCentered(int position) { + final int offset = mViewSize / 2 - mChildSize / 2; + setSelectionFromTop(position, offset); } - public void setYearBackgroundColor(ColorStateList yearBackgroundColor) { - mYearActivatedColor = yearBackgroundColor.getColorForState( - StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); - invalidate(); + public void setRange(Calendar min, Calendar max) { + mAdapter.setRange(min, max); } public void setYearTextAppearance(int resId) { mAdapter.setItemTextAppearance(resId); } - private void updateAdapterData() { - mAdapter.clear(); + public void setYearActivatedTextAppearance(int resId) { + mAdapter.setItemActivatedTextAppearance(resId); + } + + private static class YearAdapter extends BaseAdapter { + private static final int ITEM_LAYOUT = R.layout.year_label_text_view; + + private final LayoutInflater mInflater; + + private int mActivatedYear; + private int mMinYear; + private int mCount; - final int maxYear = mMaxDate.get(Calendar.YEAR); - for (int year = mMinDate.get(Calendar.YEAR); year <= maxYear; year++) { - mAdapter.add(year); + private int mItemTextAppearanceResId; + private int mItemActivatedTextAppearanceResId; + + public YearAdapter(Context context) { + mInflater = LayoutInflater.from(context); } - } - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - mController.tryVibrate(); - if (position != mSelectedPosition) { - mSelectedPosition = position; - mAdapter.notifyDataSetChanged(); + public void setRange(Calendar minDate, Calendar maxDate) { + final int minYear = minDate.get(Calendar.YEAR); + final int count = maxDate.get(Calendar.YEAR) - minYear + 1; + + if (mMinYear != minYear || mCount != count) { + mMinYear = minYear; + mCount = count; + notifyDataSetInvalidated(); + } } - mController.onYearSelected(mAdapter.getItem(position)); - } - private class YearAdapter extends ArrayAdapter<Integer> { - private int mItemTextAppearanceResId; + public boolean setSelection(int year) { + if (mActivatedYear != year) { + mActivatedYear = year; + notifyDataSetChanged(); + return true; + } + return false; + } + + public void setItemTextAppearance(int resId) { + mItemTextAppearanceResId = resId; + notifyDataSetChanged(); + } + + public void setItemActivatedTextAppearance(int resId) { + mItemActivatedTextAppearanceResId = resId; + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mCount; + } - public YearAdapter(Context context, int resource) { - super(context, resource); + @Override + public Integer getItem(int position) { + return getYearForPosition(position); + } + + @Override + public long getItemId(int position) { + return getYearForPosition(position); + } + + public int getPositionForYear(int year) { + return year - mMinYear; + } + + public int getYearForPosition(int position) { + return mMinYear + position; + } + + @Override + public boolean hasStableIds() { + return true; } @Override public View getView(int position, View convertView, ViewGroup parent) { - final TextViewWithCircularIndicator v = (TextViewWithCircularIndicator) - super.getView(position, convertView, parent); - v.setTextAppearance(v.getContext(), mItemTextAppearanceResId); - v.setCircleColor(mYearActivatedColor); + if (convertView == null) { + convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); + } - final int year = getItem(position); - final boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year; - v.setActivated(selected); + final int year = getYearForPosition(position); + final boolean activated = mActivatedYear == year; + final int textAppearanceResId; + if (activated && mItemActivatedTextAppearanceResId != 0) { + textAppearanceResId = mItemActivatedTextAppearanceResId; + } else { + textAppearanceResId = mItemTextAppearanceResId; + } + + final TextView v = (TextView) convertView; + v.setText("" + year); + v.setTextAppearance(v.getContext(), textAppearanceResId); + v.setActivated(activated); return v; } - public void setItemTextAppearance(int resId) { - mItemTextAppearanceResId = resId; + @Override + public int getItemViewType(int position) { + return 0; } - } - public void postSetSelectionCentered(final int position) { - postSetSelectionFromTop(position, mViewSize / 2 - mChildSize / 2); - } + @Override + public int getViewTypeCount() { + return 1; + } - public void postSetSelectionFromTop(final int position, final int offset) { - post(new Runnable() { + @Override + public boolean isEmpty() { + return false; + } - @Override - public void run() { - setSelectionFromTop(position, offset); - requestLayout(); - } - }); + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } } public int getFirstPositionOffset() { @@ -177,22 +250,28 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener return firstChild.getTop(); } - @Override - public void onDateChanged() { - updateAdapterData(); - mAdapter.notifyDataSetChanged(); - postSetSelectionCentered( - mController.getSelectedDay().get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR)); - } - /** @hide */ @Override public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { super.onInitializeAccessibilityEventInternal(event); + // There are a bunch of years, so don't bother. if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { event.setFromIndex(0); event.setToIndex(0); } } + + /** + * The callback used to indicate the user changed the year. + */ + public interface OnYearSelectedListener { + /** + * Called upon a year change. + * + * @param view The view associated with this listener. + * @param year The year that was set. + */ + void onYearChanged(YearPickerView view, int year); + } }
\ No newline at end of file diff --git a/core/java/android/hardware/IProCameraUser.aidl b/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl index eacb0f4..a987a16 100644 --- a/core/java/android/hardware/IProCameraUser.aidl +++ b/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,11 @@ * limitations under the License. */ -package android.hardware; +package com.android.internal.app; + +import android.graphics.Bitmap; /** @hide */ -interface IProCameraUser -{ - /** - * Keep up-to-date with frameworks/av/include/camera/IProCameraUser.h - */ - void disconnect(); +oneway interface IAssistScreenshotReceiver { + void send(in Bitmap screenshot); } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 87b6ed7..bea4ece 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -111,6 +111,7 @@ interface IBatteryStats { void noteWifiMulticastDisabledFromSource(in WorkSource ws); void noteNetworkInterfaceType(String iface, int type); void noteNetworkStatsEnabled(); + void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion); void setBatteryState(int status, int health, int plugType, int level, int temp, int volt); long getAwakeTimeBattery(); long getAwakeTimePlugged(); diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index 2bf02f1..7ae7d0f 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -23,7 +23,6 @@ import android.widget.Toolbar; import com.android.internal.R; import com.android.internal.view.ActionBarPolicy; -import com.android.internal.view.ActionModeWrapper; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.view.menu.SubMenuBuilder; diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 02f675c..f479f4f 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -33,6 +33,7 @@ import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.os.Build; import android.os.SELinux; +import android.os.SystemProperties; import android.system.ErrnoException; import android.system.Os; import android.util.Slog; @@ -74,6 +75,7 @@ public class NativeLibraryHelper { final long[] apkHandles; final boolean multiArch; + final boolean extractNativeLibs; public static Handle create(File packageFile) throws IOException { try { @@ -86,14 +88,16 @@ public class NativeLibraryHelper { public static Handle create(Package pkg) throws IOException { return create(pkg.getAllCodePaths(), - (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0); + (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0, + (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0); } public static Handle create(PackageLite lite) throws IOException { - return create(lite.getAllCodePaths(), lite.multiArch); + return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs); } - private static Handle create(List<String> codePaths, boolean multiArch) throws IOException { + private static Handle create(List<String> codePaths, boolean multiArch, + boolean extractNativeLibs) throws IOException { final int size = codePaths.size(); final long[] apkHandles = new long[size]; for (int i = 0; i < size; i++) { @@ -108,12 +112,13 @@ public class NativeLibraryHelper { } } - return new Handle(apkHandles, multiArch); + return new Handle(apkHandles, multiArch, extractNativeLibs); } - Handle(long[] apkHandles, boolean multiArch) { + Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs) { this.apkHandles = apkHandles; this.multiArch = multiArch; + this.extractNativeLibs = extractNativeLibs; mGuard.open("close"); } @@ -146,8 +151,8 @@ public class NativeLibraryHelper { private static native long nativeSumNativeBinaries(long handle, String cpuAbi); - private native static int nativeCopyNativeBinaries(long handle, - String sharedLibraryPath, String abiToCopy); + private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath, + String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge); private static long sumNativeBinaries(Handle handle, String abi) { long sum = 0; @@ -167,7 +172,8 @@ public class NativeLibraryHelper { */ public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) { for (long apkHandle : handle.apkHandles) { - int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi); + int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi, + handle.extractNativeLibs, HAS_NATIVE_BRIDGE); if (res != INSTALL_SUCCEEDED) { return res; } @@ -218,7 +224,8 @@ public class NativeLibraryHelper { /** * Remove the native binaries of a given package. This deletes the files */ - public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot, boolean deleteRootDir) { + public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot, + boolean deleteRootDir) { if (DEBUG_NATIVE) { Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryRoot.getPath()); } @@ -247,7 +254,8 @@ public class NativeLibraryHelper { // asked to or this will prevent installation of future updates. if (deleteRootDir) { if (!nativeLibraryRoot.delete()) { - Slog.w(TAG, "Could not delete native binary directory: " + nativeLibraryRoot.getPath()); + Slog.w(TAG, "Could not delete native binary directory: " + + nativeLibraryRoot.getPath()); } } } @@ -416,6 +424,9 @@ public class NativeLibraryHelper { // We don't care about the other return values for now. private static final int BITCODE_PRESENT = 1; + private static final boolean HAS_NATIVE_BRIDGE = + !"0".equals(SystemProperties.get("ro.dalvik.vm.native.bridge", "0")); + private static native int hasRenderscriptBitcode(long apkHandle); public static boolean hasRenderscriptBitcode(Handle handle) throws IOException { diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index 57fcf57..06bdb24 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -764,17 +764,55 @@ public class InputMethodUtils { private int[] mCurrentProfileIds = new int[0]; private static void buildEnabledInputMethodsSettingString( - StringBuilder builder, Pair<String, ArrayList<String>> pair) { - String id = pair.first; - ArrayList<String> subtypes = pair.second; - builder.append(id); + StringBuilder builder, Pair<String, ArrayList<String>> ime) { + builder.append(ime.first); // Inputmethod and subtypes are saved in the settings as follows: // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 - for (String subtypeId: subtypes) { + for (String subtypeId: ime.second) { builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); } } + public static String buildInputMethodsSettingString( + List<Pair<String, ArrayList<String>>> allImeSettingsMap) { + final StringBuilder b = new StringBuilder(); + boolean needsSeparator = false; + for (Pair<String, ArrayList<String>> ime : allImeSettingsMap) { + if (needsSeparator) { + b.append(INPUT_METHOD_SEPARATER); + } + buildEnabledInputMethodsSettingString(b, ime); + needsSeparator = true; + } + return b.toString(); + } + + public static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList( + String enabledInputMethodsStr, + TextUtils.SimpleStringSplitter inputMethodSplitter, + TextUtils.SimpleStringSplitter subtypeSplitter) { + ArrayList<Pair<String, ArrayList<String>>> imsList = + new ArrayList<Pair<String, ArrayList<String>>>(); + if (TextUtils.isEmpty(enabledInputMethodsStr)) { + return imsList; + } + inputMethodSplitter.setString(enabledInputMethodsStr); + while (inputMethodSplitter.hasNext()) { + String nextImsStr = inputMethodSplitter.next(); + subtypeSplitter.setString(nextImsStr); + if (subtypeSplitter.hasNext()) { + ArrayList<String> subtypeHashes = new ArrayList<String>(); + // The first element is ime id. + String imeId = subtypeSplitter.next(); + while (subtypeSplitter.hasNext()) { + subtypeHashes.add(subtypeSplitter.next()); + } + imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); + } + } + return imsList; + } + public InputMethodSettings( Resources res, ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, @@ -875,27 +913,9 @@ public class InputMethodUtils { } public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { - ArrayList<Pair<String, ArrayList<String>>> imsList - = new ArrayList<Pair<String, ArrayList<String>>>(); - final String enabledInputMethodsStr = getEnabledInputMethodsStr(); - if (TextUtils.isEmpty(enabledInputMethodsStr)) { - return imsList; - } - mInputMethodSplitter.setString(enabledInputMethodsStr); - while (mInputMethodSplitter.hasNext()) { - String nextImsStr = mInputMethodSplitter.next(); - mSubtypeSplitter.setString(nextImsStr); - if (mSubtypeSplitter.hasNext()) { - ArrayList<String> subtypeHashes = new ArrayList<String>(); - // The first element is ime id. - String imeId = mSubtypeSplitter.next(); - while (mSubtypeSplitter.hasNext()) { - subtypeHashes.add(mSubtypeSplitter.next()); - } - imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); - } - } - return imsList; + return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(), + mInputMethodSplitter, + mSubtypeSplitter); } public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags new file mode 100644 index 0000000..9e178df --- /dev/null +++ b/core/java/com/android/internal/logging/EventLogTags.logtags @@ -0,0 +1,7 @@ +# See system/core/logcat/event.logtags for a description of the format of this file. + +option java_package com.android.internal.logging; + +# interaction logs +524287 sysui_view_visibility (category|1|5),(visible|1|6) +524288 sysui_action (category|1|5),(type|1|6) diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java new file mode 100644 index 0000000..e5cba84 --- /dev/null +++ b/core/java/com/android/internal/logging/MetricsConstants.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.logging; + +/** + * Constants for mestrics logs. + * + * @hide + */ +public interface MetricsConstants { + // These constants must match those in the analytic pipeline. + public static final int ACCESSIBILITY = 2; + public static final int ACCESSIBILITY_CAPTION_PROPERTIES = 3; + public static final int ACCESSIBILITY_SERVICE = 4; + public static final int ACCESSIBILITY_TOGGLE_DALTONIZER = 5; + public static final int ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE = 6; + public static final int ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION = 7; + public static final int ACCOUNT = 8; + public static final int ACCOUNTS_ACCOUNT_SYNC = 9; + public static final int ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY = 10; + public static final int ACCOUNTS_MANAGE_ACCOUNTS = 11; + public static final int APN = 12; + public static final int APN_EDITOR = 13; + public static final int APPLICATION = 16; + public static final int APPLICATIONS_APP_LAUNCH = 17; + public static final int APPLICATIONS_APP_PERMISSION = 18; + public static final int APPLICATIONS_APP_STORAGE = 19; + public static final int APPLICATIONS_INSTALLED_APP_DETAILS = 20; + public static final int APPLICATIONS_PROCESS_STATS_DETAIL = 21; + public static final int APPLICATIONS_PROCESS_STATS_MEM_DETAIL = 22; + public static final int APPLICATIONS_PROCESS_STATS_UI = 23; + public static final int APP_OPS_DETAILS = 14; + public static final int APP_OPS_SUMMARY = 15; + public static final int BLUETOOTH = 24; + public static final int BLUETOOTH_DEVICE_PICKER = 25; + public static final int BLUETOOTH_DEVICE_PROFILES = 26; + public static final int CHOOSE_LOCK_GENERIC = 27; + public static final int CHOOSE_LOCK_PASSWORD = 28; + public static final int CHOOSE_LOCK_PATTERN = 29; + public static final int CONFIRM_LOCK_PASSWORD = 30; + public static final int CONFIRM_LOCK_PATTERN = 31; + public static final int CRYPT_KEEPER = 32; + public static final int CRYPT_KEEPER_CONFIRM = 33; + public static final int DASHBOARD_SEARCH_RESULTS = 34; + public static final int DASHBOARD_SUMMARY = 35; + public static final int DATA_USAGE = 36; + public static final int DATA_USAGE_SUMMARY = 37; + public static final int DATE_TIME = 38; + public static final int DEVELOPMENT = 39; + public static final int DEVICEINFO = 40; + public static final int DEVICEINFO_IMEI_INFORMATION = 41; + public static final int DEVICEINFO_MEMORY = 42; + public static final int DEVICEINFO_SIM_STATUS = 43; + public static final int DEVICEINFO_STATUS = 44; + public static final int DEVICEINFO_USB = 45; + public static final int DISPLAY = 46; + public static final int DREAM = 47; + public static final int ENCRYPTION = 48; + public static final int FINGERPRINT = 49; + public static final int FINGERPRINT_ENROLL = 50; + public static final int FUELGAUGE_BATTERY_HISTORY_DETAIL = 51; + public static final int FUELGAUGE_BATTERY_SAVER = 52; + public static final int FUELGAUGE_POWER_USAGE_DETAIL = 53; + public static final int FUELGAUGE_POWER_USAGE_SUMMARY = 54; + public static final int HOME = 55; + public static final int ICC_LOCK = 56; + public static final int INPUTMETHOD_KEYBOARD = 58; + public static final int INPUTMETHOD_LANGUAGE = 57; + public static final int INPUTMETHOD_SPELL_CHECKERS = 59; + public static final int INPUTMETHOD_SUBTYPE_ENABLER = 60; + public static final int INPUTMETHOD_USER_DICTIONARY = 61; + public static final int INPUTMETHOD_USER_DICTIONARY_ADD_WORD = 62; + public static final int LOCATION = 63; + public static final int LOCATION_MODE = 64; + public static final int MAIN_SETTINGS = 1; + public static final int MANAGE_APPLICATIONS = 65; + public static final int MASTER_CLEAR = 66; + public static final int MASTER_CLEAR_CONFIRM = 67; + public static final int NET_DATA_USAGE_METERED = 68; + public static final int NFC_BEAM = 69; + public static final int NFC_PAYMENT = 70; + public static final int NOTIFICATION = 71; + public static final int NOTIFICATION_APP_NOTIFICATION = 72; + public static final int NOTIFICATION_OTHER_SOUND = 73; + public static final int NOTIFICATION_REDACTION = 74; + public static final int NOTIFICATION_STATION = 75; + public static final int NOTIFICATION_ZEN_MODE = 76; + public static final int OWNER_INFO = 77; + public static final int PRINT_JOB_SETTINGS = 78; + public static final int PRINT_SERVICE_SETTINGS = 79; + public static final int PRINT_SETTINGS = 80; + public static final int PRIVACY = 81; + public static final int PROXY_SELECTOR = 82; + public static final int QS_AIRPLANEMODE = 112; + public static final int QS_BLUETOOTH = 113; + public static final int QS_CAST = 114; + public static final int QS_CELLULAR = 115; + public static final int QS_COLORINVERSION = 116; + public static final int QS_DATAUSAGEDETAIL = 117; + public static final int QS_DND = 118; + public static final int QS_FLASHLIGHT = 119; + public static final int QS_HOTSPOT = 120; + public static final int QS_INTENT = 121; + public static final int QS_LOCATION = 122; + public static final int QS_PANEL = 111; + public static final int QS_ROTATIONLOCK = 123; + public static final int QS_USERDETAIL = 125; + public static final int QS_USERDETAILITE = 124; + public static final int QS_WIFI = 126; + public static final int RESET_NETWORK = 83; + public static final int RESET_NETWORK_CONFIRM = 84; + public static final int RUNNING_SERVICE_DETAILS = 85; + public static final int SCREEN_PINNING = 86; + public static final int SECURITY = 87; + public static final int SIM = 88; + public static final int TESTING = 89; + public static final int TETHER = 90; + public static final int TRUSTED_CREDENTIALS = 92; + public static final int TRUST_AGENT = 91; + public static final int TTS_ENGINE_SETTINGS = 93; + public static final int TTS_TEXT_TO_SPEECH = 94; + public static final int TYPE_UNKNOWN = 0; + public static final int USAGE_ACCESS = 95; + public static final int USER = 96; + public static final int USERS_APP_RESTRICTIONS = 97; + public static final int USER_DETAILS = 98; + public static final int VIEW_UNKNOWN = 0; + public static final int VOICE_INPUT = 99; + public static final int VPN = 100; + public static final int WALLPAPER_TYPE = 101; + public static final int WFD_WIFI_DISPLAY = 102; + public static final int WIFI = 103; + public static final int WIFI_ADVANCED = 104; + public static final int WIFI_APITEST = 107; + public static final int WIFI_CALLING = 105; + public static final int WIFI_INFO = 108; + public static final int WIFI_P2P = 109; + public static final int WIFI_SAVED_ACCESS_POINTS = 106; + public static final int WIRELESS = 110; +} diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java new file mode 100644 index 0000000..2de7394 --- /dev/null +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.logging; + + +import android.content.Context; +import android.os.Build; + +/** + * Log all the things. + * + * @hide + */ +public class MetricsLogger implements MetricsConstants { + // These constants are temporary, they should migrate to MetricsConstants. + public static final int APPLICATIONS_ADVANCED = 132; + public static final int LOCATION_SCANNING = 133; + public static final int MANAGE_APPLICATIONS_ALL = 134; + public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 135; + + public static void visible(Context context, int category) throws IllegalArgumentException { + if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { + throw new IllegalArgumentException("Must define metric category"); + } + EventLogTags.writeSysuiViewVisibility(category, 100); + } + + public static void hidden(Context context, int category) { + if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { + throw new IllegalArgumentException("Must define metric category"); + } + EventLogTags.writeSysuiViewVisibility(category, 0); + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index f9b1ca1..93dc995 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -16,18 +16,14 @@ package com.android.internal.os; -import static android.net.NetworkStats.UID_ALL; -import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; - +import android.annotation.Nullable; import android.app.ActivityManager; import android.bluetooth.BluetoothActivityEnergyInfo; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkStats; -import android.net.wifi.IWifiManager; import android.net.wifi.WifiActivityEnergyInfo; import android.net.wifi.WifiManager; import android.os.BadParcelableException; @@ -42,8 +38,6 @@ import android.os.Parcel; import android.os.ParcelFormatException; import android.os.Parcelable; import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.WorkSource; @@ -65,13 +59,14 @@ import android.util.TimeUtils; import android.util.Xml; import android.view.Display; -import com.android.internal.annotations.GuardedBy; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; +import com.android.server.NetworkManagementSocketTagger; +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -109,7 +104,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 119 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 122 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -132,6 +127,9 @@ public final class BatteryStatsImpl extends BatteryStats { static final int MSG_REPORT_POWER_CHANGE = 2; static final long DELAY_UPDATE_WAKELOCKS = 5*1000; + private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); + private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); + public interface BatteryCallback { public void batteryNeedsCpuUpdate(); public void batteryPowerChanged(boolean onBattery); @@ -160,7 +158,12 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public interface ExternalStatsSync { + void scheduleSync(); + } + public final MyHandler mHandler; + private final ExternalStatsSync mExternalSync; private BatteryCallback mCallback; @@ -307,8 +310,14 @@ public final class BatteryStatsImpl extends BatteryStats { boolean mInteractive; StopwatchTimer mInteractiveTimer; - boolean mLowPowerModeEnabled; - StopwatchTimer mLowPowerModeEnabledTimer; + boolean mPowerSaveModeEnabled; + StopwatchTimer mPowerSaveModeEnabledTimer; + + boolean mDeviceIdling; + StopwatchTimer mDeviceIdlingTimer; + + boolean mDeviceIdleModeEnabled; + StopwatchTimer mDeviceIdleModeEnabledTimer; boolean mPhoneOn; StopwatchTimer mPhoneOnTimer; @@ -324,7 +333,7 @@ public final class BatteryStatsImpl extends BatteryStats { int mPhoneSignalStrengthBin = -1; int mPhoneSignalStrengthBinRaw = -1; - final StopwatchTimer[] mPhoneSignalStrengthsTimer = + final StopwatchTimer[] mPhoneSignalStrengthsTimer = new StopwatchTimer[SignalStrength.NUM_SIGNAL_STRENGTH_BINS]; StopwatchTimer mPhoneSignalScanningTimer; @@ -411,6 +420,7 @@ public final class BatteryStatsImpl extends BatteryStats { int mMinDischargeStepLevel; final LevelStepTracker mDischargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS); final LevelStepTracker mDailyDischargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS*2); + ArrayList<PackageChange> mDailyPackageChanges; int mLastChargeStepLevel; int mMaxChargeStepLevel; @@ -438,18 +448,17 @@ public final class BatteryStatsImpl extends BatteryStats { private int mLoadedNumConnectivityChange; private int mUnpluggedNumConnectivityChange; + private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry(); + /* * Holds a SamplingTimer associated with each kernel wakelock name being tracked. */ - private final HashMap<String, SamplingTimer> mKernelWakelockStats = - new HashMap<String, SamplingTimer>(); + private final HashMap<String, SamplingTimer> mKernelWakelockStats = new HashMap<>(); public Map<String, ? extends Timer> getKernelWakelockStats() { return mKernelWakelockStats; } - private static int sKernelWakelockUpdateVersion = 0; - String mLastWakeupReason = null; long mLastWakeupUptimeMs = 0; private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>(); @@ -458,56 +467,12 @@ public final class BatteryStatsImpl extends BatteryStats { return mWakeupReasonStats; } - private static final int[] PROC_WAKELOCKS_FORMAT = new int[] { - Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name - Process.PROC_QUOTES, - Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 1: count - Process.PROC_TAB_TERM, - Process.PROC_TAB_TERM, - Process.PROC_TAB_TERM, - Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 5: totalTime - }; - - private static final int[] WAKEUP_SOURCES_FORMAT = new int[] { - Process.PROC_TAB_TERM|Process.PROC_OUT_STRING, // 0: name - Process.PROC_TAB_TERM|Process.PROC_COMBINE| - Process.PROC_OUT_LONG, // 1: count - Process.PROC_TAB_TERM|Process.PROC_COMBINE, - Process.PROC_TAB_TERM|Process.PROC_COMBINE, - Process.PROC_TAB_TERM|Process.PROC_COMBINE, - Process.PROC_TAB_TERM|Process.PROC_COMBINE, - Process.PROC_TAB_TERM|Process.PROC_COMBINE - |Process.PROC_OUT_LONG, // 6: totalTime - }; - - private final String[] mProcWakelocksName = new String[3]; - private final long[] mProcWakelocksData = new long[3]; - - /* - * Used as a buffer for reading in data from /proc/wakelocks before it is processed and added - * to mKernelWakelockStats. - */ - private final Map<String, KernelWakelockStats> mProcWakelockFileStats = - new HashMap<String, KernelWakelockStats>(); - - private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory(); - private NetworkStats mCurMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50); - private NetworkStats mLastMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50); - private NetworkStats mCurWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50); - private NetworkStats mLastWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50); - private NetworkStats mTmpNetworkStats; - private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry(); - - @GuardedBy("this") - private String[] mMobileIfaces = new String[0]; - @GuardedBy("this") - private String[] mWifiIfaces = new String[0]; - public BatteryStatsImpl() { mFile = null; mCheckinFile = null; mDailyFile = null; mHandler = null; + mExternalSync = null; clearHistoryLocked(); } @@ -517,7 +482,7 @@ public final class BatteryStatsImpl extends BatteryStats { } static class TimeBase { - private final ArrayList<TimeBaseObs> mObservers = new ArrayList<TimeBaseObs>(); + private final ArrayList<TimeBaseObs> mObservers = new ArrayList<>(); private long mUptime; private long mRealtime; @@ -1772,143 +1737,6 @@ public final class BatteryStatsImpl extends BatteryStats { return timer; } - private final Map<String, KernelWakelockStats> readKernelWakelockStats() { - - FileInputStream is; - byte[] buffer = new byte[8192]; - int len; - boolean wakeup_sources = false; - - try { - try { - is = new FileInputStream("/proc/wakelocks"); - } catch (java.io.FileNotFoundException e) { - try { - is = new FileInputStream("/d/wakeup_sources"); - wakeup_sources = true; - } catch (java.io.FileNotFoundException e2) { - return null; - } - } - - len = is.read(buffer); - is.close(); - } catch (java.io.IOException e) { - return null; - } - - if (len > 0) { - int i; - for (i=0; i<len; i++) { - if (buffer[i] == '\0') { - len = i; - break; - } - } - } - - return parseProcWakelocks(buffer, len, wakeup_sources); - } - - private final Map<String, KernelWakelockStats> parseProcWakelocks( - byte[] wlBuffer, int len, boolean wakeup_sources) { - String name; - int count; - long totalTime; - int startIndex; - int endIndex; - int numUpdatedWlNames = 0; - - // Advance past the first line. - int i; - for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++); - startIndex = endIndex = i + 1; - - synchronized(this) { - Map<String, KernelWakelockStats> m = mProcWakelockFileStats; - - sKernelWakelockUpdateVersion++; - while (endIndex < len) { - for (endIndex=startIndex; - endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0'; - endIndex++); - endIndex++; // endIndex is an exclusive upper bound. - // Don't go over the end of the buffer, Process.parseProcLine might - // write to wlBuffer[endIndex] - if (endIndex >= (len - 1) ) { - return m; - } - - String[] nameStringArray = mProcWakelocksName; - long[] wlData = mProcWakelocksData; - // Stomp out any bad characters since this is from a circular buffer - // A corruption is seen sometimes that results in the vm crashing - // This should prevent crashes and the line will probably fail to parse - for (int j = startIndex; j < endIndex; j++) { - if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?'; - } - boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex, - wakeup_sources ? WAKEUP_SOURCES_FORMAT : - PROC_WAKELOCKS_FORMAT, - nameStringArray, wlData, null); - - name = nameStringArray[0]; - count = (int) wlData[1]; - - if (wakeup_sources) { - // convert milliseconds to microseconds - totalTime = wlData[2] * 1000; - } else { - // convert nanoseconds to microseconds with rounding. - totalTime = (wlData[2] + 500) / 1000; - } - - if (parsed && name.length() > 0) { - if (!m.containsKey(name)) { - m.put(name, new KernelWakelockStats(count, totalTime, - sKernelWakelockUpdateVersion)); - numUpdatedWlNames++; - } else { - KernelWakelockStats kwlStats = m.get(name); - if (kwlStats.mVersion == sKernelWakelockUpdateVersion) { - kwlStats.mCount += count; - kwlStats.mTotalTime += totalTime; - } else { - kwlStats.mCount = count; - kwlStats.mTotalTime = totalTime; - kwlStats.mVersion = sKernelWakelockUpdateVersion; - numUpdatedWlNames++; - } - } - } - startIndex = endIndex; - } - - if (m.size() != numUpdatedWlNames) { - // Don't report old data. - Iterator<KernelWakelockStats> itr = m.values().iterator(); - while (itr.hasNext()) { - if (itr.next().mVersion != sKernelWakelockUpdateVersion) { - itr.remove(); - } - } - } - return m; - } - } - - private class KernelWakelockStats { - public int mCount; - public long mTotalTime; - public int mVersion; - - KernelWakelockStats(int count, long totalTime, int version) { - mCount = count; - mTotalTime = totalTime; - mVersion = version; - } - } - /* * Get the KernelWakelockTimer associated with name, and create a new one if one * doesn't already exist. @@ -3380,35 +3208,110 @@ public final class BatteryStatsImpl extends BatteryStats { mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime); } else { mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs); - updateNetworkActivityLocked(NET_UPDATE_MOBILE, realElapsedRealtimeMs); + updateMobileRadioStateLocked(realElapsedRealtimeMs); mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs); } } } - public void noteLowPowerMode(boolean enabled) { - if (mLowPowerModeEnabled != enabled) { + public void notePowerSaveMode(boolean enabled) { + if (mPowerSaveModeEnabled != enabled) { int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0; mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_POWER_SAVE) ^ stepState; mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState; final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); - mLowPowerModeEnabled = enabled; + mPowerSaveModeEnabled = enabled; if (enabled) { - mHistoryCur.states2 |= HistoryItem.STATE2_LOW_POWER_FLAG; - if (DEBUG_HISTORY) Slog.v(TAG, "Low power mode enabled to: " + mHistoryCur.states2 |= HistoryItem.STATE2_POWER_SAVE_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode enabled to: " + Integer.toHexString(mHistoryCur.states2)); - mLowPowerModeEnabledTimer.startRunningLocked(elapsedRealtime); + mPowerSaveModeEnabledTimer.startRunningLocked(elapsedRealtime); } else { - mHistoryCur.states2 &= ~HistoryItem.STATE2_LOW_POWER_FLAG; - if (DEBUG_HISTORY) Slog.v(TAG, "Low power mode disabled to: " + mHistoryCur.states2 &= ~HistoryItem.STATE2_POWER_SAVE_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode disabled to: " + Integer.toHexString(mHistoryCur.states2)); - mLowPowerModeEnabledTimer.stopRunningLocked(elapsedRealtime); + mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtime); } addHistoryRecordLocked(elapsedRealtime, uptime); } } + public void noteDeviceIdleModeLocked(boolean enabled, boolean fromActive, boolean fromMotion) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + boolean nowIdling = enabled; + if (mDeviceIdling && !enabled && !fromActive && !fromMotion) { + // We don't go out of general idling mode until explicitly taken out of + // device idle through going active or significant motion. + nowIdling = true; + } + if (mDeviceIdling != nowIdling) { + mDeviceIdling = nowIdling; + int stepState = nowIdling ? STEP_LEVEL_MODE_DEVICE_IDLE : 0; + mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_DEVICE_IDLE) ^ stepState; + mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_DEVICE_IDLE) | stepState; + if (enabled) { + mDeviceIdlingTimer.startRunningLocked(elapsedRealtime); + } else { + mDeviceIdlingTimer.stopRunningLocked(elapsedRealtime); + } + } + if (mDeviceIdleModeEnabled != enabled) { + mDeviceIdleModeEnabled = enabled; + if (fromMotion) { + addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SIGNIFICANT_MOTION, + "", 0); + } + if (fromActive) { + addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ACTIVE, + "", 0); + } + if (enabled) { + mHistoryCur.states2 |= HistoryItem.STATE2_DEVICE_IDLE_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode enabled to: " + + Integer.toHexString(mHistoryCur.states2)); + mDeviceIdleModeEnabledTimer.startRunningLocked(elapsedRealtime); + } else { + mHistoryCur.states2 &= ~HistoryItem.STATE2_DEVICE_IDLE_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode disabled to: " + + Integer.toHexString(mHistoryCur.states2)); + mDeviceIdleModeEnabledTimer.stopRunningLocked(elapsedRealtime); + } + addHistoryRecordLocked(elapsedRealtime, uptime); + } + } + + public void notePackageInstalledLocked(String pkgName, int versionCode) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PACKAGE_INSTALLED, + pkgName, versionCode); + PackageChange pc = new PackageChange(); + pc.mPackageName = pkgName; + pc.mUpdate = true; + pc.mVersionCode = versionCode; + addPackageChange(pc); + } + + public void notePackageUninstalledLocked(String pkgName) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PACKAGE_UNINSTALLED, + pkgName, 0); + PackageChange pc = new PackageChange(); + pc.mPackageName = pkgName; + pc.mUpdate = true; + addPackageChange(pc); + } + + private void addPackageChange(PackageChange pc) { + if (mDailyPackageChanges == null) { + mDailyPackageChanges = new ArrayList<>(); + } + mDailyPackageChanges.add(pc); + } + public void notePhoneOnLocked() { if (!mPhoneOn) { final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -3643,6 +3546,7 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); mWifiOn = true; mWifiOnTimer.startRunningLocked(elapsedRealtime); + scheduleSyncExternalStatsLocked(); } } @@ -3656,6 +3560,7 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); mWifiOn = false; mWifiOnTimer.stopRunningLocked(elapsedRealtime); + scheduleSyncExternalStatsLocked(); } } @@ -3818,6 +3723,7 @@ public final class BatteryStatsImpl extends BatteryStats { int uid = mapUid(ws.get(i)); getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime); } + scheduleSyncExternalStatsLocked(); } else { Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running"); } @@ -3856,6 +3762,7 @@ public final class BatteryStatsImpl extends BatteryStats { int uid = mapUid(ws.get(i)); getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime); } + scheduleSyncExternalStatsLocked(); } else { Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running"); } @@ -3870,6 +3777,7 @@ public final class BatteryStatsImpl extends BatteryStats { } mWifiState = wifiState; mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime); + scheduleSyncExternalStatsLocked(); } } @@ -3941,6 +3849,7 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); mBluetoothOn = true; mBluetoothOnTimer.startRunningLocked(elapsedRealtime); + scheduleSyncExternalStatsLocked(); } } @@ -3954,6 +3863,7 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); mBluetoothOn = false; mBluetoothOnTimer.stopRunningLocked(elapsedRealtime); + scheduleSyncExternalStatsLocked(); } } @@ -3980,6 +3890,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + scheduleSyncExternalStatsLocked(); } mWifiFullLockNesting++; getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime); @@ -3995,6 +3906,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + scheduleSyncExternalStatsLocked(); } getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime); } @@ -4052,6 +3964,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + scheduleSyncExternalStatsLocked(); } mWifiMulticastNesting++; getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime); @@ -4067,6 +3980,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + scheduleSyncExternalStatsLocked(); } getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime); } @@ -4174,7 +4088,8 @@ public final class BatteryStatsImpl extends BatteryStats { // During device boot, qtaguid isn't enabled until after the inital // loading of battery stats. Now that they're enabled, take our initial // snapshot for future delta calculation. - updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime()); + updateMobileRadioStateLocked(SystemClock.elapsedRealtime()); + updateWifiStateLocked(null); } @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) { @@ -4195,12 +4110,28 @@ public final class BatteryStatsImpl extends BatteryStats { return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } - @Override public long getLowPowerModeEnabledTime(long elapsedRealtimeUs, int which) { - return mLowPowerModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + @Override public long getPowerSaveModeEnabledTime(long elapsedRealtimeUs, int which) { + return mPowerSaveModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } - @Override public int getLowPowerModeEnabledCount(int which) { - return mLowPowerModeEnabledTimer.getCountLocked(which); + @Override public int getPowerSaveModeEnabledCount(int which) { + return mPowerSaveModeEnabledTimer.getCountLocked(which); + } + + @Override public long getDeviceIdleModeEnabledTime(long elapsedRealtimeUs, int which) { + return mDeviceIdleModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public int getDeviceIdleModeEnabledCount(int which) { + return mDeviceIdleModeEnabledTimer.getCountLocked(which); + } + + @Override public long getDeviceIdlingTime(long elapsedRealtimeUs, int which) { + return mDeviceIdlingTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + + @Override public int getDeviceIdlingCount(int which) { + return mDeviceIdlingTimer.getCountLocked(which); } @Override public int getNumConnectivityChange(int which) { @@ -4505,17 +4436,17 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - public Map<String, ? extends BatteryStats.Uid.Wakelock> getWakelockStats() { + public ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> getWakelockStats() { return mWakelockStats.getMap(); } @Override - public Map<String, ? extends BatteryStats.Timer> getSyncStats() { + public ArrayMap<String, ? extends BatteryStats.Timer> getSyncStats() { return mSyncStats.getMap(); } @Override - public Map<String, ? extends BatteryStats.Timer> getJobStats() { + public ArrayMap<String, ? extends BatteryStats.Timer> getJobStats() { return mJobStats.getMap(); } @@ -4525,12 +4456,12 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override - public Map<String, ? extends BatteryStats.Uid.Proc> getProcessStats() { + public ArrayMap<String, ? extends BatteryStats.Uid.Proc> getProcessStats() { return mProcessStats; } @Override - public Map<String, ? extends BatteryStats.Uid.Pkg> getPackageStats() { + public ArrayMap<String, ? extends BatteryStats.Uid.Pkg> getPackageStats() { return mPackageStats; } @@ -5848,7 +5779,7 @@ public final class BatteryStatsImpl extends BatteryStats { Slog.w(TAG, "File corrupt: too many excessive power entries " + N); return false; } - + mExcessivePower = new ArrayList<ExcessivePower>(); for (int i=0; i<N; i++) { ExcessivePower ew = new ExcessivePower(); @@ -6051,40 +5982,20 @@ public final class BatteryStatsImpl extends BatteryStats { */ public final class Pkg extends BatteryStats.Uid.Pkg implements TimeBaseObs { /** - * Number of times this package has done something that could wake up the - * device from sleep. - */ - int mWakeups; - - /** - * Number of things that could wake up the device loaded from a - * previous save. - */ - int mLoadedWakeups; - - /** - * Number of things that could wake up the device as of the - * last run. - */ - int mLastWakeups; - - /** - * Number of things that could wake up the device as of the - * last run. + * Number of times wakeup alarms have occurred for this app. */ - int mUnpluggedWakeups; + ArrayMap<String, Counter> mWakeupAlarms = new ArrayMap<>(); /** * The statics we have collected for this package's services. */ - final HashMap<String, Serv> mServiceStats = new HashMap<String, Serv>(); + final ArrayMap<String, Serv> mServiceStats = new ArrayMap<>(); Pkg() { mOnBatteryScreenOffTimeBase.add(this); } public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) { - mUnpluggedWakeups = mWakeups; } public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) { @@ -6095,10 +6006,12 @@ public final class BatteryStatsImpl extends BatteryStats { } void readFromParcelLocked(Parcel in) { - mWakeups = in.readInt(); - mLoadedWakeups = in.readInt(); - mLastWakeups = 0; - mUnpluggedWakeups = in.readInt(); + int numWA = in.readInt(); + mWakeupAlarms.clear(); + for (int i=0; i<numWA; i++) { + String tag = in.readString(); + mWakeupAlarms.put(tag, new Counter(mOnBatteryTimeBase, in)); + } int numServs = in.readInt(); mServiceStats.clear(); @@ -6112,34 +6025,39 @@ public final class BatteryStatsImpl extends BatteryStats { } void writeToParcelLocked(Parcel out) { - out.writeInt(mWakeups); - out.writeInt(mLoadedWakeups); - out.writeInt(mUnpluggedWakeups); - - out.writeInt(mServiceStats.size()); - for (Map.Entry<String, Uid.Pkg.Serv> servEntry : mServiceStats.entrySet()) { - out.writeString(servEntry.getKey()); - Uid.Pkg.Serv serv = servEntry.getValue(); - + int numWA = mWakeupAlarms.size(); + out.writeInt(numWA); + for (int i=0; i<numWA; i++) { + out.writeString(mWakeupAlarms.keyAt(i)); + mWakeupAlarms.valueAt(i).writeToParcel(out); + } + + final int NS = mServiceStats.size(); + out.writeInt(NS); + for (int i=0; i<NS; i++) { + out.writeString(mServiceStats.keyAt(i)); + Uid.Pkg.Serv serv = mServiceStats.valueAt(i); serv.writeToParcelLocked(out); } } @Override - public Map<String, ? extends BatteryStats.Uid.Pkg.Serv> getServiceStats() { - return mServiceStats; + public ArrayMap<String, ? extends BatteryStats.Counter> getWakeupAlarmStats() { + return mWakeupAlarms; } - @Override - public int getWakeups(int which) { - int val = mWakeups; - if (which == STATS_CURRENT) { - val -= mLoadedWakeups; - } else if (which == STATS_SINCE_UNPLUGGED) { - val -= mUnpluggedWakeups; + public void noteWakeupAlarmLocked(String tag) { + Counter c = mWakeupAlarms.get(tag); + if (c == null) { + c = new Counter(mOnBatteryTimeBase); + mWakeupAlarms.put(tag, c); } + c.stepAtomic(); + } - return val; + @Override + public ArrayMap<String, ? extends BatteryStats.Uid.Pkg.Serv> getServiceStats() { + return mServiceStats; } /** @@ -6381,14 +6299,6 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public BatteryStatsImpl getBatteryStats() { - return BatteryStatsImpl.this; - } - - public void incWakeupsLocked() { - mWakeups++; - } - final Serv newServiceStatsLocked() { return new Serv(); } @@ -6647,7 +6557,7 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public BatteryStatsImpl(File systemDir, Handler handler) { + public BatteryStatsImpl(File systemDir, Handler handler, ExternalStatsSync externalSync) { if (systemDir != null) { mFile = new JournaledFile(new File(systemDir, "batterystats.bin"), new File(systemDir, "batterystats.bin.tmp")); @@ -6656,14 +6566,17 @@ public final class BatteryStatsImpl extends BatteryStats { } mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin")); mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml")); + mExternalSync = externalSync; mHandler = new MyHandler(handler.getLooper()); mStartCount++; mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase); } - mInteractiveTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase); - mLowPowerModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase); + mInteractiveTimer = new StopwatchTimer(null, -10, null, mOnBatteryTimeBase); + mPowerSaveModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase); + mDeviceIdleModeEnabledTimer = new StopwatchTimer(null, -11, null, mOnBatteryTimeBase); + mDeviceIdlingTimer = new StopwatchTimer(null, -12, null, mOnBatteryTimeBase); mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, @@ -6726,6 +6639,7 @@ public final class BatteryStatsImpl extends BatteryStats { mCheckinFile = null; mDailyFile = null; mHandler = null; + mExternalSync = null; clearHistoryLocked(); readFromParcel(p); } @@ -6789,6 +6703,11 @@ public final class BatteryStatsImpl extends BatteryStats { mDailyChargeStepTracker.mNumStepDurations, mDailyChargeStepTracker.mStepDurations); } + if (mDailyPackageChanges != null) { + hasData = true; + item.mPackageChanges = mDailyPackageChanges; + mDailyPackageChanges = null; + } mDailyDischargeStepTracker.init(); mDailyChargeStepTracker.init(); updateDailyDeadlineLocked(); @@ -6839,6 +6758,21 @@ public final class BatteryStatsImpl extends BatteryStats { out.attribute(null, "end", Long.toString(dit.mEndTime)); writeDailyLevelSteps(out, "dis", dit.mDischargeSteps, sb); writeDailyLevelSteps(out, "chg", dit.mChargeSteps, sb); + if (dit.mPackageChanges != null) { + for (int j=0; j<dit.mPackageChanges.size(); j++) { + PackageChange pc = dit.mPackageChanges.get(j); + if (pc.mUpdate) { + out.startTag(null, "upd"); + out.attribute(null, "pkg", pc.mPackageName); + out.attribute(null, "ver", Integer.toString(pc.mVersionCode)); + out.endTag(null, "upd"); + } else { + out.startTag(null, "rem"); + out.attribute(null, "pkg", pc.mPackageName); + out.endTag(null, "rem"); + } + } + } out.endTag(null, "item"); } out.endTag(null, "daily-items"); @@ -6951,6 +6885,26 @@ public final class BatteryStatsImpl extends BatteryStats { readDailyItemTagDetailsLocked(parser, dit, false, "dis"); } else if (tagName.equals("chg")) { readDailyItemTagDetailsLocked(parser, dit, true, "chg"); + } else if (tagName.equals("upd")) { + if (dit.mPackageChanges == null) { + dit.mPackageChanges = new ArrayList<>(); + } + PackageChange pc = new PackageChange(); + pc.mUpdate = true; + pc.mPackageName = parser.getAttributeValue(null, "pkg"); + String verStr = parser.getAttributeValue(null, "ver"); + pc.mVersionCode = verStr != null ? Integer.parseInt(verStr) : 0; + dit.mPackageChanges.add(pc); + XmlUtils.skipCurrentTag(parser); + } else if (tagName.equals("rem")) { + if (dit.mPackageChanges == null) { + dit.mPackageChanges = new ArrayList<>(); + } + PackageChange pc = new PackageChange(); + pc.mUpdate = false; + pc.mPackageName = parser.getAttributeValue(null, "pkg"); + dit.mPackageChanges.add(pc); + XmlUtils.skipCurrentTag(parser); } else { Slog.w(TAG, "Unknown element under <item>: " + parser.getName()); @@ -7233,7 +7187,9 @@ public final class BatteryStatsImpl extends BatteryStats { mScreenBrightnessTimer[i].reset(false); } mInteractiveTimer.reset(false); - mLowPowerModeEnabledTimer.reset(false); + mPowerSaveModeEnabledTimer.reset(false); + mDeviceIdleModeEnabledTimer.reset(false); + mDeviceIdlingTimer.reset(false); mPhoneOnTimer.reset(false); mAudioOnTimer.reset(false); mVideoOnTimer.reset(false); @@ -7355,19 +7311,233 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel; } } - + public void pullPendingStateUpdatesLocked() { - updateKernelWakelocksLocked(); - updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime()); - // TODO(adamlesinski): enable when bluedroid stops deadlocking. b/19248786 - // updateBluetoothControllerActivityLocked(); - updateWifiControllerActivityLocked(); if (mOnBatteryInternal) { final boolean screenOn = mScreenState == Display.STATE_ON; updateDischargeScreenLevelsLocked(screenOn, screenOn); } } + private String[] mMobileIfaces = EmptyArray.STRING; + private String[] mWifiIfaces = EmptyArray.STRING; + + private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory(); + + private static final int NETWORK_STATS_LAST = 0; + private static final int NETWORK_STATS_NEXT = 1; + private static final int NETWORK_STATS_DELTA = 2; + + private final NetworkStats[] mMobileNetworkStats = new NetworkStats[] { + new NetworkStats(SystemClock.elapsedRealtime(), 50), + new NetworkStats(SystemClock.elapsedRealtime(), 50), + new NetworkStats(SystemClock.elapsedRealtime(), 50) + }; + + private final NetworkStats[] mWifiNetworkStats = new NetworkStats[] { + new NetworkStats(SystemClock.elapsedRealtime(), 50), + new NetworkStats(SystemClock.elapsedRealtime(), 50), + new NetworkStats(SystemClock.elapsedRealtime(), 50) + }; + + /** + * Retrieves the delta of network stats for the given network ifaces. Uses networkStatsBuffer + * as a buffer of NetworkStats objects to cycle through when computing deltas. + */ + private NetworkStats getNetworkStatsDeltaLocked(String[] ifaces, + NetworkStats[] networkStatsBuffer) + throws IOException { + if (!SystemProperties.getBoolean(NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED, + false)) { + return null; + } + + final NetworkStats stats = mNetworkStatsFactory.readNetworkStatsDetail(NetworkStats.UID_ALL, + ifaces, NetworkStats.TAG_NONE, networkStatsBuffer[NETWORK_STATS_NEXT]); + networkStatsBuffer[NETWORK_STATS_DELTA] = NetworkStats.subtract(stats, + networkStatsBuffer[NETWORK_STATS_LAST], null, null, + networkStatsBuffer[NETWORK_STATS_DELTA]); + networkStatsBuffer[NETWORK_STATS_NEXT] = networkStatsBuffer[NETWORK_STATS_LAST]; + networkStatsBuffer[NETWORK_STATS_LAST] = stats; + return networkStatsBuffer[NETWORK_STATS_DELTA]; + } + + /** + * Distribute WiFi energy info and network traffic to apps. + * @param info The energy information from the WiFi controller. + */ + public void updateWifiStateLocked(@Nullable final WifiActivityEnergyInfo info) { + final NetworkStats delta; + try { + delta = getNetworkStatsDeltaLocked(mWifiIfaces, mWifiNetworkStats); + } catch (IOException e) { + Slog.wtf(TAG, "Failed to get wifi network stats", e); + return; + } + + if (!mOnBatteryInternal) { + return; + } + + if (delta != null) { + final int size = delta.size(); + for (int i = 0; i < size; i++) { + final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); + + if (DEBUG) { + Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes + + " tx=" + entry.txBytes); + } + + if (entry.rxBytes == 0 || entry.txBytes == 0) { + continue; + } + + final Uid u = getUidStatsLocked(mapUid(entry.uid)); + u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes, + entry.rxPackets); + u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes, + entry.txPackets); + + mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( + entry.rxBytes); + mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( + entry.txBytes); + mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( + entry.rxPackets); + mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( + entry.txPackets); + } + } + + if (info != null) { + // Update WiFi controller stats. + mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked( + info.getControllerRxTimeMillis()); + mWifiActivityCounters[CONTROLLER_TX_TIME].addCountLocked( + info.getControllerTxTimeMillis()); + mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked( + info.getControllerIdleTimeMillis()); + mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked( + info.getControllerEnergyUsed()); + } + } + + /** + * Distribute Cell radio energy info and network traffic to apps. + */ + public void updateMobileRadioStateLocked(long elapsedRealtimeMs) { + final NetworkStats delta; + + try { + delta = getNetworkStatsDeltaLocked(mMobileIfaces, mMobileNetworkStats); + } catch (IOException e) { + Slog.wtf(TAG, "Failed to get mobile network stats", e); + return; + } + + if (delta == null || !mOnBatteryInternal) { + return; + } + + long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked(elapsedRealtimeMs); + long totalPackets = delta.getTotalPackets(); + + final int size = delta.size(); + for (int i = 0; i < size; i++) { + final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); + + if (entry.rxBytes == 0 || entry.txBytes == 0) continue; + + final Uid u = getUidStatsLocked(mapUid(entry.uid)); + u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes, + entry.rxPackets); + u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes, + entry.txPackets); + + if (radioTime > 0) { + // Distribute total radio active time in to this app. + long appPackets = entry.rxPackets + entry.txPackets; + long appRadioTime = (radioTime*appPackets)/totalPackets; + u.noteMobileRadioActiveTimeLocked(appRadioTime); + // Remove this app from the totals, so that we don't lose any time + // due to rounding. + radioTime -= appRadioTime; + totalPackets -= appPackets; + } + + mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked( + entry.rxBytes); + mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked( + entry.txBytes); + mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked( + entry.rxPackets); + mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked( + entry.txPackets); + } + + if (radioTime > 0) { + // Whoops, there is some radio time we can't blame on an app! + mMobileRadioActiveUnknownTime.addCountLocked(radioTime); + mMobileRadioActiveUnknownCount.addCountLocked(1); + } + } + + /** + * Distribute Bluetooth energy info and network traffic to apps. + * @param info The energy information from the bluetooth controller. + */ + public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) { + if (info != null && mOnBatteryInternal) { + mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked( + info.getControllerRxTimeMillis()); + mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked( + info.getControllerTxTimeMillis()); + mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked( + info.getControllerIdleTimeMillis()); + mBluetoothActivityCounters[CONTROLLER_ENERGY].addCountLocked( + info.getControllerEnergyUsed()); + } + } + + /** + * Read and distribute kernel wake lock use across apps. + */ + public void updateKernelWakelocksLocked() { + final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats( + mTmpWakelockStats); + if (wakelockStats == null) { + // Not crashing might make board bringup easier. + Slog.w(TAG, "Couldn't get kernel wake lock stats"); + return; + } + + for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) { + String name = ent.getKey(); + KernelWakelockStats.Entry kws = ent.getValue(); + + SamplingTimer kwlt = mKernelWakelockStats.get(name); + if (kwlt == null) { + kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase, + true /* track reported val */); + mKernelWakelockStats.put(name, kwlt); + } + kwlt.updateCurrentReportedCount(kws.mCount); + kwlt.updateCurrentReportedTotalTime(kws.mTotalTime); + kwlt.setUpdateVersion(kws.mVersion); + } + + if (wakelockStats.size() != mKernelWakelockStats.size()) { + // Set timers to stale if they didn't appear in /proc/wakelocks this time. + for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { + SamplingTimer st = ent.getValue(); + if (st.getUpdateVersion() != wakelockStats.kernelWakelockVersion) { + st.setStale(); + } + } + } + } + void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery, final int oldStatus, final int level) { boolean doWrite = false; @@ -7521,340 +7691,132 @@ public final class BatteryStatsImpl extends BatteryStats { } } + private void scheduleSyncExternalStatsLocked() { + if (mExternalSync != null) { + mExternalSync.scheduleSync(); + } + } + // This should probably be exposed in the API, though it's not critical - private static final int BATTERY_PLUGGED_NONE = 0; + public static final int BATTERY_PLUGGED_NONE = 0; - public void setBatteryState(int status, int health, int plugType, int level, + public void setBatteryStateLocked(int status, int health, int plugType, int level, int temp, int volt) { - synchronized(this) { - final boolean onBattery = plugType == BATTERY_PLUGGED_NONE; - final long uptime = SystemClock.uptimeMillis(); - final long elapsedRealtime = SystemClock.elapsedRealtime(); - if (!mHaveBatteryLevel) { - mHaveBatteryLevel = true; - // We start out assuming that the device is plugged in (not - // on battery). If our first report is now that we are indeed - // plugged in, then twiddle our state to correctly reflect that - // since we won't be going through the full setOnBattery(). - if (onBattery == mOnBattery) { - if (onBattery) { - mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; - } else { - mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; - } - } - mHistoryCur.batteryStatus = (byte)status; - mHistoryCur.batteryLevel = (byte)level; - mMaxChargeStepLevel = mMinDischargeStepLevel = - mLastChargeStepLevel = mLastDischargeStepLevel = level; - } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) { - recordDailyStatsIfNeededLocked(level >= 100 && onBattery); - } - int oldStatus = mHistoryCur.batteryStatus; - if (onBattery) { - mDischargeCurrentLevel = level; - if (!mRecordingHistory) { - mRecordingHistory = true; - startRecordingHistory(elapsedRealtime, uptime, true); - } - } else if (level < 96) { - if (!mRecordingHistory) { - mRecordingHistory = true; - startRecordingHistory(elapsedRealtime, uptime, true); - } - } - mCurrentBatteryLevel = level; - if (mDischargePlugLevel < 0) { - mDischargePlugLevel = level; - } - if (onBattery != mOnBattery) { - mHistoryCur.batteryLevel = (byte)level; - mHistoryCur.batteryStatus = (byte)status; - mHistoryCur.batteryHealth = (byte)health; - mHistoryCur.batteryPlugType = (byte)plugType; - mHistoryCur.batteryTemperature = (short)temp; - mHistoryCur.batteryVoltage = (char)volt; - setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level); - } else { - boolean changed = false; - if (mHistoryCur.batteryLevel != level) { - mHistoryCur.batteryLevel = (byte)level; - changed = true; - } - if (mHistoryCur.batteryStatus != status) { - mHistoryCur.batteryStatus = (byte)status; - changed = true; - } - if (mHistoryCur.batteryHealth != health) { - mHistoryCur.batteryHealth = (byte)health; - changed = true; - } - if (mHistoryCur.batteryPlugType != plugType) { - mHistoryCur.batteryPlugType = (byte)plugType; - changed = true; - } - if (temp >= (mHistoryCur.batteryTemperature+10) - || temp <= (mHistoryCur.batteryTemperature-10)) { - mHistoryCur.batteryTemperature = (short)temp; - changed = true; - } - if (volt > (mHistoryCur.batteryVoltage+20) - || volt < (mHistoryCur.batteryVoltage-20)) { - mHistoryCur.batteryVoltage = (char)volt; - changed = true; - } - if (changed) { - addHistoryRecordLocked(elapsedRealtime, uptime); - } - long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT) - | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT) - | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT); + final boolean onBattery = plugType == BATTERY_PLUGGED_NONE; + final long uptime = SystemClock.uptimeMillis(); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + if (!mHaveBatteryLevel) { + mHaveBatteryLevel = true; + // We start out assuming that the device is plugged in (not + // on battery). If our first report is now that we are indeed + // plugged in, then twiddle our state to correctly reflect that + // since we won't be going through the full setOnBattery(). + if (onBattery == mOnBattery) { if (onBattery) { - if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) { - mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level, - modeBits, elapsedRealtime); - mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level, - modeBits, elapsedRealtime); - mLastDischargeStepLevel = level; - mMinDischargeStepLevel = level; - mInitStepMode = mCurStepMode; - mModStepMode = 0; - } + mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; } else { - if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) { - mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel, - modeBits, elapsedRealtime); - mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel, - modeBits, elapsedRealtime); - mLastChargeStepLevel = level; - mMaxChargeStepLevel = level; - mInitStepMode = mCurStepMode; - mModStepMode = 0; - } + mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; } } - if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) { - // We don't record history while we are plugged in and fully charged. - // The next time we are unplugged, history will be cleared. - mRecordingHistory = DEBUG; + mHistoryCur.batteryStatus = (byte)status; + mHistoryCur.batteryLevel = (byte)level; + mMaxChargeStepLevel = mMinDischargeStepLevel = + mLastChargeStepLevel = mLastDischargeStepLevel = level; + } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) { + recordDailyStatsIfNeededLocked(level >= 100 && onBattery); + } + int oldStatus = mHistoryCur.batteryStatus; + if (onBattery) { + mDischargeCurrentLevel = level; + if (!mRecordingHistory) { + mRecordingHistory = true; + startRecordingHistory(elapsedRealtime, uptime, true); + } + } else if (level < 96) { + if (!mRecordingHistory) { + mRecordingHistory = true; + startRecordingHistory(elapsedRealtime, uptime, true); } } - } - - public void updateKernelWakelocksLocked() { - Map<String, KernelWakelockStats> m = readKernelWakelockStats(); - - if (m == null) { - // Not crashing might make board bringup easier. - Slog.w(TAG, "Couldn't get kernel wake lock stats"); - return; + mCurrentBatteryLevel = level; + if (mDischargePlugLevel < 0) { + mDischargePlugLevel = level; } + if (onBattery != mOnBattery) { + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.batteryStatus = (byte)status; + mHistoryCur.batteryHealth = (byte)health; + mHistoryCur.batteryPlugType = (byte)plugType; + mHistoryCur.batteryTemperature = (short)temp; + mHistoryCur.batteryVoltage = (char)volt; + setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level); + } else { + boolean changed = false; + if (mHistoryCur.batteryLevel != level) { + mHistoryCur.batteryLevel = (byte)level; + changed = true; - for (Map.Entry<String, KernelWakelockStats> ent : m.entrySet()) { - String name = ent.getKey(); - KernelWakelockStats kws = ent.getValue(); - - SamplingTimer kwlt = mKernelWakelockStats.get(name); - if (kwlt == null) { - kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase, - true /* track reported val */); - mKernelWakelockStats.put(name, kwlt); + // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record + // which will pull external stats. + scheduleSyncExternalStatsLocked(); } - kwlt.updateCurrentReportedCount(kws.mCount); - kwlt.updateCurrentReportedTotalTime(kws.mTotalTime); - kwlt.setUpdateVersion(sKernelWakelockUpdateVersion); - } - - if (m.size() != mKernelWakelockStats.size()) { - // Set timers to stale if they didn't appear in /proc/wakelocks this time. - for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { - SamplingTimer st = ent.getValue(); - if (st.getUpdateVersion() != sKernelWakelockUpdateVersion) { - st.setStale(); - } + if (mHistoryCur.batteryStatus != status) { + mHistoryCur.batteryStatus = (byte)status; + changed = true; } - } - } - - static final int NET_UPDATE_MOBILE = 1<<0; - static final int NET_UPDATE_WIFI = 1<<1; - static final int NET_UPDATE_ALL = 0xffff; - - private void updateNetworkActivityLocked(int which, long elapsedRealtimeMs) { - if (!SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) return; - - if ((which&NET_UPDATE_MOBILE) != 0 && mMobileIfaces.length > 0) { - final NetworkStats snapshot; - final NetworkStats last = mCurMobileSnapshot; - try { - snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL, - mMobileIfaces, NetworkStats.TAG_NONE, mLastMobileSnapshot); - } catch (IOException e) { - Log.wtf(TAG, "Failed to read mobile network stats", e); - return; + if (mHistoryCur.batteryHealth != health) { + mHistoryCur.batteryHealth = (byte)health; + changed = true; } - - mCurMobileSnapshot = snapshot; - mLastMobileSnapshot = last; - - if (mOnBatteryInternal) { - final NetworkStats delta = NetworkStats.subtract(snapshot, last, - null, null, mTmpNetworkStats); - mTmpNetworkStats = delta; - - long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked( - elapsedRealtimeMs); - long totalPackets = delta.getTotalPackets(); - - final int size = delta.size(); - for (int i = 0; i < size; i++) { - final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); - - if (entry.rxBytes == 0 || entry.txBytes == 0) continue; - - final Uid u = getUidStatsLocked(mapUid(entry.uid)); - u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes, - entry.rxPackets); - u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes, - entry.txPackets); - - if (radioTime > 0) { - // Distribute total radio active time in to this app. - long appPackets = entry.rxPackets + entry.txPackets; - long appRadioTime = (radioTime*appPackets)/totalPackets; - u.noteMobileRadioActiveTimeLocked(appRadioTime); - // Remove this app from the totals, so that we don't lose any time - // due to rounding. - radioTime -= appRadioTime; - totalPackets -= appPackets; - } - - mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked( - entry.rxBytes); - mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked( - entry.txBytes); - mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked( - entry.rxPackets); - mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked( - entry.txPackets); - } - - if (radioTime > 0) { - // Whoops, there is some radio time we can't blame on an app! - mMobileRadioActiveUnknownTime.addCountLocked(radioTime); - mMobileRadioActiveUnknownCount.addCountLocked(1); - } + if (mHistoryCur.batteryPlugType != plugType) { + mHistoryCur.batteryPlugType = (byte)plugType; + changed = true; } - } - - if ((which&NET_UPDATE_WIFI) != 0 && mWifiIfaces.length > 0) { - final NetworkStats snapshot; - final NetworkStats last = mCurWifiSnapshot; - try { - snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL, - mWifiIfaces, NetworkStats.TAG_NONE, mLastWifiSnapshot); - } catch (IOException e) { - Log.wtf(TAG, "Failed to read wifi network stats", e); - return; + if (temp >= (mHistoryCur.batteryTemperature+10) + || temp <= (mHistoryCur.batteryTemperature-10)) { + mHistoryCur.batteryTemperature = (short)temp; + changed = true; } - - mCurWifiSnapshot = snapshot; - mLastWifiSnapshot = last; - - if (mOnBatteryInternal) { - final NetworkStats delta = NetworkStats.subtract(snapshot, last, - null, null, mTmpNetworkStats); - mTmpNetworkStats = delta; - - final int size = delta.size(); - for (int i = 0; i < size; i++) { - final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry); - - if (DEBUG) { - final NetworkStats.Entry cur = snapshot.getValues(i, null); - Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes - + " tx=" + entry.txBytes + ", cur rx=" + cur.rxBytes - + " tx=" + cur.txBytes); - } - - if (entry.rxBytes == 0 || entry.txBytes == 0) continue; - - final Uid u = getUidStatsLocked(mapUid(entry.uid)); - u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes, - entry.rxPackets); - u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes, - entry.txPackets); - - mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( - entry.rxBytes); - mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( - entry.txBytes); - mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( - entry.rxPackets); - mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( - entry.txPackets); + if (volt > (mHistoryCur.batteryVoltage+20) + || volt < (mHistoryCur.batteryVoltage-20)) { + mHistoryCur.batteryVoltage = (char)volt; + changed = true; + } + if (changed) { + addHistoryRecordLocked(elapsedRealtime, uptime); + } + long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT) + | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT) + | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT); + if (onBattery) { + if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) { + mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level, + modeBits, elapsedRealtime); + mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level, + modeBits, elapsedRealtime); + mLastDischargeStepLevel = level; + mMinDischargeStepLevel = level; + mInitStepMode = mCurStepMode; + mModStepMode = 0; + } + } else { + if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) { + mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel, + modeBits, elapsedRealtime); + mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel, + modeBits, elapsedRealtime); + mLastChargeStepLevel = level; + mMaxChargeStepLevel = level; + mInitStepMode = mCurStepMode; + mModStepMode = 0; } } } - } - - private void updateBluetoothControllerActivityLocked() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter == null) { - return; - } - - // We read the data even if we are not on battery. Each read clears - // the previous data, so we must always read to make sure the - // data is for the current interval. - BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo( - BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED); - if (info == null || !info.isValid() || !mOnBatteryInternal) { - // Bad info or we are not on battery. - return; + if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) { + // We don't record history while we are plugged in and fully charged. + // The next time we are unplugged, history will be cleared. + mRecordingHistory = DEBUG; } - - mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked( - info.getControllerRxTimeMillis()); - mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked( - info.getControllerTxTimeMillis()); - mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked( - info.getControllerIdleTimeMillis()); - mBluetoothActivityCounters[CONTROLLER_ENERGY].addCountLocked( - info.getControllerEnergyUsed()); - } - - private void updateWifiControllerActivityLocked() { - IWifiManager wifiManager = IWifiManager.Stub.asInterface( - ServiceManager.getService(Context.WIFI_SERVICE)); - if (wifiManager == null) { - return; - } - - WifiActivityEnergyInfo info; - try { - // We read the data even if we are not on battery. Each read clears - // the previous data, so we must always read to make sure the - // data is for the current interval. - info = wifiManager.reportActivityInfo(); - } catch (RemoteException e) { - // Nothing to report, WiFi is dead. - return; - } - - if (info == null || !info.isValid() || !mOnBatteryInternal) { - // Bad info or we are not on battery. - return; - } - - mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked( - info.getControllerRxTimeMillis()); - mWifiActivityCounters[CONTROLLER_TX_TIME].addCountLocked( - info.getControllerTxTimeMillis()); - mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked( - info.getControllerIdleTimeMillis()); - mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked( - info.getControllerEnergyUsed()); } public long getAwakeTimeBattery() { @@ -8020,6 +7982,11 @@ public final class BatteryStatsImpl extends BatteryStats { return mDailyChargeStepTracker; } + @Override + public ArrayList<PackageChange> getDailyPackageChanges() { + return mDailyPackageChanges; + } + long getBatteryUptimeLocked() { return mOnBatteryTimeBase.getUptime(SystemClock.uptimeMillis() * 1000); } @@ -8520,6 +8487,20 @@ public final class BatteryStatsImpl extends BatteryStats { mChargeStepTracker.readFromParcel(in); mDailyDischargeStepTracker.readFromParcel(in); mDailyChargeStepTracker.readFromParcel(in); + int NPKG = in.readInt(); + if (NPKG > 0) { + mDailyPackageChanges = new ArrayList<>(NPKG); + while (NPKG > 0) { + NPKG--; + PackageChange pc = new PackageChange(); + pc.mPackageName = in.readString(); + pc.mUpdate = in.readInt() != 0; + pc.mVersionCode = in.readInt(); + mDailyPackageChanges.add(pc); + } + } else { + mDailyPackageChanges = null; + } mDailyStartTime = in.readLong(); mNextMinDailyDeadline = in.readLong(); mNextMaxDailyDeadline = in.readLong(); @@ -8534,7 +8515,9 @@ public final class BatteryStatsImpl extends BatteryStats { mInteractive = false; mInteractiveTimer.readSummaryFromParcelLocked(in); mPhoneOn = false; - mLowPowerModeEnabledTimer.readSummaryFromParcelLocked(in); + mPowerSaveModeEnabledTimer.readSummaryFromParcelLocked(in); + mDeviceIdleModeEnabledTimer.readSummaryFromParcelLocked(in); + mDeviceIdlingTimer.readSummaryFromParcelLocked(in); mPhoneOnTimer.readSummaryFromParcelLocked(in); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { mPhoneSignalStrengthsTimer[i].readSummaryFromParcelLocked(in); @@ -8769,7 +8752,18 @@ public final class BatteryStatsImpl extends BatteryStats { for (int ip = 0; ip < NP; ip++) { String pkgName = in.readString(); Uid.Pkg p = u.getPackageStatsLocked(pkgName); - p.mWakeups = p.mLoadedWakeups = in.readInt(); + final int NWA = in.readInt(); + if (NWA > 1000) { + Slog.w(TAG, "File corrupt: too many wakeup alarms " + NWA); + return; + } + p.mWakeupAlarms.clear(); + for (int iwa=0; iwa<NWA; iwa++) { + String tag = in.readString(); + Counter c = new Counter(mOnBatteryTimeBase); + c.readSummaryFromParcelLocked(in); + p.mWakeupAlarms.put(tag, c); + } NS = in.readInt(); if (NS > 1000) { Slog.w(TAG, "File corrupt: too many services " + NS); @@ -8826,6 +8820,18 @@ public final class BatteryStatsImpl extends BatteryStats { mChargeStepTracker.writeToParcel(out); mDailyDischargeStepTracker.writeToParcel(out); mDailyChargeStepTracker.writeToParcel(out); + if (mDailyPackageChanges != null) { + final int NPKG = mDailyPackageChanges.size(); + out.writeInt(NPKG); + for (int i=0; i<NPKG; i++) { + PackageChange pc = mDailyPackageChanges.get(i); + out.writeString(pc.mPackageName); + out.writeInt(pc.mUpdate ? 1 : 0); + out.writeInt(pc.mVersionCode); + } + } else { + out.writeInt(0); + } out.writeLong(mDailyStartTime); out.writeLong(mNextMinDailyDeadline); out.writeLong(mNextMaxDailyDeadline); @@ -8835,7 +8841,9 @@ public final class BatteryStatsImpl extends BatteryStats { mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } mInteractiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); - mLowPowerModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mPowerSaveModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mDeviceIdleModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mDeviceIdlingTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); @@ -9080,20 +9088,22 @@ public final class BatteryStatsImpl extends BatteryStats { : u.mPackageStats.entrySet()) { out.writeString(ent.getKey()); Uid.Pkg ps = ent.getValue(); - out.writeInt(ps.mWakeups); + final int NWA = ps.mWakeupAlarms.size(); + out.writeInt(NWA); + for (int iwa=0; iwa<NWA; iwa++) { + out.writeString(ps.mWakeupAlarms.keyAt(iwa)); + ps.mWakeupAlarms.valueAt(iwa).writeSummaryFromParcelLocked(out); + } NS = ps.mServiceStats.size(); out.writeInt(NS); - if (NS > 0) { - for (Map.Entry<String, BatteryStatsImpl.Uid.Pkg.Serv> sent - : ps.mServiceStats.entrySet()) { - out.writeString(sent.getKey()); - BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue(); - long time = ss.getStartTimeToNowLocked( - mOnBatteryTimeBase.getUptime(NOW_SYS)); - out.writeLong(time); - out.writeInt(ss.mStarts); - out.writeInt(ss.mLaunches); - } + for (int is=0; is<NS; is++) { + out.writeString(ps.mServiceStats.keyAt(is)); + BatteryStatsImpl.Uid.Pkg.Serv ss = ps.mServiceStats.valueAt(is); + long time = ss.getStartTimeToNowLocked( + mOnBatteryTimeBase.getUptime(NOW_SYS)); + out.writeLong(time); + out.writeInt(ss.mStarts); + out.writeInt(ss.mLaunches); } } } @@ -9132,9 +9142,11 @@ public final class BatteryStatsImpl extends BatteryStats { in); } mInteractive = false; - mInteractiveTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase, in); + mInteractiveTimer = new StopwatchTimer(null, -10, null, mOnBatteryTimeBase, in); mPhoneOn = false; - mLowPowerModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in); + mPowerSaveModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in); + mDeviceIdleModeEnabledTimer = new StopwatchTimer(null, -11, null, mOnBatteryTimeBase, in); + mDeviceIdlingTimer = new StopwatchTimer(null, -12, null, mOnBatteryTimeBase, in); mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase, in); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, @@ -9299,7 +9311,9 @@ public final class BatteryStatsImpl extends BatteryStats { mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime); } mInteractiveTimer.writeToParcel(out, uSecRealtime); - mLowPowerModeEnabledTimer.writeToParcel(out, uSecRealtime); + mPowerSaveModeEnabledTimer.writeToParcel(out, uSecRealtime); + mDeviceIdleModeEnabledTimer.writeToParcel(out, uSecRealtime); + mDeviceIdlingTimer.writeToParcel(out, uSecRealtime); mPhoneOnTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime); @@ -9436,8 +9450,12 @@ public final class BatteryStatsImpl extends BatteryStats { } pr.println("*** Interactive timer:"); mInteractiveTimer.logState(pr, " "); - pr.println("*** Low power mode timer:"); - mLowPowerModeEnabledTimer.logState(pr, " "); + pr.println("*** Power save mode timer:"); + mPowerSaveModeEnabledTimer.logState(pr, " "); + pr.println("*** Device idle mode timer:"); + mDeviceIdleModeEnabledTimer.logState(pr, " "); + pr.println("*** Device idling timer:"); + mDeviceIdlingTimer.logState(pr, " "); pr.println("*** Phone timer:"); mPhoneOnTimer.logState(pr, " "); for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java new file mode 100644 index 0000000..768d586 --- /dev/null +++ b/core/java/com/android/internal/os/KernelWakelockReader.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.os; + +import android.os.Process; +import android.util.Slog; + +import java.io.FileInputStream; +import java.util.Iterator; + +/** + * Reads and parses wakelock stats from the kernel (/proc/wakelocks). + */ +public class KernelWakelockReader { + private static final String TAG = "KernelWakelockReader"; + private static int sKernelWakelockUpdateVersion = 0; + private static final String sWakelockFile = "/proc/wakelocks"; + private static final String sWakeupSourceFile = "/d/wakeup_sources"; + + private static final int[] PROC_WAKELOCKS_FORMAT = new int[] { + Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name + Process.PROC_QUOTES, + Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 1: count + Process.PROC_TAB_TERM, + Process.PROC_TAB_TERM, + Process.PROC_TAB_TERM, + Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 5: totalTime + }; + + private static final int[] WAKEUP_SOURCES_FORMAT = new int[] { + Process.PROC_TAB_TERM|Process.PROC_OUT_STRING, // 0: name + Process.PROC_TAB_TERM|Process.PROC_COMBINE| + Process.PROC_OUT_LONG, // 1: count + Process.PROC_TAB_TERM|Process.PROC_COMBINE, + Process.PROC_TAB_TERM|Process.PROC_COMBINE, + Process.PROC_TAB_TERM|Process.PROC_COMBINE, + Process.PROC_TAB_TERM|Process.PROC_COMBINE, + Process.PROC_TAB_TERM|Process.PROC_COMBINE + |Process.PROC_OUT_LONG, // 6: totalTime + }; + + private final String[] mProcWakelocksName = new String[3]; + private final long[] mProcWakelocksData = new long[3]; + + /** + * Reads kernel wakelock stats and updates the staleStats with the new information. + * @param staleStats Existing object to update. + * @return the updated data. + */ + public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) { + byte[] buffer = new byte[32*1024]; + int len; + boolean wakeup_sources; + + try { + FileInputStream is; + try { + is = new FileInputStream(sWakeupSourceFile); + wakeup_sources = true; + } catch (java.io.FileNotFoundException e) { + try { + is = new FileInputStream(sWakelockFile); + wakeup_sources = false; + } catch (java.io.FileNotFoundException e2) { + return null; + } + } + + len = is.read(buffer); + is.close(); + } catch (java.io.IOException e) { + return null; + } + + if (len > 0) { + if (len >= buffer.length) { + Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length); + } + int i; + for (i=0; i<len; i++) { + if (buffer[i] == '\0') { + len = i; + break; + } + } + } + return parseProcWakelocks(buffer, len, wakeup_sources, staleStats); + } + + /** + * Reads the wakelocks and updates the staleStats with the new information. + */ + private KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources, + final KernelWakelockStats staleStats) { + String name; + int count; + long totalTime; + int startIndex; + int endIndex; + int numUpdatedWlNames = 0; + + // Advance past the first line. + int i; + for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++); + startIndex = endIndex = i + 1; + + synchronized(this) { + sKernelWakelockUpdateVersion++; + while (endIndex < len) { + for (endIndex=startIndex; + endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0'; + endIndex++); + endIndex++; // endIndex is an exclusive upper bound. + // Don't go over the end of the buffer, Process.parseProcLine might + // write to wlBuffer[endIndex] + if (endIndex >= (len - 1) ) { + return staleStats; + } + + String[] nameStringArray = mProcWakelocksName; + long[] wlData = mProcWakelocksData; + // Stomp out any bad characters since this is from a circular buffer + // A corruption is seen sometimes that results in the vm crashing + // This should prevent crashes and the line will probably fail to parse + for (int j = startIndex; j < endIndex; j++) { + if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?'; + } + boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex, + wakeup_sources ? WAKEUP_SOURCES_FORMAT : + PROC_WAKELOCKS_FORMAT, + nameStringArray, wlData, null); + + name = nameStringArray[0]; + count = (int) wlData[1]; + + if (wakeup_sources) { + // convert milliseconds to microseconds + totalTime = wlData[2] * 1000; + } else { + // convert nanoseconds to microseconds with rounding. + totalTime = (wlData[2] + 500) / 1000; + } + + if (parsed && name.length() > 0) { + if (!staleStats.containsKey(name)) { + staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime, + sKernelWakelockUpdateVersion)); + numUpdatedWlNames++; + } else { + KernelWakelockStats.Entry kwlStats = staleStats.get(name); + if (kwlStats.mVersion == sKernelWakelockUpdateVersion) { + kwlStats.mCount += count; + kwlStats.mTotalTime += totalTime; + } else { + kwlStats.mCount = count; + kwlStats.mTotalTime = totalTime; + kwlStats.mVersion = sKernelWakelockUpdateVersion; + numUpdatedWlNames++; + } + } + } + startIndex = endIndex; + } + + if (staleStats.size() != numUpdatedWlNames) { + // Don't report old data. + Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator(); + while (itr.hasNext()) { + if (itr.next().mVersion != sKernelWakelockUpdateVersion) { + itr.remove(); + } + } + } + + staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion; + return staleStats; + } + } +} diff --git a/core/java/com/android/internal/os/KernelWakelockStats.java b/core/java/com/android/internal/os/KernelWakelockStats.java new file mode 100644 index 0000000..144ea00 --- /dev/null +++ b/core/java/com/android/internal/os/KernelWakelockStats.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.os; + +import java.util.HashMap; + +/** + * Kernel wakelock stats object. + */ +public class KernelWakelockStats extends HashMap<String, KernelWakelockStats.Entry> { + public static class Entry { + public int mCount; + public long mTotalTime; + public int mVersion; + + Entry(int count, long totalTime, int version) { + mCount = count; + mTotalTime = totalTime; + mVersion = version; + } + } + + int kernelWakelockVersion; +} diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index fced092..8674a21 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -34,10 +34,13 @@ public final class Zygote { public static final int DEBUG_ENABLE_CHECKJNI = 1 << 1; /** enable Java programming language "assert" statements */ public static final int DEBUG_ENABLE_ASSERT = 1 << 2; - /** disable the JIT compiler */ + /** disable the AOT compiler and JIT */ public static final int DEBUG_ENABLE_SAFEMODE = 1 << 3; /** Enable logging of third-party JNI activity. */ public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4; + /** enable the JIT compiler */ + public static final int DEBUG_ENABLE_JIT = 1 << 5; + /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = 0; diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 7acd8f5..4d405b2 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -321,7 +321,7 @@ class ZygoteConnection { /** * From --enable-debugger, --enable-checkjni, --enable-assert, - * --enable-safemode, and --enable-jni-logging. + * --enable-safemode, --enable-jit, and --enable-jni-logging. */ int debugFlags; @@ -431,6 +431,8 @@ class ZygoteConnection { debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE; } else if (arg.equals("--enable-checkjni")) { debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; + } else if (arg.equals("--enable-jit")) { + debugFlags |= Zygote.DEBUG_ENABLE_JIT; } else if (arg.equals("--enable-jni-logging")) { debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; } else if (arg.equals("--enable-assert")) { diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index d9ebc25..a106f48 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -52,6 +52,7 @@ public class Protocol { public static final int BASE_WIFI_RTT_SERVICE = 0x00027300; public static final int BASE_WIFI_PASSPOINT_MANAGER = 0x00028000; public static final int BASE_WIFI_PASSPOINT_SERVICE = 0x00028100; + public static final int BASE_WIFI_LOGGER = 0x00028300; public static final int BASE_DHCP = 0x00030000; public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; diff --git a/core/java/com/android/internal/util/ScreenShapeHelper.java b/core/java/com/android/internal/util/ScreenShapeHelper.java new file mode 100644 index 0000000..1bcc7a0 --- /dev/null +++ b/core/java/com/android/internal/util/ScreenShapeHelper.java @@ -0,0 +1,48 @@ +package com.android.internal.util; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.Build; +import android.os.SystemProperties; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.ViewRootImpl; + +import com.android.internal.R; + +/** + * @hide + */ +public class ScreenShapeHelper { + private static final boolean IS_EMULATOR = Build.HARDWARE.contains("goldfish"); + + /** + * Return the bottom pixel window outset of a window given its style attributes. + * @param displayMetrics Display metrics of the current device + * @param windowStyle Window style attributes for the window. + * @return An outset dimension in pixels or 0 if no outset should be applied. + */ + public static int getWindowOutsetBottomPx(DisplayMetrics displayMetrics, + TypedArray windowStyle) { + if (IS_EMULATOR) { + return SystemProperties.getInt(ViewRootImpl.PROPERTY_EMULATOR_WIN_OUTSET_BOTTOM_PX, 0); + } else if (windowStyle.hasValue(R.styleable.Window_windowOutsetBottom)) { + TypedValue outsetBottom = new TypedValue(); + windowStyle.getValue(R.styleable.Window_windowOutsetBottom, outsetBottom); + return (int) outsetBottom.getDimension(displayMetrics); + } + return 0; + } + + /** + * Get whether a device has has a round screen. + */ + public static boolean getWindowIsRound(Resources resources) { + if (IS_EMULATOR) { + return SystemProperties.getBoolean(ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false); + } else { + return resources.getBoolean( + com.android.internal.R.bool.config_windowIsRound); + } + } +} diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java index c69d14f..daf745f 100644 --- a/core/java/com/android/internal/util/UserIcons.java +++ b/core/java/com/android/internal/util/UserIcons.java @@ -70,8 +70,8 @@ public class UserIcons { // Return colored icon instead colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length]; } - Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle).mutate(); - icon.setColorFilter(Resources.getSystem().getColor(colorResId), Mode.SRC_IN); + Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle, null).mutate(); + icon.setColorFilter(Resources.getSystem().getColor(colorResId, null), Mode.SRC_IN); icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); return icon; } diff --git a/core/java/com/android/internal/view/ActionModeWrapper.java b/core/java/com/android/internal/view/ActionModeWrapper.java deleted file mode 100644 index d98617d..0000000 --- a/core/java/com/android/internal/view/ActionModeWrapper.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.view; - -import android.content.Context; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; - -import com.android.internal.view.menu.MenuBuilder; - -/** - * ActionMode implementation that wraps several actions modes and creates them on the fly depending - * on the ActionMode type chosen by the client. - */ -public class ActionModeWrapper extends ActionMode { - - /** - * Interface to defer the ActionMode creation until the type is chosen. - */ - public interface ActionModeProvider { - /** - * Create the desired ActionMode, that will immediately be used as the current active mode - * in the decorator. - * - * @param callback The {@link ActionMode.Callback} to be used. - * @param menuBuilder The {@link MenuBuilder} that should be used by the created - * {@link ActionMode}. This will already have been populated. - * @return A new {@link ActionMode} ready to be used that uses menuBuilder as its menu. - */ - ActionMode createActionMode(ActionMode.Callback callback, MenuBuilder menuBuilder); - } - - private ActionMode mActionMode; - private final Context mContext; - private MenuBuilder mMenu; - private final ActionMode.Callback mCallback; - private boolean mTypeLocked = false; - - private CharSequence mTitle; - private CharSequence mSubtitle; - private View mCustomView; - - private final ActionModeProvider mActionModeProvider; - - public ActionModeWrapper( - Context context, ActionMode.Callback callback, ActionModeProvider actionModeProvider) { - mContext = context; - mMenu = new MenuBuilder(context).setDefaultShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM); - mCallback = callback; - mActionModeProvider = actionModeProvider; - } - - @Override - public void setTitle(CharSequence title) { - if (mActionMode != null) { - mActionMode.setTitle(title); - } else { - mTitle = title; - } - } - - @Override - public void setTitle(int resId) { - if (mActionMode != null) { - mActionMode.setTitle(resId); - } else { - mTitle = resId != 0 ? mContext.getString(resId) : null; - } - } - - @Override - public void setSubtitle(CharSequence subtitle) { - if (mActionMode != null) { - mActionMode.setSubtitle(subtitle); - } else { - mSubtitle = subtitle; - } - } - - @Override - public void setSubtitle(int resId) { - if (mActionMode != null) { - mActionMode.setSubtitle(resId); - } else { - mSubtitle = resId != 0 ? mContext.getString(resId) : null; - } - } - - @Override - public void setCustomView(View view) { - if (mActionMode != null) { - mActionMode.setCustomView(view); - } else { - mCustomView = view; - } - } - - public ActionMode getWrappedActionMode() { - return mActionMode; - } - - /** - * Set the current type as final and create the necessary ActionMode. After this call, any - * changes to the ActionMode type will be ignored. - */ - public void lockType() { - mTypeLocked = true; - switch (getType()) { - case ActionMode.TYPE_PRIMARY: - default: - mActionMode = mActionModeProvider.createActionMode(mCallback, mMenu); - break; - case ActionMode.TYPE_FLOATING: - // Not implemented yet. - break; - } - - if (mActionMode == null) { - return; - } - - mActionMode.setTitle(mTitle); - mActionMode.setSubtitle(mSubtitle); - if (mCustomView != null) { - mActionMode.setCustomView(mCustomView); - } - - mTitle = null; - mSubtitle = null; - mCustomView = null; - } - - @Override - public void setType(int type) { - if (!mTypeLocked) { - super.setType(type); - } else { - throw new IllegalStateException( - "You can't change the ActionMode's type after onCreateActionMode."); - } - } - - @Override - public void invalidate() { - if (mActionMode != null) { - mActionMode.invalidate(); - } - } - - @Override - public void finish() { - if (mActionMode != null) { - mActionMode.finish(); - } else { - mCallback.onDestroyActionMode(this); - } - } - - @Override - public Menu getMenu() { - return mMenu; - } - - @Override - public CharSequence getTitle() { - if (mActionMode != null) { - return mActionMode.getTitle(); - } - return mTitle; - } - - @Override - public CharSequence getSubtitle() { - if (mActionMode != null) { - return mActionMode.getSubtitle(); - } - return mSubtitle; - } - - @Override - public View getCustomView() { - if (mActionMode != null) { - return mActionMode.getCustomView(); - } - return mCustomView; - } - - @Override - public MenuInflater getMenuInflater() { - return new MenuInflater(mContext); - } - -} diff --git a/core/java/com/android/internal/widget/AccessibleDateAnimator.java b/core/java/com/android/internal/widget/AccessibleDateAnimator.java deleted file mode 100644 index f97a5d1..0000000 --- a/core/java/com/android/internal/widget/AccessibleDateAnimator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.text.format.DateUtils; -import android.util.AttributeSet; -import android.view.accessibility.AccessibilityEvent; -import android.widget.ViewAnimator; - -/** - * @hide - */ -public class AccessibleDateAnimator extends ViewAnimator { - private long mDateMillis; - - public AccessibleDateAnimator(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void setDateMillis(long dateMillis) { - mDateMillis = dateMillis; - } - - /** - * Announce the currently-selected date when launched. - */ - @Override - public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - // Clear the event's current text so that only the current date will be spoken. - event.getText().clear(); - int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_WEEKDAY; - - String dateString = DateUtils.formatDateTime(getContext(), mDateMillis, flags); - event.getText().add(dateString); - return true; - } - return super.dispatchPopulateAccessibilityEventInternal(event); - } -} diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java new file mode 100644 index 0000000..be9945d --- /dev/null +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.PopupWindow; + +import com.android.internal.R; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * A floating toolbar for showing contextual menu items. + * This view shows as many menu item buttons as can fit in the horizontal toolbar and the + * the remaining menu items in a vertical overflow view when the overflow button is clicked. + * The horizontal toolbar morphs into the vertical overflow view. + */ +public final class FloatingToolbar { + + private static final MenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER = + new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return false; + } + }; + + private final Context mContext; + private final FloatingToolbarPopup mPopup; + private final ViewGroup mMenuItemButtonsContainer; + private final View.OnClickListener mMenuItemButtonOnClickListener = + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v.getTag() instanceof MenuItem) { + mMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag()); + mPopup.dismiss(); + } + } + }; + + private final Rect mContentRect = new Rect(); + private final Point mCoordinates = new Point(); + + private Menu mMenu; + private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>(); + private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER; + private View mOpenOverflowButton; + + private int mSuggestedWidth; + + /** + * Initializes a floating toolbar. + */ + public FloatingToolbar(Context context, Window window) { + mContext = Preconditions.checkNotNull(context); + mPopup = new FloatingToolbarPopup(Preconditions.checkNotNull(window.getDecorView())); + mMenuItemButtonsContainer = createMenuButtonsContainer(context); + } + + /** + * Sets the menu to be shown in this floating toolbar. + * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the + * toolbar. + */ + public FloatingToolbar setMenu(Menu menu) { + mMenu = Preconditions.checkNotNull(menu); + return this; + } + + /** + * Sets the custom listener for invocation of menu items in this floating + * toolbar. + */ + public FloatingToolbar setOnMenuItemClickListener( + MenuItem.OnMenuItemClickListener menuItemClickListener) { + if (menuItemClickListener != null) { + mMenuItemClickListener = menuItemClickListener; + } else { + mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER; + } + return this; + } + + /** + * Sets the content rectangle. This is the area of the interesting content that this toolbar + * should avoid obstructing. + * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the + * toolbar. + */ + public FloatingToolbar setContentRect(Rect rect) { + mContentRect.set(Preconditions.checkNotNull(rect)); + return this; + } + + /** + * Sets the suggested width of this floating toolbar. + * The actual width will be about this size but there are no guarantees that it will be exactly + * the suggested width. + * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the + * toolbar. + */ + public FloatingToolbar setSuggestedWidth(int suggestedWidth) { + mSuggestedWidth = suggestedWidth; + return this; + } + + /** + * Shows this floating toolbar. + */ + public FloatingToolbar show() { + List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu); + if (hasContentChanged(menuItems) || hasWidthChanged()) { + mPopup.dismiss(); + layoutMenuItemButtons(menuItems); + mShowingTitles = getMenuItemTitles(menuItems); + } + refreshCoordinates(); + mPopup.updateCoordinates(mCoordinates.x, mCoordinates.y); + if (!mPopup.isShowing()) { + mPopup.show(mCoordinates.x, mCoordinates.y); + } + return this; + } + + /** + * Updates this floating toolbar to reflect recent position and view updates. + * NOTE: This method is a no-op if the toolbar isn't showing. + */ + public FloatingToolbar updateLayout() { + if (mPopup.isShowing()) { + // show() performs all the logic we need here. + show(); + } + return this; + } + + /** + * Dismisses this floating toolbar. + */ + public void dismiss() { + mPopup.dismiss(); + } + + /** + * Returns {@code true} if this popup is currently showing. {@code false} otherwise. + */ + public boolean isShowing() { + return mPopup.isShowing(); + } + + /** + * Refreshes {@link #mCoordinates} with values based on {@link #mContentRect}. + */ + private void refreshCoordinates() { + int popupWidth = mPopup.getWidth(); + int popupHeight = mPopup.getHeight(); + if (!mPopup.isShowing()) { + // Popup isn't yet shown, get estimated size from the menu item buttons container. + mMenuItemButtonsContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + popupWidth = mMenuItemButtonsContainer.getMeasuredWidth(); + popupHeight = mMenuItemButtonsContainer.getMeasuredHeight(); + } + int x = mContentRect.centerX() - popupWidth / 2; + int y; + if (shouldDisplayAtTopOfContent()) { + y = mContentRect.top - popupHeight; + } else { + y = mContentRect.bottom; + } + mCoordinates.set(x, y); + } + + /** + * Returns true if this floating toolbar's menu items have been reordered or changed. + */ + private boolean hasContentChanged(List<MenuItem> menuItems) { + return !mShowingTitles.equals(getMenuItemTitles(menuItems)); + } + + /** + * Returns true if there is a significant change in width of the toolbar. + */ + private boolean hasWidthChanged() { + int actualWidth = mPopup.getWidth(); + int difference = Math.abs(actualWidth - mSuggestedWidth); + return difference > (actualWidth * 0.2); + } + + /** + * Returns true if the preferred positioning of the toolbar is above the content rect. + */ + private boolean shouldDisplayAtTopOfContent() { + return mContentRect.top - getMinimumOverflowHeight(mContext) > 0; + } + + /** + * Returns the visible and enabled menu items in the specified menu. + * This method is recursive. + */ + private List<MenuItem> getVisibleAndEnabledMenuItems(Menu menu) { + List<MenuItem> menuItems = new ArrayList<MenuItem>(); + for (int i = 0; (menu != null) && (i < menu.size()); i++) { + MenuItem menuItem = menu.getItem(i); + if (menuItem.isVisible() && menuItem.isEnabled()) { + Menu subMenu = menuItem.getSubMenu(); + if (subMenu != null) { + menuItems.addAll(getVisibleAndEnabledMenuItems(subMenu)); + } else { + menuItems.add(menuItem); + } + } + } + return menuItems; + } + + private List<CharSequence> getMenuItemTitles(List<MenuItem> menuItems) { + List<CharSequence> titles = new ArrayList<CharSequence>(); + for (MenuItem menuItem : menuItems) { + titles.add(menuItem.getTitle()); + } + return titles; + } + + private void layoutMenuItemButtons(List<MenuItem> menuItems) { + final int toolbarWidth = getAdjustedToolbarWidth(mContext, mSuggestedWidth) + // Reserve space for the "open overflow" button. + - getEstimatedOpenOverflowButtonWidth(mContext); + + int availableWidth = toolbarWidth; + LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems); + + mMenuItemButtonsContainer.removeAllViews(); + + boolean isFirstItem = true; + while (!remainingMenuItems.isEmpty()) { + final MenuItem menuItem = remainingMenuItems.peek(); + Button menuItemButton = createMenuItemButton(mContext, menuItem); + + // Adding additional left padding for the first button to even out button spacing. + if (isFirstItem) { + menuItemButton.setPadding( + 2 * menuItemButton.getPaddingLeft(), + menuItemButton.getPaddingTop(), + menuItemButton.getPaddingRight(), + menuItemButton.getPaddingBottom()); + isFirstItem = false; + } + + // Adding additional right padding for the last button to even out button spacing. + if (remainingMenuItems.size() == 1) { + menuItemButton.setPadding( + menuItemButton.getPaddingLeft(), + menuItemButton.getPaddingTop(), + 2 * menuItemButton.getPaddingRight(), + menuItemButton.getPaddingBottom()); + } + + menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth); + if (menuItemButtonWidth <= availableWidth) { + menuItemButton.setTag(menuItem); + menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener); + mMenuItemButtonsContainer.addView(menuItemButton); + menuItemButton.getLayoutParams().width = menuItemButtonWidth; + availableWidth -= menuItemButtonWidth; + remainingMenuItems.pop(); + } else { + // The "open overflow" button launches the vertical overflow from the + // floating toolbar. + createOpenOverflowButtonIfNotExists(); + mMenuItemButtonsContainer.addView(mOpenOverflowButton); + break; + } + } + mPopup.setContentView(mMenuItemButtonsContainer); + } + + /** + * Creates and returns the button that opens the vertical overflow. + */ + private void createOpenOverflowButtonIfNotExists() { + mOpenOverflowButton = (ImageButton) LayoutInflater.from(mContext) + .inflate(R.layout.floating_popup_open_overflow_button, null); + mOpenOverflowButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Open the overflow. + } + }); + } + + /** + * Creates and returns a floating toolbar menu buttons container. + */ + private static ViewGroup createMenuButtonsContainer(Context context) { + return (ViewGroup) LayoutInflater.from(context) + .inflate(R.layout.floating_popup_container, null); + } + + /** + * Creates and returns a menu button for the specified menu item. + */ + private static Button createMenuItemButton(Context context, MenuItem menuItem) { + Button menuItemButton = (Button) LayoutInflater.from(context) + .inflate(R.layout.floating_popup_menu_button, null); + menuItemButton.setText(menuItem.getTitle()); + menuItemButton.setContentDescription(menuItem.getTitle()); + return menuItemButton; + } + + private static int getMinimumOverflowHeight(Context context) { + return context.getResources(). + getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height); + } + + private static int getEstimatedOpenOverflowButtonWidth(Context context) { + return context.getResources() + .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_minimum_width); + } + + private static int getAdjustedToolbarWidth(Context context, int width) { + if (width <= 0 || width > getScreenWidth(context)) { + width = context.getResources() + .getDimensionPixelSize(R.dimen.floating_toolbar_default_width); + } + return width; + } + + /** + * Returns the device's screen width. + */ + public static int getScreenWidth(Context context) { + return context.getResources().getDisplayMetrics().widthPixels; + } + + /** + * Returns the device's screen height. + */ + public static int getScreenHeight(Context context) { + return context.getResources().getDisplayMetrics().heightPixels; + } + + + /** + * A popup window used by the floating toolbar. + */ + private static final class FloatingToolbarPopup { + + private final View mParent; + private final PopupWindow mPopupWindow; + private final ViewGroup mContentContainer; + private final Animator.AnimatorListener mOnDismissEnd = + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mPopupWindow.dismiss(); + mDismissAnimating = false; + } + }; + private final AnimatorSet mGrowFadeInFromBottomAnimation; + private final AnimatorSet mShrinkFadeOutFromBottomAnimation; + + private boolean mDismissAnimating; + + /** + * Initializes a new floating bar popup. + * + * @param parent A parent view to get the {@link View#getWindowToken()} token from. + */ + public FloatingToolbarPopup(View parent) { + mParent = Preconditions.checkNotNull(parent); + mContentContainer = createContentContainer(parent.getContext()); + mPopupWindow = createPopupWindow(mContentContainer); + mGrowFadeInFromBottomAnimation = createGrowFadeInFromBottom(mContentContainer); + mShrinkFadeOutFromBottomAnimation = + createShrinkFadeOutFromBottomAnimation(mContentContainer, mOnDismissEnd); + } + + /** + * Shows this popup at the specified coordinates. + * The specified coordinates may be adjusted to make sure the popup is entirely on-screen. + * If this popup is already showing, this will be a no-op. + */ + public void show(int x, int y) { + if (isShowing()) { + updateCoordinates(x, y); + return; + } + + mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, 0, 0); + positionOnScreen(x, y); + growFadeInFromBottom(); + + mDismissAnimating = false; + } + + /** + * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op. + */ + public void dismiss() { + if (!isShowing()) { + return; + } + + if (mDismissAnimating) { + // This window is already dismissing. Don't restart the animation. + return; + } + mDismissAnimating = true; + shrinkFadeOutFromBottom(); + } + + /** + * Returns {@code true} if this popup is currently showing. {@code false} otherwise. + */ + public boolean isShowing() { + return mPopupWindow.isShowing() && !mDismissAnimating; + } + + /** + * Updates the coordinates of this popup. + * The specified coordinates may be adjusted to make sure the popup is entirely on-screen. + */ + public void updateCoordinates(int x, int y) { + if (isShowing()) { + positionOnScreen(x, y); + } + } + + /** + * Sets the content of this popup. + */ + public void setContentView(View view) { + Preconditions.checkNotNull(view); + mContentContainer.removeAllViews(); + mContentContainer.addView(view); + } + + /** + * Returns the width of this popup. + */ + public int getWidth() { + return mContentContainer.getWidth(); + } + + /** + * Returns the height of this popup. + */ + public int getHeight() { + return mContentContainer.getHeight(); + } + + /** + * Returns the context this popup is running in. + */ + public Context getContext() { + return mContentContainer.getContext(); + } + + private void positionOnScreen(int x, int y) { + if (getWidth() == 0) { + // content size is yet to be measured. + mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + } + x = clamp(x, 0, getScreenWidth(getContext()) - getWidth()); + y = clamp(y, 0, getScreenHeight(getContext()) - getHeight()); + + // Position the view w.r.t. the window. + mContentContainer.setX(x); + mContentContainer.setY(y); + } + + /** + * Performs the "grow and fade in from the bottom" animation on the floating popup. + */ + private void growFadeInFromBottom() { + setPivot(); + mGrowFadeInFromBottomAnimation.start(); + } + + /** + * Performs the "shrink and fade out from bottom" animation on the floating popup. + */ + private void shrinkFadeOutFromBottom() { + setPivot(); + mShrinkFadeOutFromBottomAnimation.start(); + } + + /** + * Sets the popup content container's pivot. + */ + private void setPivot() { + mContentContainer.setPivotX(mContentContainer.getMeasuredWidth() / 2); + mContentContainer.setPivotY(mContentContainer.getMeasuredHeight()); + } + + private static ViewGroup createContentContainer(Context context) { + return (ViewGroup) LayoutInflater.from(context) + .inflate(R.layout.floating_popup_container, null); + } + + private static PopupWindow createPopupWindow(View content) { + ViewGroup popupContentHolder = new LinearLayout(content.getContext()); + PopupWindow popupWindow = new PopupWindow(popupContentHolder); + popupWindow.setAnimationStyle(0); + popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + popupWindow.setWidth(getScreenWidth(content.getContext())); + popupWindow.setHeight(getScreenHeight(content.getContext())); + content.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + popupContentHolder.addView(content); + return popupWindow; + } + + /** + * Creates a "grow and fade in from the bottom" animation for the specified view. + * + * @param view The view to animate + */ + private static AnimatorSet createGrowFadeInFromBottom(View view) { + AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet(); + growFadeInFromBottomAnimation.playTogether( + ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125), + ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125), + ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75)); + return growFadeInFromBottomAnimation; + } + + /** + * Creates a "shrink and fade out from bottom" animation for the specified view. + * + * @param view The view to animate + * @param listener The animation listener + */ + private static AnimatorSet createShrinkFadeOutFromBottomAnimation( + View view, Animator.AnimatorListener listener) { + AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet(); + shrinkFadeOutFromBottomAnimation.playTogether( + ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125), + ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75)); + shrinkFadeOutFromBottomAnimation.setStartDelay(150); + shrinkFadeOutFromBottomAnimation.addListener(listener); + return shrinkFadeOutFromBottomAnimation; + } + + /** + * Returns value, restricted to the range min->max (inclusive). + * If maximum is less than minimum, the result is undefined. + * + * @param value The value to clamp. + * @param minimum The minimum value in the range. + * @param maximum The maximum value in the range. Must not be less than minimum. + */ + private static int clamp(int value, int minimum, int maximum) { + return Math.max(minimum, Math.min(value, maximum)); + } + } +} diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 52bbabf..8be34e7 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -269,9 +269,9 @@ public class LockPatternView extends View { mPathPaint.setAntiAlias(true); mPathPaint.setDither(true); - mRegularColor = getResources().getColor(R.color.lock_pattern_view_regular_color); - mErrorColor = getResources().getColor(R.color.lock_pattern_view_error_color); - mSuccessColor = getResources().getColor(R.color.lock_pattern_view_success_color); + mRegularColor = context.getColor(R.color.lock_pattern_view_regular_color); + mErrorColor = context.getColor(R.color.lock_pattern_view_error_color); + mSuccessColor = context.getColor(R.color.lock_pattern_view_success_color); mRegularColor = a.getColor(R.styleable.LockPatternView_regularColor, mRegularColor); mErrorColor = a.getColor(R.styleable.LockPatternView_errorColor, mErrorColor); mSuccessColor = a.getColor(R.styleable.LockPatternView_successColor, mSuccessColor); diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java index f916e6f..8018942 100644 --- a/core/java/com/android/internal/widget/ViewPager.java +++ b/core/java/com/android/internal/widget/ViewPager.java @@ -137,7 +137,7 @@ public class ViewPager extends ViewGroup { private int mRestoredCurItem = -1; private Parcelable mRestoredAdapterState = null; private ClassLoader mRestoredClassLoader = null; - private Scroller mScroller; + private final Scroller mScroller; private PagerObserver mObserver; private int mPageMargin; @@ -162,9 +162,9 @@ public class ViewPager extends ViewGroup { private boolean mIsBeingDragged; private boolean mIsUnableToDrag; - private int mDefaultGutterSize; + private final int mDefaultGutterSize; private int mGutterSize; - private int mTouchSlop; + private final int mTouchSlop; /** * Position of the last motion event. */ @@ -187,10 +187,10 @@ public class ViewPager extends ViewGroup { * Determines speed during touch scrolling */ private VelocityTracker mVelocityTracker; - private int mMinimumVelocity; - private int mMaximumVelocity; - private int mFlingDistance; - private int mCloseEnough; + private final int mMinimumVelocity; + private final int mMaximumVelocity; + private final int mFlingDistance; + private final int mCloseEnough; // If the pager is at least this close to its final position, complete the scroll // on touch down and let the user interact with the content inside instead of @@ -200,8 +200,8 @@ public class ViewPager extends ViewGroup { private boolean mFakeDragging; private long mFakeDragBeginTime; - private EdgeEffect mLeftEdge; - private EdgeEffect mRightEdge; + private final EdgeEffect mLeftEdge; + private final EdgeEffect mRightEdge; private boolean mFirstLayout = true; private boolean mNeedCalculatePageOffsets = false; @@ -339,20 +339,24 @@ public class ViewPager extends ViewGroup { interface Decor {} public ViewPager(Context context) { - super(context); - initViewPager(); + this(context, null); } public ViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - initViewPager(); + this(context, attrs, 0); } - void initViewPager() { + public ViewPager(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ViewPager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setWillNotDraw(false); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setFocusable(true); - final Context context = getContext(); + mScroller = new Scroller(context, sInterpolator); final ViewConfiguration configuration = ViewConfiguration.get(context); final float density = context.getResources().getDisplayMetrics().density; |
