diff options
Diffstat (limited to 'core/java')
25 files changed, 1658 insertions, 1316 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 5fd288f..d9adba3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3010,8 +3010,8 @@ public final class ActivityThread { int h; if (w < 0) { Resources res = r.activity.getResources(); - int wId = com.android.internal.R.dimen.recents_thumbnail_width; - int hId = com.android.internal.R.dimen.recents_thumbnail_height; + int wId = com.android.internal.R.dimen.thumbnail_width; + int hId = com.android.internal.R.dimen.thumbnail_height; mThumbnailWidth = w = res.getDimensionPixelSize(wId); mThumbnailHeight = h = res.getDimensionPixelSize(hId); } else { diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 7617886..d08978b 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -189,15 +189,17 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { final protected SharedElementListener mListener; protected ResultReceiver mResultReceiver; final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); + final protected boolean mIsReturning; public ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, ArrayList<String> accepted, ArrayList<String> localNames, - SharedElementListener listener) { + SharedElementListener listener, boolean isReturning) { super(new Handler()); mWindow = window; mListener = listener; mAllSharedElementNames = allSharedElementNames; + mIsReturning = isReturning; setSharedElements(accepted, localNames); if (getViewsTransition() != null) { getDecor().captureTransitioningViews(mTransitioningViews); diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 4cca355..bc97852 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -55,16 +55,14 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mHasStopped; private Handler mHandler; private boolean mIsCanceled; - private boolean mIsReturning; private ObjectAnimator mBackgroundAnimator; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, ArrayList<String> acceptedNames, ArrayList<String> mappedNames) { super(activity.getWindow(), sharedElementNames, acceptedNames, mappedNames, - getListener(activity, acceptedNames)); + getListener(activity, acceptedNames), acceptedNames != null); mActivity = activity; - mIsReturning = acceptedNames != null; setResultReceiver(resultReceiver); prepareEnter(); Bundle resultReceiverBundle = new Bundle(); diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index f36c36a..93eb53e 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -58,16 +58,14 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private Handler mHandler; - private boolean mIsReturning; - private ObjectAnimator mBackgroundAnimator; private boolean mIsHidden; public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { - super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning)); - mIsReturning = isReturning; + super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning), + isReturning); mIsBackgroundReady = !isReturning; mActivity = activity; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index dfd927f..6e23b11 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -35,6 +35,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; +import android.view.Gravity; import android.view.View; import android.widget.ProgressBar; import android.widget.RemoteViews; @@ -46,7 +47,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.List; /** * A class that represents how a persistent notification is to be presented to @@ -767,7 +770,7 @@ public class Notification implements Parcelable */ public static class Action implements Parcelable { private final Bundle mExtras; - private RemoteInput[] mRemoteInputs; + private final RemoteInput[] mRemoteInputs; /** * Small icon representing the action. @@ -910,25 +913,12 @@ public class Notification implements Parcelable * Apply an extender to this action builder. Extenders may be used to add * metadata or change options on this builder. */ - public Builder apply(Extender extender) { - extender.applyTo(this); + public Builder extend(Extender extender) { + extender.extend(this); return this; } /** - * Extender interface for use with {@link #apply}. Extenders may be used to add - * metadata or change options on this builder. - */ - public interface Extender { - /** - * Apply this extender to a notification action builder. - * @param builder the builder to be modified. - * @return the build object for chaining. - */ - public Builder applyTo(Builder builder); - } - - /** * Combine all of the options that have been set and return a new {@link Action} * object. * @return the built action @@ -975,6 +965,121 @@ public class Notification implements Parcelable return new Action[size]; } }; + + /** + * Extender interface for use with {@link Builder#extend}. Extenders may be used to add + * metadata or change options on an action builder. + */ + public interface Extender { + /** + * Apply this extender to a notification action builder. + * @param builder the builder to be modified. + * @return the build object for chaining. + */ + public Builder extend(Builder builder); + } + + /** + * Wearable extender for notification actions. To add extensions to an action, + * create a new {@link android.app.Notification.Action.WearableExtender} object using + * the {@code WearableExtender()} constructor and apply it to a + * {@link android.app.Notification.Action.Builder} using + * {@link android.app.Notification.Action.Builder#extend}. + * + * <pre class="prettyprint"> + * Notification.Action action = new Notification.Action.Builder( + * R.drawable.archive_all, "Archive all", actionIntent) + * .apply(new Notification.Action.WearableExtender() + * .setAvailableOffline(false)) + * .build(); + * </pre> + */ + public static final class WearableExtender implements Extender { + /** Notification action extra which contains wearable extensions */ + private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; + + private static final String KEY_FLAGS = "flags"; + + // Flags bitwise-ored to mFlags + private static final int FLAG_AVAILABLE_OFFLINE = 0x1; + + // Default value for flags integer + private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; + + private int mFlags = DEFAULT_FLAGS; + + /** + * Create a {@link android.app.Notification.Action.WearableExtender} with default + * options. + */ + public WearableExtender() { + } + + /** + * Create a {@link android.app.Notification.Action.WearableExtender} by reading + * wearable options present in an existing notification action. + * @param action the notification action to inspect. + */ + public WearableExtender(Action action) { + Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); + if (wearableBundle != null) { + mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); + } + } + + /** + * Apply wearable extensions to a notification action that is being built. This is + * typically called by the {@link android.app.Notification.Action.Builder#extend} + * method of {@link android.app.Notification.Action.Builder}. + */ + @Override + public Action.Builder extend(Action.Builder builder) { + Bundle wearableBundle = new Bundle(); + + if (mFlags != DEFAULT_FLAGS) { + wearableBundle.putInt(KEY_FLAGS, mFlags); + } + + builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); + return builder; + } + + @Override + public WearableExtender clone() { + WearableExtender that = new WearableExtender(); + that.mFlags = this.mFlags; + return that; + } + + /** + * Set whether this action is available when the wearable device is not connected to + * a companion device. The user can still trigger this action when the wearable device is + * offline, but a visual hint will indicate that the action may not be available. + * Defaults to true. + */ + public WearableExtender setAvailableOffline(boolean availableOffline) { + setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); + return this; + } + + /** + * Get whether this action is available when the wearable device is not connected to + * a companion device. The user can still trigger this action when the wearable device is + * offline, but a visual hint will indicate that the action may not be available. + * Defaults to true. + */ + public boolean isAvailableOffline() { + return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; + } + + private void setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + } + } } /** @@ -2169,24 +2274,11 @@ public class Notification implements Parcelable * Apply an extender to this notification builder. Extenders may be used to add * metadata or change options on this builder. */ - public Builder apply(Extender extender) { - extender.applyTo(this); + public Builder extend(Extender extender) { + extender.extend(this); return this; } - /** - * Extender interface for use with {@link #apply}. Extenders may be used to add - * metadata or change options on this builder. - */ - public interface Extender { - /** - * Apply this extender to a notification builder. - * @param builder the builder to be modified. - * @return the build object for chaining. - */ - public Builder applyTo(Builder builder); - } - private void setFlag(int mask, boolean value) { if (value) { mFlags |= mask; @@ -3163,4 +3255,634 @@ public class Notification implements Parcelable return big; } } + + /** + * Extender interface for use with {@link Builder#extend}. Extenders may be used to add + * metadata or change options on a notification builder. + */ + public interface Extender { + /** + * Apply this extender to a notification builder. + * @param builder the builder to be modified. + * @return the build object for chaining. + */ + public Builder extend(Builder builder); + } + + /** + * Helper class to add wearable extensions to notifications. + * <p class="note"> See + * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications + * for Android Wear</a> for more information on how to use this class. + * <p> + * To create a notification with wearable extensions: + * <ol> + * <li>Create a {@link android.app.Notification.Builder}, setting any desired + * properties. + * <li>Create a {@link android.app.Notification.WearableExtender}. + * <li>Set wearable-specific properties using the + * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. + * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a + * notification. + * <li>Post the notification to the notification system with the + * {@code NotificationManager.notify(...)} methods. + * </ol> + * + * <pre class="prettyprint"> + * Notification notif = new Notification.Builder(mContext) + * .setContentTitle("New mail from " + sender.toString()) + * .setContentText(subject) + * .setSmallIcon(R.drawable.new_mail) + * .extend(new Notification.WearableExtender() + * .setContentIcon(R.drawable.new_mail)) + * .build(); + * NotificationManager notificationManger = + * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + * notificationManger.notify(0, notif);</pre> + * + * <p>Wearable extensions can be accessed on an existing notification by using the + * {@code WearableExtender(Notification)} constructor, + * and then using the {@code get} methods to access values. + * + * <pre class="prettyprint"> + * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( + * notification); + * List<Notification> pages = wearableExtender.getPages(); + * </pre> + */ + public static final class WearableExtender implements Extender { + /** + * Sentinel value for an action index that is unset. + */ + public static final int UNSET_ACTION_INDEX = -1; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification with + * default sizing. + * <p>For custom display notifications created using {@link #setDisplayIntent}, + * the default is {@link #SIZE_LARGE}. All other notifications size automatically based + * on their content. + */ + public static final int SIZE_DEFAULT = 0; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * with an extra small size. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_XSMALL = 1; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * with a small size. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_SMALL = 2; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * with a medium size. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_MEDIUM = 3; + + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * with a large size. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_LARGE = 4; + + /** Notification extra which contains wearable extensions */ + private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; + + // Keys within EXTRA_WEARABLE_OPTIONS for wearable options. + private static final String KEY_ACTIONS = "actions"; + private static final String KEY_FLAGS = "flags"; + private static final String KEY_DISPLAY_INTENT = "displayIntent"; + private static final String KEY_PAGES = "pages"; + private static final String KEY_BACKGROUND = "background"; + private static final String KEY_CONTENT_ICON = "contentIcon"; + private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; + private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; + private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; + private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; + private static final String KEY_GRAVITY = "gravity"; + + // Flags bitwise-ored to mFlags + private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; + private static final int FLAG_HINT_HIDE_ICON = 1 << 1; + private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; + private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; + + // Default value for flags integer + private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; + + private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; + private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; + + private ArrayList<Action> mActions = new ArrayList<Action>(); + private int mFlags = DEFAULT_FLAGS; + private PendingIntent mDisplayIntent; + private ArrayList<Notification> mPages = new ArrayList<Notification>(); + private Bitmap mBackground; + private int mContentIcon; + private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; + private int mContentActionIndex = UNSET_ACTION_INDEX; + private int mCustomSizePreset = SIZE_DEFAULT; + private int mCustomContentHeight; + private int mGravity = DEFAULT_GRAVITY; + + /** + * Create a {@link android.app.Notification.WearableExtender} with default + * options. + */ + public WearableExtender() { + } + + public WearableExtender(Notification notif) { + Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); + if (wearableBundle != null) { + List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); + if (actions != null) { + mActions.addAll(actions); + } + + mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); + mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); + + Notification[] pages = getNotificationArrayFromBundle( + wearableBundle, KEY_PAGES); + if (pages != null) { + Collections.addAll(mPages, pages); + } + + mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); + mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); + mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, + DEFAULT_CONTENT_ICON_GRAVITY); + mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, + UNSET_ACTION_INDEX); + mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, + SIZE_DEFAULT); + mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); + mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); + } + } + + /** + * Apply wearable extensions to a notification that is being built. This is typically + * called by the {@link android.app.Notification.Builder#extend} method of + * {@link android.app.Notification.Builder}. + */ + @Override + public Notification.Builder extend(Notification.Builder builder) { + Bundle wearableBundle = new Bundle(); + + if (!mActions.isEmpty()) { + wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); + } + if (mFlags != DEFAULT_FLAGS) { + wearableBundle.putInt(KEY_FLAGS, mFlags); + } + if (mDisplayIntent != null) { + wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); + } + if (!mPages.isEmpty()) { + wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( + new Notification[mPages.size()])); + } + if (mBackground != null) { + wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); + } + if (mContentIcon != 0) { + wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); + } + if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { + wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); + } + if (mContentActionIndex != UNSET_ACTION_INDEX) { + wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, + mContentActionIndex); + } + if (mCustomSizePreset != SIZE_DEFAULT) { + wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); + } + if (mCustomContentHeight != 0) { + wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); + } + if (mGravity != DEFAULT_GRAVITY) { + wearableBundle.putInt(KEY_GRAVITY, mGravity); + } + + builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); + return builder; + } + + @Override + public WearableExtender clone() { + WearableExtender that = new WearableExtender(); + that.mActions = new ArrayList<Action>(this.mActions); + that.mFlags = this.mFlags; + that.mDisplayIntent = this.mDisplayIntent; + that.mPages = new ArrayList<Notification>(this.mPages); + that.mBackground = this.mBackground; + that.mContentIcon = this.mContentIcon; + that.mContentIconGravity = this.mContentIconGravity; + that.mContentActionIndex = this.mContentActionIndex; + that.mCustomSizePreset = this.mCustomSizePreset; + that.mCustomContentHeight = this.mCustomContentHeight; + that.mGravity = this.mGravity; + return that; + } + + /** + * Add a wearable action to this notification. + * + * <p>When wearable actions are added using this method, the set of actions that + * show on a wearable device splits from devices that only show actions added + * using {@link android.app.Notification.Builder#addAction}. This allows for customization + * of which actions display on different devices. + * + * @param action the action to add to this notification + * @return this object for method chaining + * @see android.app.Notification.Action + */ + public WearableExtender addAction(Action action) { + mActions.add(action); + return this; + } + + /** + * Adds wearable actions to this notification. + * + * <p>When wearable actions are added using this method, the set of actions that + * show on a wearable device splits from devices that only show actions added + * using {@link android.app.Notification.Builder#addAction}. This allows for customization + * of which actions display on different devices. + * + * @param actions the actions to add to this notification + * @return this object for method chaining + * @see android.app.Notification.Action + */ + public WearableExtender addActions(List<Action> actions) { + mActions.addAll(actions); + return this; + } + + /** + * Clear all wearable actions present on this builder. + * @return this object for method chaining. + * @see #addAction + */ + public WearableExtender clearActions() { + mActions.clear(); + return this; + } + + /** + * Get the wearable actions present on this notification. + */ + public List<Action> getActions() { + return mActions; + } + + /** + * Set an intent to launch inside of an activity view when displaying + * this notification. This {@link PendingIntent} should be for an activity. + * + * @param intent the {@link PendingIntent} for an activity + * @return this object for method chaining + * @see android.app.Notification.WearableExtender#getDisplayIntent + */ + public WearableExtender setDisplayIntent(PendingIntent intent) { + mDisplayIntent = intent; + return this; + } + + /** + * Get the intent to launch inside of an activity view when displaying this + * notification. This {@code PendingIntent} should be for an activity. + */ + public PendingIntent getDisplayIntent() { + return mDisplayIntent; + } + + /** + * Add an additional page of content to display with this notification. The current + * notification forms the first page, and pages added using this function form + * subsequent pages. This field can be used to separate a notification into multiple + * sections. + * + * @param page the notification to add as another page + * @return this object for method chaining + * @see android.app.Notification.WearableExtender#getPages + */ + public WearableExtender addPage(Notification page) { + mPages.add(page); + return this; + } + + /** + * Add additional pages of content to display with this notification. The current + * notification forms the first page, and pages added using this function form + * subsequent pages. This field can be used to separate a notification into multiple + * sections. + * + * @param pages a list of notifications + * @return this object for method chaining + * @see android.app.Notification.WearableExtender#getPages + */ + public WearableExtender addPages(List<Notification> pages) { + mPages.addAll(pages); + return this; + } + + /** + * Clear all additional pages present on this builder. + * @return this object for method chaining. + * @see #addPage + */ + public WearableExtender clearPages() { + mPages.clear(); + return this; + } + + /** + * Get the array of additional pages of content for displaying this notification. The + * current notification forms the first page, and elements within this array form + * subsequent pages. This field can be used to separate a notification into multiple + * sections. + * @return the pages for this notification + */ + public List<Notification> getPages() { + return mPages; + } + + /** + * Set a background image to be displayed behind the notification content. + * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background + * will work with any notification style. + * + * @param background the background bitmap + * @return this object for method chaining + * @see android.app.Notification.WearableExtender#getBackground + */ + public WearableExtender setBackground(Bitmap background) { + mBackground = background; + return this; + } + + /** + * Get a background image to be displayed behind the notification content. + * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background + * will work with any notification style. + * + * @return the background image + * @see android.app.Notification.WearableExtender#setBackground + */ + public Bitmap getBackground() { + return mBackground; + } + + /** + * Set an icon that goes with the content of this notification. + */ + public WearableExtender setContentIcon(int icon) { + mContentIcon = icon; + return this; + } + + /** + * Get an icon that goes with the content of this notification. + */ + public int getContentIcon() { + return mContentIcon; + } + + /** + * Set the gravity that the content icon should have within the notification display. + * Supported values include {@link android.view.Gravity#START} and + * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. + * @see #setContentIcon + */ + public WearableExtender setContentIconGravity(int contentIconGravity) { + mContentIconGravity = contentIconGravity; + return this; + } + + /** + * Get the gravity that the content icon should have within the notification display. + * Supported values include {@link android.view.Gravity#START} and + * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. + * @see #getContentIcon + */ + public int getContentIconGravity() { + return mContentIconGravity; + } + + /** + * Set an action from this notification's actions to be clickable with the content of + * this notification page. This action will no longer display separately from the + * notification content. This action's icon will display with optional subtext provided + * by the action's title. + * @param actionIndex The index of the action to hoist on the current notification page. + * If wearable actions are present, this index will apply to that list, + * otherwise it will apply to the main notification's actions list. + */ + public WearableExtender setContentAction(int actionIndex) { + mContentActionIndex = actionIndex; + return this; + } + + /** + * Get the action index of an action from this notification to show as clickable with + * the content of this notification page. When the user clicks this notification page, + * this action will trigger. This action will no longer display separately from the + * notification content. The action's icon will display with optional subtext provided + * by the action's title. + * + * <p>If wearable specific actions are present, this index will apply to that list, + * otherwise it will apply to the main notification's actions list. + */ + public int getContentAction() { + return mContentActionIndex; + } + + /** + * Set the gravity that this notification should have within the available viewport space. + * Supported values include {@link android.view.Gravity#TOP}, + * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. + * The default value is {@link android.view.Gravity#BOTTOM}. + */ + public WearableExtender setGravity(int gravity) { + mGravity = gravity; + return this; + } + + /** + * Get the gravity that this notification should have within the available viewport space. + * Supported values include {@link android.view.Gravity#TOP}, + * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. + * The default value is {@link android.view.Gravity#BOTTOM}. + */ + public int getGravity() { + return mGravity; + } + + /** + * Set the custom size preset for the display of this notification out of the available + * presets found in {@link android.app.Notification.WearableExtender}, e.g. + * {@link #SIZE_LARGE}. + * <p>Some custom size presets are only applicable for custom display notifications created + * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the + * documentation for the preset in question. See also + * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. + */ + public WearableExtender setCustomSizePreset(int sizePreset) { + mCustomSizePreset = sizePreset; + return this; + } + + /** + * Get the custom size preset for the display of this notification out of the available + * presets found in {@link android.app.Notification.WearableExtender}, e.g. + * {@link #SIZE_LARGE}. + * <p>Some custom size presets are only applicable for custom display notifications created + * using {@link #setDisplayIntent}. Check the documentation for the preset in question. + * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. + */ + public int getCustomSizePreset() { + return mCustomSizePreset; + } + + /** + * Set the custom height in pixels for the display of this notification's content. + * <p>This option is only available for custom display notifications created + * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also + * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and + * {@link #getCustomContentHeight}. + */ + public WearableExtender setCustomContentHeight(int height) { + mCustomContentHeight = height; + return this; + } + + /** + * Get the custom height in pixels for the display of this notification's content. + * <p>This option is only available for custom display notifications created + * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and + * {@link #setCustomContentHeight}. + */ + public int getCustomContentHeight() { + return mCustomContentHeight; + } + + /** + * Set whether the scrolling position for the contents of this notification should start + * at the bottom of the contents instead of the top when the contents are too long to + * display within the screen. Default is false (start scroll at the top). + */ + public WearableExtender setStartScrollBottom(boolean startScrollBottom) { + setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); + return this; + } + + /** + * Get whether the scrolling position for the contents of this notification should start + * at the bottom of the contents instead of the top when the contents are too long to + * display within the screen. Default is false (start scroll at the top). + */ + public boolean getStartScrollBottom() { + return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; + } + + /** + * Set whether the content intent is available when the wearable device is not connected + * to a companion device. The user can still trigger this intent when the wearable device + * is offline, but a visual hint will indicate that the content intent may not be available. + * Defaults to true. + */ + public WearableExtender setContentIntentAvailableOffline( + boolean contentIntentAvailableOffline) { + setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); + return this; + } + + /** + * Get whether the content intent is available when the wearable device is not connected + * to a companion device. The user can still trigger this intent when the wearable device + * is offline, but a visual hint will indicate that the content intent may not be available. + * Defaults to true. + */ + public boolean getContentIntentAvailableOffline() { + return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; + } + + /** + * Set a hint that this notification's icon should not be displayed. + * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. + * @return this object for method chaining + */ + public WearableExtender setHintHideIcon(boolean hintHideIcon) { + setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); + return this; + } + + /** + * Get a hint that this notification's icon should not be displayed. + * @return {@code true} if this icon should not be displayed, false otherwise. + * The default value is {@code false} if this was never set. + */ + public boolean getHintHideIcon() { + return (mFlags & FLAG_HINT_HIDE_ICON) != 0; + } + + /** + * Set a visual hint that only the background image of this notification should be + * displayed, and other semantic content should be hidden. This hint is only applicable + * to sub-pages added using {@link #addPage}. + */ + public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { + setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); + return this; + } + + /** + * Get a visual hint that only the background image of this notification should be + * displayed, and other semantic content should be hidden. This hint is only applicable + * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. + */ + public boolean getHintShowBackgroundOnly() { + return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; + } + + private void setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + } + } + + /** + * 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. + */ + private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) { + Parcelable[] array = bundle.getParcelableArray(key); + if (array instanceof Notification[] || array == null) { + return (Notification[]) array; + } + Notification[] typedArray = Arrays.copyOf(array, array.length, + Notification[].class); + bundle.putParcelableArray(key, typedArray); + return typedArray; + } } diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java index 9cfc541..11420c5 100644 --- a/core/java/android/app/RemoteInput.java +++ b/core/java/android/app/RemoteInput.java @@ -64,18 +64,24 @@ public final class RemoteInput implements Parcelable { /** Extra added to a clip data intent object to hold the results bundle. */ public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; + // Flags bitwise-ored to mFlags + private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1; + + // Default value for flags integer + private static final int DEFAULT_FLAGS = FLAG_ALLOW_FREE_FORM_INPUT; + private final String mResultKey; private final CharSequence mLabel; private final CharSequence[] mChoices; - private final boolean mAllowFreeFormInput; + private final int mFlags; private final Bundle mExtras; private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, - boolean allowFreeFormInput, Bundle extras) { + int flags, Bundle extras) { this.mResultKey = resultKey; this.mLabel = label; this.mChoices = choices; - this.mAllowFreeFormInput = allowFreeFormInput; + this.mFlags = flags; this.mExtras = extras; } @@ -108,7 +114,7 @@ public final class RemoteInput implements Parcelable { * if you set this to false and {@link #getChoices} returns {@code null} or empty. */ public boolean getAllowFreeFormInput() { - return mAllowFreeFormInput; + return (mFlags & FLAG_ALLOW_FREE_FORM_INPUT) != 0; } /** @@ -125,7 +131,7 @@ public final class RemoteInput implements Parcelable { private final String mResultKey; private CharSequence mLabel; private CharSequence[] mChoices; - private boolean mAllowFreeFormInput = true; + private int mFlags = DEFAULT_FLAGS; private Bundle mExtras = new Bundle(); /** @@ -178,7 +184,7 @@ public final class RemoteInput implements Parcelable { * @return this object for method chaining */ public Builder setAllowFreeFormInput(boolean allowFreeFormInput) { - mAllowFreeFormInput = allowFreeFormInput; + setFlag(mFlags, allowFreeFormInput); return this; } @@ -205,12 +211,20 @@ public final class RemoteInput implements Parcelable { return mExtras; } + private void setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + } + /** * Combine all of the options that have been set and return a new {@link RemoteInput} * object. */ public RemoteInput build() { - return new RemoteInput(mResultKey, mLabel, mChoices, mAllowFreeFormInput, mExtras); + return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mExtras); } } @@ -218,7 +232,7 @@ public final class RemoteInput implements Parcelable { mResultKey = in.readString(); mLabel = in.readCharSequence(); mChoices = in.readCharSequenceArray(); - mAllowFreeFormInput = in.readInt() != 0; + mFlags = in.readInt(); mExtras = in.readBundle(); } @@ -279,7 +293,7 @@ public final class RemoteInput implements Parcelable { out.writeString(mResultKey); out.writeCharSequence(mLabel); out.writeCharSequenceArray(mChoices); - out.writeInt(mAllowFreeFormInput ? 1 : 0); + out.writeInt(mFlags); out.writeBundle(mExtras); } diff --git a/core/java/android/app/wearable/WearableActionExtensions.java b/core/java/android/app/wearable/WearableActionExtensions.java deleted file mode 100644 index c296ef2..0000000 --- a/core/java/android/app/wearable/WearableActionExtensions.java +++ /dev/null @@ -1,176 +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.app.wearable; - -import android.app.Notification; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Wearable extensions to notification actions. To add extensions to an action, - * create a new {@link WearableActionExtensions} object using - * {@link WearableActionExtensions.Builder} and apply it to a - * {@link android.app.Notification.Action.Builder}. - * - * <pre class="prettyprint"> - * Notification.Action action = new Notification.Action.Builder( - * R.drawable.archive_all, "Archive all", actionIntent) - * .apply(new WearableActionExtensions.Builder() - * .setAvailableOffline(false) - * .build()) - * .build(); - * </pre> - */ -public final class WearableActionExtensions implements Notification.Action.Builder.Extender, - Parcelable { - /** Notification action extra which contains wearable extensions */ - private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; - - // Flags bitwise-ored to mFlags - private static final int FLAG_AVAILABLE_OFFLINE = 1 << 0; - - // Default value for flags integer - private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; - - private final int mFlags; - - private WearableActionExtensions(int flags) { - mFlags = flags; - } - - private WearableActionExtensions(Parcel in) { - mFlags = in.readInt(); - } - - /** - * Create a {@link WearableActionExtensions} by reading wearable extensions present on an - * existing notification action. - * @param action the notification action to inspect. - * @return a new {@link WearableActionExtensions} object. - */ - public static WearableActionExtensions from(Notification.Action action) { - WearableActionExtensions extensions = action.getExtras().getParcelable( - EXTRA_WEARABLE_EXTENSIONS); - if (extensions != null) { - return extensions; - } else { - // Return a WearableActionExtensions with default values. - return new Builder().build(); - } - } - - /** - * Get whether this action is available when the wearable device is not connected to - * a companion device. The user can still trigger this action when the wearable device is - * offline, but a visual hint will indicate that the action may not be available. - * Defaults to true. - */ - public boolean isAvailableOffline() { - return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; - } - - @Override - public Notification.Action.Builder applyTo(Notification.Action.Builder builder) { - builder.getExtras().putParcelable(EXTRA_WEARABLE_EXTENSIONS, this); - return builder; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mFlags); - } - - /** - * Builder for {@link WearableActionExtensions} objects, which adds wearable extensions to - * notification actions. To extend an action, create an instance of this class, call the set - * methods present, call {@link #build}, and finally apply the options to a - * {@link Notification.Action.Builder} using its - * {@link android.app.Notification.Action.Builder#apply} method. - */ - public static final class Builder { - private int mFlags = DEFAULT_FLAGS; - - /** - * Construct a builder to be used for adding wearable extensions to notification actions. - * - * <pre class="prettyprint"> - * Notification.Action action = new Notification.Action.Builder( - * R.drawable.archive_all, "Archive all", actionIntent) - * .apply(new WearableActionExtensions.Builder() - * .setAvailableOffline(false) - * .build()) - * .build();</pre> - */ - public Builder() { - } - - /** - * Create a {@link Builder} by reading wearable extensions present on an - * existing {@code WearableActionExtensions} object. - * @param other the existing extensions to inspect. - */ - public Builder(WearableActionExtensions other) { - mFlags = other.mFlags; - } - - /** - * Set whether this action is available when the wearable device is not connected to - * a companion device. The user can still trigger this action when the wearable device is - * offline, but a visual hint will indicate that the action may not be available. - * Defaults to true. - */ - public Builder setAvailableOffline(boolean availableOffline) { - setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); - return this; - } - - /** - * Build a new {@link WearableActionExtensions} object with the extensions - * currently present on this builder. - * @return the extensions object. - */ - public WearableActionExtensions build() { - return new WearableActionExtensions(mFlags); - } - - private void setFlag(int mask, boolean value) { - if (value) { - mFlags |= mask; - } else { - mFlags &= ~mask; - } - } - } - - public static final Creator<WearableActionExtensions> CREATOR = - new Creator<WearableActionExtensions>() { - @Override - public WearableActionExtensions createFromParcel(Parcel in) { - return new WearableActionExtensions(in); - } - - @Override - public WearableActionExtensions[] newArray(int size) { - return new WearableActionExtensions[size]; - } - }; -} diff --git a/core/java/android/app/wearable/WearableNotificationExtensions.java b/core/java/android/app/wearable/WearableNotificationExtensions.java deleted file mode 100644 index d433613..0000000 --- a/core/java/android/app/wearable/WearableNotificationExtensions.java +++ /dev/null @@ -1,702 +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.app.wearable; - -import android.app.Notification; -import android.app.PendingIntent; -import android.graphics.Bitmap; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.Gravity; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Helper class that contains wearable extensions for notifications. - * <p class="note"> See - * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications - * for Android Wear</a> for more information on how to use this class. - * <p> - * To create a notification with wearable extensions: - * <ol> - * <li>Create a {@link Notification.Builder}, setting any desired - * properties. - * <li>Create a {@link WearableNotificationExtensions.Builder}. - * <li>Set wearable-specific properties using the - * {@code add} and {@code set} methods of {@link WearableNotificationExtensions.Builder}. - * <li>Call {@link WearableNotificationExtensions.Builder#build} to build the extensions - * object. - * <li>Call {@link Notification.Builder#apply} to apply the extensions to a notification. - * <li>Post the notification to the notification system with the - * {@code NotificationManager.notify(...)} methods. - * </ol> - * - * <pre class="prettyprint"> - * Notification notif = new Notification.Builder(mContext) - * .setContentTitle("New mail from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail) - * .apply(new new WearableNotificationExtensions.Builder() - * .setContentIcon(R.drawable.new_mail) - * .build()) - * .build(); - * NotificationManager notificationManger = - * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - * notificationManger.notify(0, notif);</pre> - * - * <p>Wearable extensions can be accessed on an existing notification by using the - * {@link WearableNotificationExtensions#from} function. - * - * <pre class="prettyprint"> - * WearableNotificationExtensions wearableExtensions = WearableNotificationExtensions.from( - * notification); - * Notification[] pages = wearableExtensions.getPages(); - * </pre> - */ -public final class WearableNotificationExtensions implements Notification.Builder.Extender, - Parcelable { - /** - * Sentinel value for an action index that is unset. - */ - public static final int UNSET_ACTION_INDEX = -1; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification with - * default sizing. - * <p>For custom display notifications created using {@link Builder#setDisplayIntent}, - * the default is {@link #SIZE_LARGE}. All other notifications size automatically based - * on their content. - */ - public static final int SIZE_DEFAULT = 0; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification - * with an extra small size. - * <p>This value is only applicable for custom display notifications created using - * {@link Builder#setDisplayIntent}. - */ - public static final int SIZE_XSMALL = 1; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification - * with a small size. - * <p>This value is only applicable for custom display notifications created using - * {@link Builder#setDisplayIntent}. - */ - public static final int SIZE_SMALL = 2; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification - * with a medium size. - * <p>This value is only applicable for custom display notifications created using - * {@link Builder#setDisplayIntent}. - */ - public static final int SIZE_MEDIUM = 3; - - /** - * Size value for use with {@link Builder#setCustomSizePreset} to show this notification - * with a large size. - * <p>This value is only applicable for custom display notifications created using - * {@link Builder#setDisplayIntent}. - */ - public static final int SIZE_LARGE = 4; - - /** Notification extra which contains wearable extensions */ - static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; - - // Flags bitwise-ored to mFlags - static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 1 << 0; - static final int FLAG_HINT_HIDE_ICON = 1 << 1; - static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; - static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; - - // Default value for flags integer - static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; - - private final Notification.Action[] mActions; - private final int mFlags; - private final PendingIntent mDisplayIntent; - private final Notification[] mPages; - private final Bitmap mBackground; - private final int mContentIcon; - private final int mContentIconGravity; - private final int mContentActionIndex; - private final int mCustomSizePreset; - private final int mCustomContentHeight; - private final int mGravity; - - private WearableNotificationExtensions(Notification.Action[] actions, int flags, - PendingIntent displayIntent, Notification[] pages, Bitmap background, - int contentIcon, int contentIconGravity, int contentActionIndex, - int customSizePreset, int customContentHeight, int gravity) { - mActions = actions; - mFlags = flags; - mDisplayIntent = displayIntent; - mPages = pages; - mBackground = background; - mContentIcon = contentIcon; - mContentIconGravity = contentIconGravity; - mContentActionIndex = contentActionIndex; - mCustomSizePreset = customSizePreset; - mCustomContentHeight = customContentHeight; - mGravity = gravity; - } - - private WearableNotificationExtensions(Parcel in) { - mActions = in.createTypedArray(Notification.Action.CREATOR); - mFlags = in.readInt(); - mDisplayIntent = in.readParcelable(PendingIntent.class.getClassLoader()); - mPages = in.createTypedArray(Notification.CREATOR); - mBackground = in.readParcelable(Bitmap.class.getClassLoader()); - mContentIcon = in.readInt(); - mContentIconGravity = in.readInt(); - mContentActionIndex = in.readInt(); - mCustomSizePreset = in.readInt(); - mCustomContentHeight = in.readInt(); - mGravity = in.readInt(); - } - - /** - * Create a {@link WearableNotificationExtensions} by reading wearable extensions present on an - * existing notification. - * @param notif the notification to inspect. - * @return a new {@link WearableNotificationExtensions} object. - */ - public static WearableNotificationExtensions from(Notification notif) { - WearableNotificationExtensions extensions = notif.extras.getParcelable( - EXTRA_WEARABLE_EXTENSIONS); - if (extensions != null) { - return extensions; - } else { - // Return a WearableNotificationExtensions with default values. - return new Builder().build(); - } - } - - /** - * Apply wearable extensions to a notification that is being built. This is typically - * called by {@link Notification.Builder#apply} method of {@link Notification.Builder}. - */ - @Override - public Notification.Builder applyTo(Notification.Builder builder) { - builder.getExtras().putParcelable(EXTRA_WEARABLE_EXTENSIONS, this); - return builder; - } - - /** - * Get the number of wearable actions present on this notification. - * - * @return the number of wearable actions for this notification - */ - public int getActionCount() { - return mActions.length; - } - - /** - * Get a {@link Notification.Action} for the wearable action at {@code actionIndex}. - * @param actionIndex the index of the desired wearable action - */ - public Notification.Action getAction(int actionIndex) { - return mActions[actionIndex]; - } - - /** - * Get the wearable actions present on this notification. - */ - public Notification.Action[] getActions() { - return mActions; - } - - /** - * Get the intent to launch inside of an activity view when displaying this - * notification. This {@code PendingIntent} should be for an activity. - */ - public PendingIntent getDisplayIntent() { - return mDisplayIntent; - } - - /** - * Get the array of additional pages of content for displaying this notification. The - * current notification forms the first page, and elements within this array form - * subsequent pages. This field can be used to separate a notification into multiple - * sections. - * @return the pages for this notification - */ - public Notification[] getPages() { - return mPages; - } - - /** - * Get a background image to be displayed behind the notification content. - * Contrary to the {@link Notification.BigPictureStyle}, this background - * will work with any notification style. - * - * @return the background image - * @see Builder#setBackground - */ - public Bitmap getBackground() { - return mBackground; - } - - /** - * Get an icon that goes with the content of this notification. - */ - public int getContentIcon() { - return mContentIcon; - } - - /** - * Get the gravity that the content icon should have within the notification display. - * Supported values include {@link Gravity#START} and {@link Gravity#END}. The default - * value is {@link android.view.Gravity#END}. - * @see #getContentIcon - */ - public int getContentIconGravity() { - return mContentIconGravity; - } - - /** - * Get the action index of an action from this notification to show as clickable with - * the content of this notification page. When the user clicks this notification page, - * this action will trigger. This action will no longer display separately from the - * notification content. The action's icon will display with optional subtext provided - * by the action's title. - * - * <p>If wearable specific actions are present, this index will apply to that list, - * otherwise it will apply to the main notification's actions list. - */ - public int getContentAction() { - return mContentActionIndex; - } - - /** - * Get the gravity that this notification should have within the available viewport space. - * Supported values include {@link Gravity#TOP}, {@link Gravity#CENTER_VERTICAL} and - * {@link android.view.Gravity#BOTTOM}. The default value is - * {@link android.view.Gravity#BOTTOM}. - */ - public int getGravity() { - return mGravity; - } - - /** - * Get the custom size preset for the display of this notification out of the available - * presets found in {@link WearableNotificationExtensions}, e.g. {@link #SIZE_LARGE}. - * <p>Some custom size presets are only applicable for custom display notifications created - * using {@link Builder#setDisplayIntent}. Check the documentation for the preset in question. - * See also {@link Builder#setCustomContentHeight} and {@link Builder#setCustomSizePreset}. - */ - public int getCustomSizePreset() { - return mCustomSizePreset; - } - - /** - * Get the custom height in pixels for the display of this notification's content. - * <p>This option is only available for custom display notifications created - * using {@link Builder#setDisplayIntent}. See also {@link Builder#setCustomSizePreset} and - * {@link Builder#setCustomContentHeight}. - */ - public int getCustomContentHeight() { - return mCustomContentHeight; - } - - /** - * Get whether the scrolling position for the contents of this notification should start - * at the bottom of the contents instead of the top when the contents are too long to - * display within the screen. Default is false (start scroll at the top). - */ - public boolean getStartScrollBottom() { - return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; - } - - /** - * Get whether the content intent is available when the wearable device is not connected - * to a companion device. The user can still trigger this intent when the wearable device is - * offline, but a visual hint will indicate that the content intent may not be available. - * Defaults to true. - */ - public boolean getContentIntentAvailableOffline() { - return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; - } - - /** - * Get a hint that this notification's icon should not be displayed. - * @return {@code true} if this icon should not be displayed, false otherwise. - * The default value is {@code false} if this was never set. - */ - public boolean getHintHideIcon() { - return (mFlags & FLAG_HINT_HIDE_ICON) != 0; - } - - /** - * Get a visual hint that only the background image of this notification should be - * displayed, and other semantic content should be hidden. This hint is only applicable - * to sub-pages added using {@link Builder#addPage}. - */ - public boolean getHintShowBackgroundOnly() { - return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeTypedArray(mActions, flags); - out.writeInt(mFlags); - out.writeParcelable(mDisplayIntent, flags); - out.writeTypedArray(mPages, flags); - out.writeParcelable(mBackground, flags); - out.writeInt(mContentIcon); - out.writeInt(mContentIconGravity); - out.writeInt(mContentActionIndex); - out.writeInt(mCustomSizePreset); - out.writeInt(mCustomContentHeight); - out.writeInt(mGravity); - } - - /** - * Builder to apply wearable notification extensions to a {@link Notification.Builder} - * object. - * - * <p>You can chain the "set" methods for this builder in any order, - * but you must call the {@link #build} method and then the {@link Notification.Builder#apply} - * method to apply your extensions to a notification. - * - * <pre class="prettyprint"> - * Notification notif = new Notification.Builder(mContext) - * .setContentTitle("New mail from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail); - * .apply(new WearableNotificationExtensions.Builder() - * .setContentIcon(R.drawable.new_mail) - * .build()) - * .build(); - * NotificationManager notificationManger = - * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - * notificationManager.notify(0, notif);</pre> - */ - public static final class Builder { - private final List<Notification.Action> mActions = - new ArrayList<Notification.Action>(); - private int mFlags = DEFAULT_FLAGS; - private PendingIntent mDisplayIntent; - private final List<Notification> mPages = new ArrayList<Notification>(); - private Bitmap mBackground; - private int mContentIcon; - private int mContentIconGravity = Gravity.END; - private int mContentActionIndex = UNSET_ACTION_INDEX; - private int mCustomContentHeight; - private int mCustomSizePreset = SIZE_DEFAULT; - private int mGravity = Gravity.BOTTOM; - - /** - * Construct a builder to be used for adding wearable extensions to notifications. - * - * <pre class="prettyprint"> - * Notification notif = new Notification.Builder(mContext) - * .setContentTitle("New mail from " + sender.toString()) - * .setContentText(subject) - * .setSmallIcon(R.drawable.new_mail); - * .apply(new WearableNotificationExtensions.Builder() - * .setContentIcon(R.drawable.new_mail) - * .build()) - * .build(); - * NotificationManager notificationManger = - * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - * notificationManager.notify(0, notif);</pre> - */ - public Builder() { - } - - /** - * Create a {@link Builder} by reading wearable extensions present on an - * existing {@code WearableNotificationExtensions} object. - * @param other the existing extensions to inspect. - */ - public Builder(WearableNotificationExtensions other) { - Collections.addAll(mActions, other.mActions); - mFlags = other.mFlags; - mDisplayIntent = other.mDisplayIntent; - Collections.addAll(mPages, other.mPages); - mBackground = other.mBackground; - mContentIcon = other.mContentIcon; - mContentIconGravity = other.mContentIconGravity; - mContentActionIndex = other.mContentActionIndex; - mCustomContentHeight = other.mCustomContentHeight; - mCustomSizePreset = other.mCustomSizePreset; - mGravity = other.mGravity; - } - - /** - * Add a wearable action to this notification. - * - * <p>When wearable actions are added using this method, the set of actions that - * show on a wearable device splits from devices that only show actions added - * using {@link android.app.Notification.Builder#addAction}. This allows for customization - * of which actions display on different devices. - * - * @param action the action to add to this notification - * @return this object for method chaining - * @see Notification.Action - */ - public Builder addAction(Notification.Action action) { - mActions.add(action); - return this; - } - - /** - * Adds wearable actions to this notification. - * - * <p>When wearable actions are added using this method, the set of actions that - * show on a wearable device splits from devices that only show actions added - * using {@link android.app.Notification.Builder#addAction}. This allows for customization - * of which actions display on different devices. - * - * @param actions the actions to add to this notification - * @return this object for method chaining - * @see Notification.Action - */ - public Builder addActions(List<Notification.Action> actions) { - mActions.addAll(actions); - return this; - } - - /** - * Clear all wearable actions present on this builder. - * @return this object for method chaining. - * @see #addAction - */ - public Builder clearActions() { - mActions.clear(); - return this; - } - - /** - * Set an intent to launch inside of an activity view when displaying - * this notification. This {@link android.app.PendingIntent} should be for an activity. - * - * @param intent the {@link android.app.PendingIntent} for an activity - * @return this object for method chaining - * @see WearableNotificationExtensions#getDisplayIntent - */ - public Builder setDisplayIntent(PendingIntent intent) { - mDisplayIntent = intent; - return this; - } - - /** - * Add an additional page of content to display with this notification. The current - * notification forms the first page, and pages added using this function form - * subsequent pages. This field can be used to separate a notification into multiple - * sections. - * - * @param page the notification to add as another page - * @return this object for method chaining - * @see WearableNotificationExtensions#getPages - */ - public Builder addPage(Notification page) { - mPages.add(page); - return this; - } - - /** - * Add additional pages of content to display with this notification. The current - * notification forms the first page, and pages added using this function form - * subsequent pages. This field can be used to separate a notification into multiple - * sections. - * - * @param pages a list of notifications - * @return this object for method chaining - * @see WearableNotificationExtensions#getPages - */ - public Builder addPages(List<Notification> pages) { - mPages.addAll(pages); - return this; - } - - /** - * Clear all additional pages present on this builder. - * @return this object for method chaining. - * @see #addPage - */ - public Builder clearPages() { - mPages.clear(); - return this; - } - - /** - * Set a background image to be displayed behind the notification content. - * Contrary to the {@link Notification.BigPictureStyle}, this background - * will work with any notification style. - * - * @param background the background bitmap - * @return this object for method chaining - * @see WearableNotificationExtensions#getBackground - */ - public Builder setBackground(Bitmap background) { - mBackground = background; - return this; - } - - /** - * Set an icon that goes with the content of this notification. - */ - public Builder setContentIcon(int icon) { - mContentIcon = icon; - return this; - } - - /** - * Set the gravity that the content icon should have within the notification display. - * Supported values include {@link Gravity#START} and {@link Gravity#END}. The default - * value is {@link android.view.Gravity#END}. - * @see #setContentIcon - */ - public Builder setContentIconGravity(int contentIconGravity) { - mContentIconGravity = contentIconGravity; - return this; - } - - /** - * Set an action from this notification's actions to be clickable with the content of - * this notification page. This action will no longer display separately from the - * notification content. This action's icon will display with optional subtext provided - * by the action's title. - * @param actionIndex The index of the action to hoist on the current notification page. - * If wearable actions are present, this index will apply to that list, - * otherwise it will apply to the main notification's actions list. - */ - public Builder setContentAction(int actionIndex) { - mContentActionIndex = actionIndex; - return this; - } - - /** - * Set the gravity that this notification should have within the available viewport space. - * Supported values include {@link Gravity#TOP}, {@link Gravity#CENTER_VERTICAL} and - * {@link Gravity#BOTTOM}. The default value is {@link Gravity#BOTTOM}. - */ - public Builder setGravity(int gravity) { - mGravity = gravity; - return this; - } - - /** - * Set the custom size preset for the display of this notification out of the available - * presets found in {@link WearableNotificationExtensions}, e.g. {@link #SIZE_LARGE}. - * <p>Some custom size presets are only applicable for custom display notifications created - * using {@link Builder#setDisplayIntent}. Check the documentation for the preset in - * question. See also {@link Builder#setCustomContentHeight} and - * {@link #getCustomSizePreset}. - */ - public Builder setCustomSizePreset(int sizePreset) { - mCustomSizePreset = sizePreset; - return this; - } - - /** - * Set the custom height in pixels for the display of this notification's content. - * <p>This option is only available for custom display notifications created - * using {@link Builder#setDisplayIntent}. See also {@link Builder#setCustomSizePreset} and - * {@link #getCustomContentHeight}. - */ - public Builder setCustomContentHeight(int height) { - mCustomContentHeight = height; - return this; - } - - /** - * Set whether the scrolling position for the contents of this notification should start - * at the bottom of the contents instead of the top when the contents are too long to - * display within the screen. Default is false (start scroll at the top). - */ - public Builder setStartScrollBottom(boolean startScrollBottom) { - setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); - return this; - } - - /** - * Set whether the content intent is available when the wearable device is not connected - * to a companion device. The user can still trigger this intent when the wearable device - * is offline, but a visual hint will indicate that the content intent may not be available. - * Defaults to true. - */ - public Builder setContentIntentAvailableOffline(boolean contentIntentAvailableOffline) { - setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); - return this; - } - - /** - * Set a hint that this notification's icon should not be displayed. - * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. - * @return this object for method chaining - */ - public Builder setHintHideIcon(boolean hintHideIcon) { - setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); - return this; - } - - /** - * Set a visual hint that only the background image of this notification should be - * displayed, and other semantic content should be hidden. This hint is only applicable - * to sub-pages added using {@link #addPage}. - */ - public Builder setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { - setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); - return this; - } - - /** - * Build a new {@link WearableNotificationExtensions} object with the extensions - * currently present on this builder. - * @return the extensions object. - */ - public WearableNotificationExtensions build() { - return new WearableNotificationExtensions( - mActions.toArray(new Notification.Action[mActions.size()]), mFlags, - mDisplayIntent, mPages.toArray(new Notification[mPages.size()]), - mBackground, mContentIcon, mContentIconGravity, mContentActionIndex, - mCustomSizePreset, mCustomContentHeight, mGravity); - } - - private void setFlag(int mask, boolean value) { - if (value) { - mFlags |= mask; - } else { - mFlags &= ~mask; - } - } - } - - public static final Creator<WearableNotificationExtensions> CREATOR = - new Creator<WearableNotificationExtensions>() { - @Override - public WearableNotificationExtensions createFromParcel(Parcel in) { - return new WearableNotificationExtensions(in); - } - - @Override - public WearableNotificationExtensions[] newArray(int size) { - return new WearableNotificationExtensions[size]; - } - }; -} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index d0ac9c9..6ae006c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1994,6 +1994,7 @@ public abstract class Context { WIFI_PASSPOINT_SERVICE, WIFI_P2P_SERVICE, WIFI_SCANNING_SERVICE, + //@hide: ETHERNET_SERVICE, NSD_SERVICE, AUDIO_SERVICE, MEDIA_ROUTER_SERVICE, @@ -2069,9 +2070,6 @@ public abstract class Context { * <dt> {@link #WIFI_P2P_SERVICE} ("wifip2p") * <dd> A {@link android.net.wifi.p2p.WifiP2pManager WifiP2pManager} for management of * Wi-Fi Direct connectivity. - * <dt> {@link #ETHERNET_SERVICE} ("ethernet") - * <dd> A {@link android.net.ethernet.EthernetManager EthernetManager} for - * management of Ethernet connectivity. * <dt> {@link #INPUT_METHOD_SERVICE} ("input_method") * <dd> An {@link android.view.inputmethod.InputMethodManager InputMethodManager} * for management of input methods. diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d7bd473..4672015 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -19,9 +19,12 @@ package android.content.pm; import android.app.PackageInstallObserver; import android.app.PackageUninstallObserver; import android.content.pm.PackageManager.NameNotFoundException; +import android.os.FileBridge; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import java.io.OutputStream; + /** {@hide} */ public class PackageInstaller { private final PackageManager mPm; @@ -127,10 +130,17 @@ public class PackageInstaller { } } - public ParcelFileDescriptor openWrite(String overlayName, long offsetBytes, - long lengthBytes) { + /** + * Open an APK file for writing, starting at the given offset. You can + * then stream data into the file, periodically calling + * {@link OutputStream#flush()} to ensure bytes have been written to + * disk. + */ + public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) { try { - return mSession.openWrite(overlayName, offsetBytes, lengthBytes); + final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName, + offsetBytes, lengthBytes); + return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java index 220b40d..2dce425 100644 --- a/core/java/android/ddm/DdmHandleHello.java +++ b/core/java/android/ddm/DdmHandleHello.java @@ -22,6 +22,7 @@ import org.apache.harmony.dalvik.ddmc.DdmServer; import android.util.Log; import android.os.Debug; import android.os.UserHandle; +import dalvik.system.VMRuntime; import java.nio.ByteBuffer; @@ -126,8 +127,21 @@ public class DdmHandleHello extends ChunkHandler { // appName = "unknown"; String appName = DdmHandleAppName.getAppName(); - ByteBuffer out = ByteBuffer.allocate(20 - + vmIdent.length()*2 + appName.length()*2); + VMRuntime vmRuntime = VMRuntime.getRuntime(); + String instructionSetDescription = + vmRuntime.is64Bit() ? "64-bit" : "32-bit"; + String vmInstructionSet = vmRuntime.vmInstructionSet(); + if (vmInstructionSet != null && vmInstructionSet.length() > 0) { + instructionSetDescription += " (" + vmInstructionSet + ")"; + } + String vmFlags = "CheckJNI=" + + (vmRuntime.isCheckJniEnabled() ? "true" : "false"); + + ByteBuffer out = ByteBuffer.allocate(28 + + vmIdent.length() * 2 + + appName.length() * 2 + + instructionSetDescription.length() * 2 + + vmFlags.length() * 2); out.order(ChunkHandler.CHUNK_ORDER); out.putInt(DdmServer.CLIENT_PROTOCOL_VERSION); out.putInt(android.os.Process.myPid()); @@ -136,6 +150,10 @@ public class DdmHandleHello extends ChunkHandler { putString(out, vmIdent); putString(out, appName); out.putInt(UserHandle.myUserId()); + out.putInt(instructionSetDescription.length()); + putString(out, instructionSetDescription); + out.putInt(vmFlags.length()); + putString(out, vmFlags); Chunk reply = new Chunk(CHUNK_HELO, out); diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 86208fc..c593e9e 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -613,6 +613,7 @@ public final class Sensor { } /** + * @hide * @return The permission required to access this sensor. If empty, no permission is required. */ public String getRequiredPermission() { diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java index 71adf8b..22ff9c6 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java @@ -93,7 +93,7 @@ public class CameraDeviceState { * {@link CameraDeviceStateListener#onConfiguring()} will be called. * </p> * - * @returns {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. + * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. */ public synchronized int setConfiguring() { doStateTransition(STATE_CONFIGURING); @@ -108,7 +108,7 @@ public class CameraDeviceState { * {@link CameraDeviceStateListener#onIdle()} will be called. * </p> * - * @returns {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. + * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. */ public synchronized int setIdle() { doStateTransition(STATE_IDLE); @@ -124,7 +124,7 @@ public class CameraDeviceState { * </p> * * @param request A {@link RequestHolder} containing the request for the current capture. - * @returns {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. + * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. */ public synchronized int setCaptureStart(final RequestHolder request) { mCurrentRequest = request; @@ -144,7 +144,7 @@ public class CameraDeviceState { * * @param request the {@link RequestHolder} request that created this result. * @param result the {@link CameraMetadataNative} result to set. - * @returns {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. + * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. */ public synchronized int setCaptureResult(final RequestHolder request, final CameraMetadataNative result) { diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 80a9598..2f2aba3 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -35,15 +35,17 @@ import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; +import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.telephony.ITelephony; +import com.android.internal.util.Protocol; + import java.net.InetAddress; import java.util.concurrent.atomic.AtomicInteger; import java.util.HashMap; -import com.android.internal.util.Protocol; - /** * Class that answers queries about the state of network connectivity. It also * notifies applications when network connectivity changes. Get an instance @@ -940,34 +942,18 @@ public class ConnectivityManager { } /** - * Gets the value of the setting for enabling Mobile data. - * - * @return Whether mobile data is enabled. - * - * <p>This method requires the call to hold the permission - * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * @hide + * @deprecated Talk to TelephonyManager directly */ public boolean getMobileDataEnabled() { - try { - return mService.getMobileDataEnabled(); - } catch (RemoteException e) { - return true; - } - } - - /** - * Sets the persisted value for enabling/disabling Mobile data. - * - * @param enabled Whether the user wants the mobile data connection used - * or not. - * @hide - */ - public void setMobileDataEnabled(boolean enabled) { - try { - mService.setMobileDataEnabled(enabled); - } catch (RemoteException e) { + IBinder b = ServiceManager.getService(Context.TELEPHONY_SERVICE); + if (b != null) { + try { + ITelephony it = ITelephony.Stub.asInterface(b); + return it.getDataEnabled(); + } catch (RemoteException e) { } } + return false; } /** diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java index 70cc708..5df4baf 100644 --- a/core/java/android/net/EthernetManager.java +++ b/core/java/android/net/EthernetManager.java @@ -47,7 +47,7 @@ public class EthernetManager { } /** - * Get Ethernet configuration + * Get Ethernet configuration. * @return the Ethernet Configuration, contained in {@link IpConfiguration}. */ public IpConfiguration getConfiguration() { @@ -61,8 +61,7 @@ public class EthernetManager { } /** - * Set Ethernet configuration - * @return true if setting success + * Set Ethernet configuration. */ public void setConfiguration(IpConfiguration config) { try { diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index d97b1e9..baec36a 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -74,9 +74,6 @@ interface IConnectivityManager boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, String packageName); - boolean getMobileDataEnabled(); - void setMobileDataEnabled(boolean enabled); - /** Policy control over specific {@link NetworkStateTracker}. */ void setPolicyDataEnable(int networkType, boolean enabled); diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index e0d69e3..e489e05 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -16,10 +16,13 @@ package android.net; +import android.net.NetworkUtils; import android.os.Parcelable; import android.os.Parcel; +import java.io.IOException; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import javax.net.SocketFactory; @@ -38,6 +41,8 @@ public class Network implements Parcelable { */ public final int netId; + private NetworkBoundSocketFactory mNetworkBoundSocketFactory = null; + /** * @hide */ @@ -79,6 +84,59 @@ public class Network implements Parcelable { } /** + * A {@code SocketFactory} that produces {@code Socket}'s bound to this network. + */ + private class NetworkBoundSocketFactory extends SocketFactory { + private final int mNetId; + + public NetworkBoundSocketFactory(int netId) { + super(); + mNetId = netId; + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + Socket socket = createSocket(); + socket.bind(new InetSocketAddress(localHost, localPort)); + socket.connect(new InetSocketAddress(host, port)); + return socket; + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, + int localPort) throws IOException { + Socket socket = createSocket(); + socket.bind(new InetSocketAddress(localAddress, localPort)); + socket.connect(new InetSocketAddress(address, port)); + return socket; + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + Socket socket = createSocket(); + socket.connect(new InetSocketAddress(host, port)); + return socket; + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + Socket socket = createSocket(); + socket.connect(new InetSocketAddress(host, port)); + return socket; + } + + @Override + public Socket createSocket() throws IOException { + Socket socket = new Socket(); + // Query a property of the underlying socket to ensure the underlying + // socket exists so a file descriptor is available to bind to a network. + socket.getReuseAddress(); + NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId); + return socket; + } + } + + /** * Returns a {@link SocketFactory} bound to this network. Any {@link Socket} created by * this factory will have its traffic sent over this {@code Network}. Note that if this * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the @@ -88,7 +146,10 @@ public class Network implements Parcelable { * {@code Network}. */ public SocketFactory socketFactory() { - return null; + if (mNetworkBoundSocketFactory == null) { + mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId); + } + return mNetworkBoundSocketFactory; } /** @@ -99,6 +160,29 @@ public class Network implements Parcelable { * doesn't accidentally use sockets it thinks are still bound to a particular {@code Network}. */ public void bindProcess() { + NetworkUtils.bindProcessToNetwork(netId); + } + + /** + * Binds host resolutions performed by this process to this network. {@link #bindProcess} + * takes precedence over this setting. + * + * @hide + * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature(). + */ + public void bindProcessForHostResolution() { + NetworkUtils.bindProcessToNetworkForHostResolution(netId); + } + + /** + * Clears any process specific {@link Network} binding for host resolution. This does + * not clear bindings enacted via {@link #bindProcess}. + * + * @hide + * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature(). + */ + public void unbindProcessForHostResolution() { + NetworkUtils.unbindProcessToNetworkForHostResolution(); } /** @@ -107,7 +191,7 @@ public class Network implements Parcelable { * @return {@code Network} to which this process is bound. */ public static Network getProcessBoundNetwork() { - return null; + return new Network(NetworkUtils.getNetworkBoundToProcess()); } /** @@ -115,6 +199,7 @@ public class Network implements Parcelable { * {@link Network#bindProcess}. */ public static void unbindProcess() { + NetworkUtils.unbindProcessToNetwork(); } // implement the Parcelable interface diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index b24d396..edb3286 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -109,6 +109,50 @@ public class NetworkUtils { public native static void markSocket(int socketfd, int mark); /** + * 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#socketFactory}) will be bound to this network. Note that if this + * {@code Network} ever disconnects all sockets created in this way will cease to work. This + * is by design so an application doesn't accidentally use sockets it thinks are still bound to + * a particular {@code Network}. + */ + public native static void bindProcessToNetwork(int netId); + + /** + * Clear any process specific {@code Network} binding. This reverts a call to + * {@link #bindProcessToNetwork}. + */ + public native static void unbindProcessToNetwork(); + + /** + * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if + * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}. + */ + public native static int getNetworkBoundToProcess(); + + /** + * Binds host resolutions performed by this process to the network designated by {@code netId}. + * {@link #bindProcessToNetwork} takes precedence over this setting. + * + * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature(). + */ + public native static void bindProcessToNetworkForHostResolution(int netId); + + /** + * Clears any process specific {@link Network} binding for host resolution. This does + * not clear bindings enacted via {@link #bindProcessToNetwork}. + * + * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature(). + */ + public native static void unbindProcessToNetworkForHostResolution(); + + /** + * Explicitly binds {@code socketfd} to the network designated by {@code netId}. This + * overrides any binding via {@link #bindProcessToNetwork}. + */ + public native static void bindSocketToNetwork(int socketfd, int netId); + + /** * Convert a IPv4 address from an integer to an InetAddress. * @param hostAddress an int corresponding to the IPv4 address in network byte order */ diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java new file mode 100644 index 0000000..7f8bc9f --- /dev/null +++ b/core/java/android/os/FileBridge.java @@ -0,0 +1,165 @@ +/* + * 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.os; + +import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.SOCK_STREAM; + +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import libcore.io.IoBridge; +import libcore.io.IoUtils; +import libcore.io.Memory; +import libcore.io.Streams; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.OutputStream; +import java.io.SyncFailedException; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Simple bridge that allows file access across process boundaries without + * returning the underlying {@link FileDescriptor}. This is useful when the + * server side needs to strongly assert that a client side is completely + * hands-off. + * + * @hide + */ +public class FileBridge extends Thread { + private static final String TAG = "FileBridge"; + + // TODO: consider extending to support bidirectional IO + + private static final int MSG_LENGTH = 8; + + /** CMD_WRITE [len] [data] */ + private static final int CMD_WRITE = 1; + /** CMD_FSYNC */ + private static final int CMD_FSYNC = 2; + + private FileDescriptor mTarget; + + private final FileDescriptor mServer = new FileDescriptor(); + private final FileDescriptor mClient = new FileDescriptor(); + + private volatile boolean mClosed; + + public FileBridge() { + try { + Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient); + } catch (ErrnoException e) { + throw new RuntimeException("Failed to create bridge"); + } + } + + public boolean isClosed() { + return mClosed; + } + + public void setTargetFile(FileDescriptor target) { + mTarget = target; + } + + public FileDescriptor getClientSocket() { + return mClient; + } + + @Override + public void run() { + final byte[] temp = new byte[8192]; + try { + while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) { + final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN); + + if (cmd == CMD_WRITE) { + // Shuttle data into local file + int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN); + while (len > 0) { + int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len)); + IoBridge.write(mTarget, temp, 0, n); + len -= n; + } + + } else if (cmd == CMD_FSYNC) { + // Sync and echo back to confirm + Os.fsync(mTarget); + IoBridge.write(mServer, temp, 0, MSG_LENGTH); + } + } + + // Client was closed; one last fsync + Os.fsync(mTarget); + + } catch (ErrnoException e) { + Log.e(TAG, "Failed during bridge: ", e); + } catch (IOException e) { + Log.e(TAG, "Failed during bridge: ", e); + } finally { + IoUtils.closeQuietly(mTarget); + IoUtils.closeQuietly(mServer); + IoUtils.closeQuietly(mClient); + mClosed = true; + } + } + + public static class FileBridgeOutputStream extends OutputStream { + private final FileDescriptor mClient; + private final byte[] mTemp = new byte[MSG_LENGTH]; + + public FileBridgeOutputStream(FileDescriptor client) { + mClient = client; + } + + @Override + public void close() throws IOException { + IoBridge.closeAndSignalBlockedThreads(mClient); + } + + @Override + public void flush() throws IOException { + Memory.pokeInt(mTemp, 0, CMD_FSYNC, ByteOrder.BIG_ENDIAN); + IoBridge.write(mClient, mTemp, 0, MSG_LENGTH); + + // Wait for server to ack + if (IoBridge.read(mClient, mTemp, 0, MSG_LENGTH) == MSG_LENGTH) { + if (Memory.peekInt(mTemp, 0, ByteOrder.BIG_ENDIAN) == CMD_FSYNC) { + return; + } + } + + throw new SyncFailedException("Failed to fsync() across bridge"); + } + + @Override + public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException { + Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount); + Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN); + Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN); + IoBridge.write(mClient, mTemp, 0, MSG_LENGTH); + IoBridge.write(mClient, buffer, byteOffset, byteCount); + } + + @Override + public void write(int oneByte) throws IOException { + Streams.writeSingleByte(this, oneByte); + } + } +} diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java index 5ffffb5..e4f93a8 100644 --- a/core/java/android/provider/TvContract.java +++ b/core/java/android/provider/TvContract.java @@ -462,7 +462,7 @@ public final class TvContract { * <p> * A value of 1 indicates the channel is included in the channel list that applications use * to browse channels, a value of 0 indicates the channel is not included in the list. If - * not specified, this value is set to 1 by default. + * not specified, this value is set to 1 (browsable) by default. * </p><p> * Type: INTEGER (boolean) * </p> @@ -470,6 +470,36 @@ public final class TvContract { public static final String COLUMN_BROWSABLE = "browsable"; /** + * The flag indicating whether this TV channel is searchable or not. + * <p> + * In some regions, it is not allowed to surface search results for a given channel without + * broadcaster's consent. This is used to impose such restriction. A value of 1 indicates + * the channel is searchable and can be included in search results, a value of 0 indicates + * the channel and its TV programs are hidden from search. If not specified, this value is + * set to 1 (searchable) by default. + * </p> + * <p> + * Type: INTEGER (boolean) + * </p> + */ + public static final String COLUMN_SEARCHABLE = "searchable"; + + /** + * The flag indicating whether this TV channel is locked or not. + * <p> + * This is primarily used for alternative parental control to prevent unauthorized users + * from watching the current channel regardless of the content rating. A value of 1 + * indicates the channel is locked and the user is required to enter passcode to unlock it + * in order to watch the current program from the channel, a value of 0 indicates the + * channel is not locked thus the user is not prompted to enter passcode If not specified, + * this value is set to 0 (not locked) by default. + * </p><p> + * Type: INTEGER (boolean) + * </p> + */ + public static final String COLUMN_LOCKED = "locked"; + + /** * Generic data used by individual TV input services. * <p> * Type: BLOB @@ -544,6 +574,33 @@ public final class TvContract { public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; /** + * The comma-separated genre string of this TV program. + * <p> + * Use the same language appeared in the underlying broadcast standard, if applicable. (For + * example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or + * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, use one of the + * following genres: + * <ul> + * <li>Family/Kids</li> + * <li>Sports</li> + * <li>Shopping</li> + * <li>Movies</li> + * <li>Comedy</li> + * <li>Travel</li> + * <li>Drama</li> + * <li>Education</li> + * <li>Animal/Wildlife</li> + * <li>News</li> + * <li>Gaming</li> + * <li>Others</li> + * </ul> + * </p><p> + * Type: TEXT + * </p> + */ + public static final String COLUMN_GENRE = "genre"; + + /** * The description of this TV program that is displayed to the user by default. * <p> * The maximum length of this field is 256 characters. @@ -566,6 +623,17 @@ public final class TvContract { public static final String COLUMN_LONG_DESCRIPTION = "long_description"; /** + * The comma-separated audio languages of this TV program. + * <p> + * This is used to describe available audio languages included in the program. Use + * 3-character language code as specified by ISO 639-2. + * </p><p> + * Type: TEXT + * </p> + */ + public static final String COLUMN_AUDIO_LANGUAGE = "audio_language"; + + /** * Generic data used by TV input services. * <p> * Type: BLOB diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index a272296..424d860 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -41,10 +41,6 @@ import android.text.TextUtils; * An implementation of Canvas on top of OpenGL ES 2.0. */ class GLES20Canvas extends HardwareCanvas { - // Must match modifiers used in the JNI layer - private static final int MODIFIER_NONE = 0; - private static final int MODIFIER_SHADER = 2; - private final boolean mOpaque; protected long mRenderer; @@ -650,13 +646,8 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom, - startAngle, sweepAngle, useCenter, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom, + startAngle, sweepAngle, useCenter, paint.mNativePaint); } private static native void nDrawArc(long renderer, float left, float top, @@ -672,7 +663,6 @@ class GLES20Canvas extends HardwareCanvas { public void drawPatch(NinePatch patch, Rect dst, Paint paint) { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); - // Shaders are ignored when drawing patches final long nativePaint = paint == null ? 0 : paint.mNativePaint; nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); @@ -682,7 +672,6 @@ class GLES20Canvas extends HardwareCanvas { public void drawPatch(NinePatch patch, RectF dst, Paint paint) { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); - // Shaders are ignored when drawing patches final long nativePaint = paint == null ? 0 : paint.mNativePaint; nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); @@ -694,14 +683,8 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { throwIfCannotDraw(bitmap); - // Shaders are ignored when drawing bitmaps - int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint); } private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer, @@ -710,15 +693,9 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { throwIfCannotDraw(bitmap); - // Shaders are ignored when drawing bitmaps - int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, - matrix.native_instance, nativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, + matrix.native_instance, nativePaint); } private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer, @@ -727,55 +704,43 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { throwIfCannotDraw(bitmap); - // Shaders are ignored when drawing bitmaps - int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - - int left, top, right, bottom; - if (src == null) { - left = top = 0; - right = bitmap.getWidth(); - bottom = bitmap.getHeight(); - } else { - left = src.left; - right = src.right; - top = src.top; - bottom = src.bottom; - } + final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom, - dst.left, dst.top, dst.right, dst.bottom, nativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); + int left, top, right, bottom; + if (src == null) { + left = top = 0; + right = bitmap.getWidth(); + bottom = bitmap.getHeight(); + } else { + left = src.left; + right = src.right; + top = src.top; + bottom = src.bottom; } + + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom, + dst.left, dst.top, dst.right, dst.bottom, nativePaint); } @Override public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { throwIfCannotDraw(bitmap); - // Shaders are ignored when drawing bitmaps - int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - - float left, top, right, bottom; - if (src == null) { - left = top = 0; - right = bitmap.getWidth(); - bottom = bitmap.getHeight(); - } else { - left = src.left; - right = src.right; - top = src.top; - bottom = src.bottom; - } - - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom, - dst.left, dst.top, dst.right, dst.bottom, nativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + + float left, top, right, bottom; + if (src == null) { + left = top = 0; + right = bitmap.getWidth(); + bottom = bitmap.getHeight(); + } else { + left = src.left; + right = src.right; + top = src.top; + bottom = src.bottom; } + + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom, + dst.left, dst.top, dst.right, dst.bottom, nativePaint); } private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer, @@ -805,7 +770,6 @@ class GLES20Canvas extends HardwareCanvas { throw new ArrayIndexOutOfBoundsException(); } - // Shaders are ignored when drawing bitmaps final long nativePaint = paint == null ? 0 : paint.mNativePaint; nDrawBitmap(mRenderer, colors, offset, stride, x, y, width, height, hasAlpha, nativePaint); @@ -817,7 +781,6 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, int height, boolean hasAlpha, Paint paint) { - // Shaders are ignored when drawing bitmaps drawBitmap(colors, offset, stride, (float) x, (float) y, width, height, hasAlpha, paint); } @@ -840,14 +803,9 @@ class GLES20Canvas extends HardwareCanvas { checkRange(colors.length, colorOffset, count); } - int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; - try { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight, - verts, vertOffset, colors, colorOffset, nativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + final long nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight, + verts, vertOffset, colors, colorOffset, nativePaint); } private static native void nDrawBitmapMesh(long renderer, long bitmap, byte[] buffer, @@ -856,12 +814,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawCircle(float cx, float cy, float radius, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint); } private static native void nDrawCircle(long renderer, float cx, float cy, @@ -906,12 +859,7 @@ class GLES20Canvas extends HardwareCanvas { if ((offset | count) < 0 || offset + count > pts.length) { throw new IllegalArgumentException("The lines array must contain 4 elements per line."); } - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint); } private static native void nDrawLines(long renderer, float[] points, @@ -924,12 +872,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawOval(RectF oval, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint); } private static native void nDrawOval(long renderer, float left, float top, @@ -944,17 +887,12 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPath(Path path, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - if (path.isSimplePath) { - if (path.rects != null) { - nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint); - } - } else { - nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint); + if (path.isSimplePath) { + if (path.rects != null) { + nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint); } - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); + } else { + nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint); } } @@ -962,12 +900,7 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawRects(long renderer, long region, long paint); void drawRects(float[] rects, int count, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - nDrawRects(mRenderer, rects, count, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawRects(mRenderer, rects, count, paint.mNativePaint); } private static native void nDrawRects(long renderer, float[] rects, int count, long paint); @@ -1029,12 +962,7 @@ class GLES20Canvas extends HardwareCanvas { public void drawPoints(float[] pts, int offset, int count, Paint paint) { if (count < 2) return; - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint); } private static native void nDrawPoints(long renderer, float[] points, @@ -1047,12 +975,7 @@ class GLES20Canvas extends HardwareCanvas { throw new IndexOutOfBoundsException(); } - int modifiers = setupModifiers(paint); - try { - nDrawPosText(mRenderer, text, index, count, pos, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawPosText(mRenderer, text, index, count, pos, paint.mNativePaint); } private static native void nDrawPosText(long renderer, char[] text, int index, int count, @@ -1065,12 +988,7 @@ class GLES20Canvas extends HardwareCanvas { throw new ArrayIndexOutOfBoundsException(); } - int modifiers = setupModifiers(paint); - try { - nDrawPosText(mRenderer, text, 0, text.length(), pos, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawPosText(mRenderer, text, 0, text.length(), pos, paint.mNativePaint); } private static native void nDrawPosText(long renderer, String text, int start, int end, @@ -1079,12 +997,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawRect(float left, float top, float right, float bottom, Paint paint) { if (left == right || top == bottom) return; - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint); } private static native void nDrawRect(long renderer, float left, float top, @@ -1108,12 +1021,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) { - int modifiers = setupModifiers(paint, MODIFIER_SHADER); - try { - nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint); } private static native void nDrawRoundRect(long renderer, float left, float top, @@ -1125,13 +1033,8 @@ class GLES20Canvas extends HardwareCanvas { throw new IndexOutOfBoundsException(); } - int modifiers = setupModifiers(paint); - try { - nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint, - paint.mNativeTypeface); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawText(mRenderer, text, index, count, x, y, + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } private static native void nDrawText(long renderer, char[] text, int index, int count, @@ -1139,24 +1042,18 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { - int modifiers = setupModifiers(paint); - try { - if (text instanceof String || text instanceof SpannedString || - text instanceof SpannableString) { - nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags, - paint.mNativePaint, paint.mNativeTypeface); - } else if (text instanceof GraphicsOperations) { - ((GraphicsOperations) text).drawText(this, start, end, x, y, - paint); - } else { - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - nDrawText(mRenderer, buf, 0, end - start, x, y, - paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); - TemporaryBuffer.recycle(buf); - } - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); + if (text instanceof String || text instanceof SpannedString || + text instanceof SpannableString) { + nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags, + paint.mNativePaint, paint.mNativeTypeface); + } else if (text instanceof GraphicsOperations) { + ((GraphicsOperations) text).drawText(this, start, end, x, y, paint); + } else { + char[] buf = TemporaryBuffer.obtain(end - start); + TextUtils.getChars(text, start, end, buf, 0); + nDrawText(mRenderer, buf, 0, end - start, x, y, + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); + TemporaryBuffer.recycle(buf); } } @@ -1166,13 +1063,8 @@ class GLES20Canvas extends HardwareCanvas { throw new IndexOutOfBoundsException(); } - int modifiers = setupModifiers(paint); - try { - nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint, - paint.mNativeTypeface); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawText(mRenderer, text, start, end, x, y, + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } private static native void nDrawText(long renderer, String text, int start, int end, @@ -1180,13 +1072,8 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawText(String text, float x, float y, Paint paint) { - int modifiers = setupModifiers(paint); - try { - nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags, - paint.mNativePaint, paint.mNativeTypeface); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawText(mRenderer, text, 0, text.length(), x, y, + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } @Override @@ -1196,13 +1083,8 @@ class GLES20Canvas extends HardwareCanvas { throw new ArrayIndexOutOfBoundsException(); } - int modifiers = setupModifiers(paint); - try { - nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset, + paint.mBidiFlags, paint.mNativePaint); } private static native void nDrawTextOnPath(long renderer, char[] text, int index, int count, @@ -1212,13 +1094,8 @@ class GLES20Canvas extends HardwareCanvas { public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { if (text.length() == 0) return; - int modifiers = setupModifiers(paint); - try { - nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset, + paint.mBidiFlags, paint.mNativePaint); } private static native void nDrawTextOnPath(long renderer, String text, int start, int end, @@ -1234,13 +1111,8 @@ class GLES20Canvas extends HardwareCanvas { throw new IllegalArgumentException("Unknown direction: " + dir); } - int modifiers = setupModifiers(paint); - try { - nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir, - paint.mNativePaint, paint.mNativeTypeface); - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); - } + nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir, + paint.mNativePaint, paint.mNativeTypeface); } private static native void nDrawTextRun(long renderer, char[] text, int index, int count, @@ -1253,27 +1125,22 @@ class GLES20Canvas extends HardwareCanvas { throw new IndexOutOfBoundsException(); } - int modifiers = setupModifiers(paint); - try { - int flags = dir == 0 ? 0 : 1; - if (text instanceof String || text instanceof SpannedString || - text instanceof SpannableString) { - nDrawTextRun(mRenderer, text.toString(), start, end, contextStart, - contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface); - } else if (text instanceof GraphicsOperations) { - ((GraphicsOperations) text).drawTextRun(this, start, end, - contextStart, contextEnd, x, y, flags, paint); - } else { - int contextLen = contextEnd - contextStart; - int len = end - start; - char[] buf = TemporaryBuffer.obtain(contextLen); - TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen, - x, y, flags, paint.mNativePaint, paint.mNativeTypeface); - TemporaryBuffer.recycle(buf); - } - } finally { - if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); + int flags = dir == 0 ? 0 : 1; + if (text instanceof String || text instanceof SpannedString || + text instanceof SpannableString) { + nDrawTextRun(mRenderer, text.toString(), start, end, contextStart, + contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface); + } else if (text instanceof GraphicsOperations) { + ((GraphicsOperations) text).drawTextRun(this, start, end, + contextStart, contextEnd, x, y, flags, paint); + } else { + int contextLen = contextEnd - contextStart; + int len = end - start; + char[] buf = TemporaryBuffer.obtain(contextLen); + TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen, + x, y, flags, paint.mNativePaint, paint.mNativeTypeface); + TemporaryBuffer.recycle(buf); } } @@ -1286,40 +1153,4 @@ class GLES20Canvas extends HardwareCanvas { int indexOffset, int indexCount, Paint paint) { // TODO: Implement } - - private int setupModifiers(Bitmap b, Paint paint) { - if (b.getConfig() != Bitmap.Config.ALPHA_8) { - return MODIFIER_NONE; - } else { - return setupModifiers(paint); - } - } - - private int setupModifiers(Paint paint) { - int modifiers = MODIFIER_NONE; - - final Shader shader = paint.getShader(); - if (shader != null) { - nSetupShader(mRenderer, shader.native_shader); - modifiers |= MODIFIER_SHADER; - } - - return modifiers; - } - - private int setupModifiers(Paint paint, int flags) { - int modifiers = MODIFIER_NONE; - - final Shader shader = paint.getShader(); - if (shader != null && (flags & MODIFIER_SHADER) != 0) { - nSetupShader(mRenderer, shader.native_shader); - modifiers |= MODIFIER_SHADER; - } - - return modifiers; - } - - private static native void nSetupShader(long renderer, long shader); - - private static native void nResetModifiers(long renderer, int modifiers); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0f21c1d..6396781 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9236,6 +9236,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Request unbuffered dispatch of the given stream of MotionEvents to this View. + * + * Until this View receives a corresponding {@link MotionEvent#ACTION_UP}, ask that the input + * system not batch {@link MotionEvent}s but instead deliver them as soon as they're + * available. This method should only be called for touch events. + * + * <p class="note">This api is not intended for most applications. Buffered dispatch + * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent + * streams will not improve your input latency. Side effects include: increased latency, + * jittery scrolls and inability to take advantage of system resampling. Talk to your input + * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for + * you.</p> + */ + public final void requestUnbufferedDispatch(MotionEvent event) { + final int action = event.getAction(); + if (mAttachInfo == null + || action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_MOVE + || !event.isTouchEvent()) { + return; + } + mAttachInfo.mUnbufferedDispatchRequested = true; + } + + /** * Set flags controlling behavior of this view. * * @param flags Constant indicating the value which should be set @@ -19760,6 +19784,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean mInTouchMode; /** + * Indicates whether the view has requested unbuffered input dispatching for the current + * event stream. + */ + boolean mUnbufferedDispatchRequested; + + /** * Indicates that ViewAncestor should trigger a global layout change * the next time it performs a traversal */ diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 799a406..148d9ec 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -230,6 +230,7 @@ public final class ViewRootImpl implements ViewParent, QueuedInputEvent mPendingInputEventTail; int mPendingInputEventCount; boolean mProcessInputEventsScheduled; + boolean mUnbufferedInputDispatch; String mPendingInputEventQueueLengthCounterName = "pq"; InputStage mFirstInputStage; @@ -1005,7 +1006,9 @@ public final class ViewRootImpl implements ViewParent, mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); - scheduleConsumeBatchedInput(); + if (!mUnbufferedInputDispatch) { + scheduleConsumeBatchedInput(); + } } } @@ -2604,7 +2607,7 @@ public final class ViewRootImpl implements ViewParent, } final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); - final Rect bounds = mView.mAttachInfo.mTmpInvalRect; + final Rect bounds = mAttachInfo.mTmpInvalRect; if (provider == null) { host.getBoundsOnScreen(bounds); } else if (mAccessibilityFocusedVirtualView != null) { @@ -3886,6 +3889,18 @@ public final class ViewRootImpl implements ViewParent, } } + @Override + protected void onDeliverToNext(QueuedInputEvent q) { + if (mUnbufferedInputDispatch + && q.mEvent instanceof MotionEvent + && ((MotionEvent)q.mEvent).isTouchEvent() + && isTerminalInputEvent(q.mEvent)) { + mUnbufferedInputDispatch = false; + scheduleConsumeBatchedInput(); + } + super.onDeliverToNext(q); + } + private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; @@ -3998,10 +4013,15 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; - if (mView.dispatchPointerEvent(event)) { - return FINISH_HANDLED; + mAttachInfo.mUnbufferedDispatchRequested = false; + boolean handled = mView.dispatchPointerEvent(event); + if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) { + mUnbufferedInputDispatch = true; + if (mConsumeBatchedInputScheduled) { + scheduleConsumeBatchedInputImmediately(); + } } - return FORWARD; + return handled ? FINISH_HANDLED : FORWARD; } private int processTrackballEvent(QueuedInputEvent q) { @@ -5266,6 +5286,8 @@ public final class ViewRootImpl implements ViewParent, writer.print(" mRemoved="); writer.println(mRemoved); writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled="); writer.println(mConsumeBatchedInputScheduled); + writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled="); + writer.println(mConsumeBatchedInputImmediatelyScheduled); writer.print(innerPrefix); writer.print("mPendingInputEventCount="); writer.println(mPendingInputEventCount); writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled="); @@ -5676,6 +5698,7 @@ public final class ViewRootImpl implements ViewParent, private void finishInputEvent(QueuedInputEvent q) { Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent", q.mEvent.getSequenceNumber()); + if (q.mReceiver != null) { boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0; q.mReceiver.finishInputEvent(q.mEvent, handled); @@ -5715,15 +5738,25 @@ public final class ViewRootImpl implements ViewParent, } } + void scheduleConsumeBatchedInputImmediately() { + if (!mConsumeBatchedInputImmediatelyScheduled) { + unscheduleConsumeBatchedInput(); + mConsumeBatchedInputImmediatelyScheduled = true; + mHandler.post(mConsumeBatchedInputImmediatelyRunnable); + } + } + void doConsumeBatchedInput(long frameTimeNanos) { if (mConsumeBatchedInputScheduled) { mConsumeBatchedInputScheduled = false; if (mInputEventReceiver != null) { - if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)) { + if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos) + && frameTimeNanos != -1) { // If we consumed a batch here, we want to go ahead and schedule the // consumption of batched input events on the next frame. Otherwise, we would // wait until we have more input events pending and might get starved by other - // things occurring in the process. + // things occurring in the process. If the frame time is -1, however, then + // we're in a non-batching mode, so there's no need to schedule this. scheduleConsumeBatchedInput(); } } @@ -5751,7 +5784,11 @@ public final class ViewRootImpl implements ViewParent, @Override public void onBatchedInputEventPending() { - scheduleConsumeBatchedInput(); + if (mUnbufferedInputDispatch) { + super.onBatchedInputEventPending(); + } else { + scheduleConsumeBatchedInput(); + } } @Override @@ -5772,6 +5809,16 @@ public final class ViewRootImpl implements ViewParent, new ConsumeBatchedInputRunnable(); boolean mConsumeBatchedInputScheduled; + final class ConsumeBatchedInputImmediatelyRunnable implements Runnable { + @Override + public void run() { + doConsumeBatchedInput(-1); + } + } + final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable = + new ConsumeBatchedInputImmediatelyRunnable(); + boolean mConsumeBatchedInputImmediatelyScheduled; + final class InvalidateOnAnimationRunnable implements Runnable { private boolean mPosted; private final ArrayList<View> mViews = new ArrayList<View>(); diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 8511601..97f89aa 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -104,14 +104,16 @@ import static java.lang.Math.min; * * <h4>Excess Space Distribution</h4> * - * GridLayout's distribution of excess space is based on <em>priority</em> - * rather than <em>weight</em>. + * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight. + * In the event that no weights are specified, the previous conventions are respected and + * columns and rows are taken as flexible if their views specify some form of alignment + * within their groups. * <p> - * A child's ability to stretch is inferred from the alignment properties of - * its row and column groups (which are typically set by setting the - * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters). - * If alignment was defined along a given axis then the component - * is taken as <em>flexible</em> in that direction. If no alignment was set, + * The flexibility of a view is therefore influenced by its alignment which is, + * in turn, typically defined by setting the + * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters. + * If either a weight or alignment were defined along a given axis then the component + * is taken as <em>flexible</em> in that direction. If no weight or alignment was set, * the component is instead assumed to be <em>inflexible</em>. * <p> * Multiple components in the same row or column group are @@ -122,12 +124,16 @@ import static java.lang.Math.min; * elements is flexible if <em>one</em> of its elements is flexible. * <p> * To make a column stretch, make sure all of the components inside it define a - * gravity. To prevent a column from stretching, ensure that one of the components - * in the column does not define a gravity. + * weight or a gravity. To prevent a column from stretching, ensure that one of the components + * in the column does not define a weight or a gravity. * <p> * When the principle of flexibility does not provide complete disambiguation, * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em> - * and <em>bottom</em> edges. + * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout + * parameters as a constraint in the a set of variables that define the grid-lines along a + * given axis. During layout, GridLayout solves the constraints so as to return the unique + * solution to those constraints for which all variables are less-than-or-equal-to + * the corresponding value in any other valid solution. * * <h4>Interpretation of GONE</h4> * @@ -140,18 +146,6 @@ import static java.lang.Math.min; * had never been added to it. * These statements apply equally to rows as well as columns, and to groups of rows or columns. * - * <h5>Limitations</h5> - * - * GridLayout does not provide support for the principle of <em>weight</em>, as defined in - * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible - * to configure a GridLayout to distribute excess space between multiple components. - * <p> - * Some common use-cases may nevertheless be accommodated as follows. - * To place equal amounts of space around a component in a cell group; - * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}). - * For complete control over excess space distribution in a row or column; - * use a {@link LinearLayout} subview to hold the components in the associated cell group. - * When using either of these techniques, bear in mind that cell groups may be defined to overlap. * <p> * See {@link GridLayout.LayoutParams} for a full description of the * layout parameters used by GridLayout. @@ -1693,8 +1687,74 @@ public class GridLayout extends ViewGroup { return trailingMargins; } - private void computeLocations(int[] a) { + private void solve(int[] a) { solve(getArcs(), a); + } + + private void resetDeltas() { + Bounds[] values = getGroupBounds().values; + for (int i = 0, N = values.length; i < N; i++) { + values[i].shareOfDelta = 0; + } + } + + private boolean requiresWeightDistribution() { + Bounds[] values = getGroupBounds().values; + for (int i = 0, N = values.length; i < N; i++) { + if (values[i].weight != 0) { + return true; + } + } + return false; + } + + private void shareOutDelta(int[] locations) { + PackedMap<Spec, Bounds> groupBounds = getGroupBounds(); + Spec[] specs = groupBounds.keys; + Bounds[] bounds = groupBounds.values; + int totalDelta = 0; + float totalWeight = 0; + final int N = bounds.length; + + for (int i = 0; i < N; i++) { + Spec spec = specs[i]; + Bounds bound = bounds[i]; + if (bound.weight != 0) { + int minSize = bound.size(true); + Interval span = spec.span; + int actualSize = locations[span.max] - locations[span.min]; + int delta = actualSize - minSize; + totalDelta += delta; + totalWeight += bound.weight; + } + } + for (int i = 0; i < N; i++) { + Bounds bound = bounds[i]; + float weight = bound.weight; + if (weight != 0) { + int delta = Math.round((weight * totalDelta / totalWeight)); + bound.shareOfDelta = delta; + // the two adjustments below compensate for the rounding above + totalDelta -= delta; + totalWeight -= weight; + } + } + } + + private void solveAndDistributeSpace(int[] a) { + resetDeltas(); + solve(a); + if (requiresWeightDistribution()) { + shareOutDelta(a); + arcsValid = false; + forwardLinksValid = false; + backwardLinksValid = false; + solve(a); + } + } + + private void computeLocations(int[] a) { + solveAndDistributeSpace(a); if (!orderPreserved) { // Solve returns the smallest solution to the constraint system for which all // values are positive. One value is therefore zero - though if the row/col @@ -1810,6 +1870,23 @@ public class GridLayout extends ViewGroup { * both aspects of alignment within the cell group. It is also possible to specify a child's * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} * method. + * <p> + * The weight property is also included in Spec and specifies the proportion of any + * excess space that is due to the enclosing row or column. + * GridLayout's model of flexibility and weight is broadly based on the physical properties of + * systems of mechanical springs. For example, a column in a GridLayout is modeled as + * if a set of springs were attached in parallel at their ends. Columns are therefore + * flexible only if and only if all views inside them are flexible. Similarly, the combined + * weight of a column is defined as the <em>minimum</em> of the weights of the components it + * contains. So, if any one component in a column of components has a weight of zero, + * the entire group has a weight of zero and will not receive any of the + * excess space that GridLayout distributes in its second + * (internal) layout pass. The default weight of a component is zero, + * reflecting inflexibility, but the default weight of a column (with nothing in it) + * is effectively infinite, reflecting the fact that a parallel group of + * springs is infinitely flexible when it contains no springs. + * <p> + * The above comments apply equally to rows and groups of rows and columns. * * <h4>WRAP_CONTENT and MATCH_PARENT</h4> * @@ -1851,9 +1928,11 @@ public class GridLayout extends ViewGroup { * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li> * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li> * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li> + * <li>{@link #rowSpec}<code>.weight</code> = 0 </li> * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li> * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li> * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li> + * <li>{@link #columnSpec}<code>.weight</code> = 0 </li> * </ul> * * See {@link GridLayout} for a more complete description of the conventions @@ -1861,8 +1940,10 @@ public class GridLayout extends ViewGroup { * * @attr ref android.R.styleable#GridLayout_Layout_layout_row * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan + * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight * @attr ref android.R.styleable#GridLayout_Layout_layout_column * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan + * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity */ public static class LayoutParams extends MarginLayoutParams { @@ -1889,9 +1970,11 @@ public class GridLayout extends ViewGroup { private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column; private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; + private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight; private static final int ROW = R.styleable.GridLayout_Layout_layout_row; private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; + private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight; private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity; @@ -2034,11 +2117,13 @@ public class GridLayout extends ViewGroup { int column = a.getInt(COLUMN, DEFAULT_COLUMN); int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); - this.columnSpec = spec(column, colSpan, getAlignment(gravity, true)); + float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT); + this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight); int row = a.getInt(ROW, DEFAULT_ROW); int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); - this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false)); + float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT); + this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight); } finally { a.recycle(); } @@ -2244,6 +2329,8 @@ public class GridLayout extends ViewGroup { public int before; public int after; public int flexibility; // we're flexible iff all included specs are flexible + public float weight; // the min of the weights of the individual specs + public int shareOfDelta; private Bounds() { reset(); @@ -2253,6 +2340,7 @@ public class GridLayout extends ViewGroup { before = Integer.MIN_VALUE; after = Integer.MIN_VALUE; flexibility = CAN_STRETCH; // from the above, we're flexible when empty + weight = Float.MAX_VALUE; // default is large => row/cols take all slack when empty } protected void include(int before, int after) { @@ -2266,7 +2354,7 @@ public class GridLayout extends ViewGroup { return MAX_SIZE; } } - return before + after; + return before + after + shareOfDelta; } protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) { @@ -2275,6 +2363,7 @@ public class GridLayout extends ViewGroup { protected final void include(GridLayout gl, View c, Spec spec, Axis axis) { this.flexibility &= spec.getFlexibility(); + weight = Math.min(weight, spec.weight); boolean horizontal = axis.horizontal; int size = gl.getMeasurementIncludingMargin(c, horizontal); Alignment alignment = gl.getAlignment(spec.alignment, horizontal); @@ -2401,36 +2490,43 @@ public class GridLayout extends ViewGroup { * <li>{@link #spec(int, int)}</li> * <li>{@link #spec(int, Alignment)}</li> * <li>{@link #spec(int, int, Alignment)}</li> + * <li>{@link #spec(int, float)}</li> + * <li>{@link #spec(int, int, float)}</li> + * <li>{@link #spec(int, Alignment, float)}</li> + * <li>{@link #spec(int, int, Alignment, float)}</li> * </ul> * */ public static class Spec { static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); + static final float DEFAULT_WEIGHT = 0; final boolean startDefined; final Interval span; final Alignment alignment; + final float weight; - private Spec(boolean startDefined, Interval span, Alignment alignment) { + private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) { this.startDefined = startDefined; this.span = span; this.alignment = alignment; + this.weight = weight; } - private Spec(boolean startDefined, int start, int size, Alignment alignment) { - this(startDefined, new Interval(start, start + size), alignment); + private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) { + this(startDefined, new Interval(start, start + size), alignment, weight); } final Spec copyWriteSpan(Interval span) { - return new Spec(startDefined, span, alignment); + return new Spec(startDefined, span, alignment, weight); } final Spec copyWriteAlignment(Alignment alignment) { - return new Spec(startDefined, span, alignment); + return new Spec(startDefined, span, alignment, weight); } final int getFlexibility() { - return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH; + return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH; } /** @@ -2478,6 +2574,7 @@ public class GridLayout extends ViewGroup { * <ul> * <li> {@code spec.span = [start, start + size]} </li> * <li> {@code spec.alignment = alignment} </li> + * <li> {@code spec.weight = weight} </li> * </ul> * <p> * To leave the start index undefined, use the value {@link #UNDEFINED}. @@ -2485,9 +2582,55 @@ public class GridLayout extends ViewGroup { * @param start the start * @param size the size * @param alignment the alignment + * @param weight the weight + */ + public static Spec spec(int start, int size, Alignment alignment, float weight) { + return new Spec(start != UNDEFINED, start, size, alignment, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, alignment, weight)}. + * + * @param start the start + * @param alignment the alignment + * @param weight the weight + */ + public static Spec spec(int start, Alignment alignment, float weight) { + return spec(start, 1, alignment, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, default_alignment, weight)} - + * where {@code default_alignment} is specified in + * {@link android.widget.GridLayout.LayoutParams}. + * + * @param start the start + * @param size the size + * @param weight the weight + */ + public static Spec spec(int start, int size, float weight) { + return spec(start, size, UNDEFINED_ALIGNMENT, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, weight)}. + * + * @param start the start + * @param weight the weight + */ + public static Spec spec(int start, float weight) { + return spec(start, 1, weight); + } + + /** + * Equivalent to: {@code spec(start, size, alignment, 0f)}. + * + * @param start the start + * @param size the size + * @param alignment the alignment */ public static Spec spec(int start, int size, Alignment alignment) { - return new Spec(start != UNDEFINED, start, size, alignment); + return spec(start, size, alignment, Spec.DEFAULT_WEIGHT); } /** diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 495d5c6..70c77b8 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -19,6 +19,7 @@ package com.android.internal.inputmethod; import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -211,54 +212,111 @@ public class InputMethodSubtypeSwitchingController { } } - private final InputMethodSettings mSettings; - private InputMethodAndSubtypeList mSubtypeList; + private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { + return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, + subtype.hashCode()) : NOT_A_SUBTYPE_ID; + } - @VisibleForTesting - public static ImeSubtypeListItem getNextInputMethodLockedImpl(List<ImeSubtypeListItem> imList, - boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { - if (imi == null) { - return null; + private static class StaticRotationList { + private final List<ImeSubtypeListItem> mImeSubtypeList; + public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { + mImeSubtypeList = imeSubtypeList; } - if (imList.size() <= 1) { - return null; + + /** + * Returns the index of the specified input method and subtype in the given list. + * @param imi The {@link InputMethodInfo} to be searched. + * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method + * does not have a subtype. + * @return The index in the given list. -1 if not found. + */ + private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; ++i) { + final ImeSubtypeListItem isli = mImeSubtypeList.get(i); + // Skip until the current IME/subtype is found. + if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { + return i; + } + } + return -1; } - // Here we have two rotation groups, depending on the returned boolean value of - // {@link InputMethodInfo#supportsSwitchingToNextInputMethod()}. - final boolean expectedValueOfSupportsSwitchingToNextInputMethod = - imi.supportsSwitchingToNextInputMethod(); - final int N = imList.size(); - final int currentSubtypeId = - subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, - subtype.hashCode()) : NOT_A_SUBTYPE_ID; - for (int i = 0; i < N; ++i) { - final ImeSubtypeListItem isli = imList.get(i); - // Skip until the current IME/subtype is found. - if (!isli.mImi.equals(imi) || isli.mSubtypeId != currentSubtypeId) { - continue; + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return null; } - // Found the current IME/subtype. Start searching the next IME/subtype from here. - for (int j = 0; j < N - 1; ++j) { - final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); - // Skip if the candidate doesn't belong to the expected rotation group. - if (expectedValueOfSupportsSwitchingToNextInputMethod != - candidate.mImi.supportsSwitchingToNextInputMethod()) { - continue; - } + if (mImeSubtypeList.size() <= 1) { + return null; + } + final int currentIndex = getIndex(imi, subtype); + if (currentIndex < 0) { + return null; + } + final int N = mImeSubtypeList.size(); + for (int offset = 1; offset < N; ++offset) { + // Start searching the next IME/subtype from the next of the current index. + final int candidateIndex = (currentIndex + offset) % N; + final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); // Skip if searching inside the current IME only, but the candidate is not // the current IME. - if (onlyCurrentIme && !candidate.mImi.equals(imi)) { + if (onlyCurrentIme && !imi.equals(candidate.mImi)) { continue; } return candidate; } - // No appropriate IME/subtype is found in the list. Give up. return null; } - // The current IME/subtype is not found in the list. Give up. - return null; } + @VisibleForTesting + public static class ControllerImpl { + private final StaticRotationList mSwitchingAwareSubtypeList; + private final StaticRotationList mSwitchingUnawareSubtypeList; + + public ControllerImpl(final List<ImeSubtypeListItem> sortedItems) { + mSwitchingAwareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, + true /* supportsSwitchingToNextInputMethod */)); + mSwitchingUnawareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, + false /* supportsSwitchingToNextInputMethod */)); + } + + public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (imi.supportsSwitchingToNextInputMethod()) { + return mSwitchingAwareSubtypeList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } else { + return mSwitchingUnawareSubtypeList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } + } + + private static List<ImeSubtypeListItem> filterImeSubtypeList( + final List<ImeSubtypeListItem> items, + final boolean supportsSwitchingToNextInputMethod) { + final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); + final int ALL_ITEMS_COUNT = items.size(); + for (int i = 0; i < ALL_ITEMS_COUNT; i++) { + final ImeSubtypeListItem item = items.get(i); + if (item.mImi.supportsSwitchingToNextInputMethod() == + supportsSwitchingToNextInputMethod) { + result.add(item); + } + } + return result; + } + } + + private final InputMethodSettings mSettings; + private InputMethodAndSubtypeList mSubtypeList; + private ControllerImpl mController; + private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) { mSettings = settings; resetCircularListLocked(context); @@ -276,12 +334,18 @@ public class InputMethodSubtypeSwitchingController { public void resetCircularListLocked(Context context) { mSubtypeList = new InputMethodAndSubtypeList(context, mSettings); + mController = new ControllerImpl(mSubtypeList.getSortedInputMethodAndSubtypeList()); } - public ImeSubtypeListItem getNextInputMethodLocked( - boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { - return getNextInputMethodLockedImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(), - onlyCurrentIme, imi, subtype); + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return null; + } + return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, |
