diff options
114 files changed, 3316 insertions, 3089 deletions
@@ -326,6 +326,7 @@ LOCAL_SRC_FILES += \ telecomm/java/com/android/internal/telecomm/ICallServiceSelectorAdapter.aidl \ telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl \ telecomm/java/com/android/internal/telecomm/IInCallService.aidl \ + telecomm/java/com/android/internal/telecomm/ITelecommService.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ telephony/java/com/android/internal/telephony/ITelephony.aidl \ diff --git a/api/current.txt b/api/current.txt index 282f062..3f5a090 100644 --- a/api/current.txt +++ b/api/current.txt @@ -749,6 +749,7 @@ package android { field public static final int layout_centerVertical = 16843153; // 0x1010191 field public static final int layout_column = 16843084; // 0x101014c field public static final int layout_columnSpan = 16843645; // 0x101037d + field public static final int layout_columnWeight = 16843868; // 0x101045c field public static final int layout_gravity = 16842931; // 0x10100b3 field public static final int layout_height = 16842997; // 0x10100f5 field public static final int layout_margin = 16842998; // 0x10100f6 @@ -760,6 +761,7 @@ package android { field public static final int layout_marginTop = 16843000; // 0x10100f8 field public static final int layout_row = 16843643; // 0x101037b field public static final int layout_rowSpan = 16843644; // 0x101037c + field public static final int layout_rowWeight = 16843867; // 0x101045b field public static final int layout_scale = 16843155; // 0x1010193 field public static final int layout_span = 16843085; // 0x101014d field public static final int layout_toEndOf = 16843704; // 0x10103b8 @@ -1399,8 +1401,6 @@ package android { field public static final int l_resource_pad9 = 17104904; // 0x1050008 field public static final int notification_large_icon_height = 17104902; // 0x1050006 field public static final int notification_large_icon_width = 17104901; // 0x1050005 - field public static final int recents_thumbnail_height = 17104913; // 0x1050011 - field public static final int recents_thumbnail_width = 17104914; // 0x1050012 field public static final int thumbnail_height = 17104897; // 0x1050001 field public static final int thumbnail_width = 17104898; // 0x1050002 } @@ -4538,13 +4538,22 @@ package android.app { ctor public Notification.Action.Builder(android.app.Notification.Action); method public android.app.Notification.Action.Builder addExtras(android.os.Bundle); method public android.app.Notification.Action.Builder addRemoteInput(android.app.RemoteInput); - method public android.app.Notification.Action.Builder apply(android.app.Notification.Action.Builder.Extender); method public android.app.Notification.Action build(); + method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender); method public android.os.Bundle getExtras(); } - public static abstract interface Notification.Action.Builder.Extender { - method public abstract android.app.Notification.Action.Builder applyTo(android.app.Notification.Action.Builder); + public static abstract interface Notification.Action.Extender { + method public abstract android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder); + } + + public static final class Notification.Action.WearableExtender implements android.app.Notification.Action.Extender { + ctor public Notification.Action.WearableExtender(); + ctor public Notification.Action.WearableExtender(android.app.Notification.Action); + method public android.app.Notification.Action.WearableExtender clone(); + method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder); + method public boolean isAvailableOffline(); + method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean); } public static class Notification.BigPictureStyle extends android.app.Notification.Style { @@ -4570,8 +4579,8 @@ package android.app { method public android.app.Notification.Builder addAction(android.app.Notification.Action); method public android.app.Notification.Builder addExtras(android.os.Bundle); method public android.app.Notification.Builder addPerson(java.lang.String); - method public android.app.Notification.Builder apply(android.app.Notification.Builder.Extender); method public android.app.Notification build(); + method public android.app.Notification.Builder extend(android.app.Notification.Extender); method public android.os.Bundle getExtras(); method public deprecated android.app.Notification getNotification(); method public android.app.Notification.Builder setAutoCancel(boolean); @@ -4613,8 +4622,8 @@ package android.app { method public android.app.Notification.Builder setWhen(long); } - public static abstract interface Notification.Builder.Extender { - method public abstract android.app.Notification.Builder applyTo(android.app.Notification.Builder); + public static abstract interface Notification.Extender { + method public abstract android.app.Notification.Builder extend(android.app.Notification.Builder); } public static class Notification.InboxStyle extends android.app.Notification.Style { @@ -4644,6 +4653,51 @@ package android.app { field protected android.app.Notification.Builder mBuilder; } + public static final class Notification.WearableExtender implements android.app.Notification.Extender { + ctor public Notification.WearableExtender(); + ctor public Notification.WearableExtender(android.app.Notification); + method public android.app.Notification.WearableExtender addAction(android.app.Notification.Action); + method public android.app.Notification.WearableExtender addActions(java.util.List<android.app.Notification.Action>); + method public android.app.Notification.WearableExtender addPage(android.app.Notification); + method public android.app.Notification.WearableExtender addPages(java.util.List<android.app.Notification>); + method public android.app.Notification.WearableExtender clearActions(); + method public android.app.Notification.WearableExtender clearPages(); + method public android.app.Notification.WearableExtender clone(); + method public android.app.Notification.Builder extend(android.app.Notification.Builder); + method public java.util.List<android.app.Notification.Action> getActions(); + method public android.graphics.Bitmap getBackground(); + method public int getContentAction(); + method public int getContentIcon(); + method public int getContentIconGravity(); + method public boolean getContentIntentAvailableOffline(); + method public int getCustomContentHeight(); + method public int getCustomSizePreset(); + method public android.app.PendingIntent getDisplayIntent(); + method public int getGravity(); + method public boolean getHintHideIcon(); + method public boolean getHintShowBackgroundOnly(); + method public java.util.List<android.app.Notification> getPages(); + method public boolean getStartScrollBottom(); + method public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap); + method public android.app.Notification.WearableExtender setContentAction(int); + method public android.app.Notification.WearableExtender setContentIcon(int); + method public android.app.Notification.WearableExtender setContentIconGravity(int); + method public android.app.Notification.WearableExtender setContentIntentAvailableOffline(boolean); + method public android.app.Notification.WearableExtender setCustomContentHeight(int); + method public android.app.Notification.WearableExtender setCustomSizePreset(int); + method public android.app.Notification.WearableExtender setDisplayIntent(android.app.PendingIntent); + method public android.app.Notification.WearableExtender setGravity(int); + method public android.app.Notification.WearableExtender setHintHideIcon(boolean); + method public android.app.Notification.WearableExtender setHintShowBackgroundOnly(boolean); + method public android.app.Notification.WearableExtender setStartScrollBottom(boolean); + field public static final int SIZE_DEFAULT = 0; // 0x0 + field public static final int SIZE_LARGE = 4; // 0x4 + field public static final int SIZE_MEDIUM = 3; // 0x3 + field public static final int SIZE_SMALL = 2; // 0x2 + field public static final int SIZE_XSMALL = 1; // 0x1 + field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff + } + public class NotificationManager { method public void cancel(int); method public void cancel(java.lang.String, int); @@ -5317,80 +5371,6 @@ package android.app.task { } -package android.app.wearable { - - public final class WearableActionExtensions implements android.app.Notification.Action.Builder.Extender android.os.Parcelable { - method public android.app.Notification.Action.Builder applyTo(android.app.Notification.Action.Builder); - method public int describeContents(); - method public static android.app.wearable.WearableActionExtensions from(android.app.Notification.Action); - method public boolean isAvailableOffline(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - - public static final class WearableActionExtensions.Builder { - ctor public WearableActionExtensions.Builder(); - ctor public WearableActionExtensions.Builder(android.app.wearable.WearableActionExtensions); - method public android.app.wearable.WearableActionExtensions build(); - method public android.app.wearable.WearableActionExtensions.Builder setAvailableOffline(boolean); - } - - public final class WearableNotificationExtensions implements android.app.Notification.Builder.Extender android.os.Parcelable { - method public android.app.Notification.Builder applyTo(android.app.Notification.Builder); - method public int describeContents(); - method public static android.app.wearable.WearableNotificationExtensions from(android.app.Notification); - method public android.app.Notification.Action getAction(int); - method public int getActionCount(); - method public android.app.Notification.Action[] getActions(); - method public android.graphics.Bitmap getBackground(); - method public int getContentAction(); - method public int getContentIcon(); - method public int getContentIconGravity(); - method public boolean getContentIntentAvailableOffline(); - method public int getCustomContentHeight(); - method public int getCustomSizePreset(); - method public android.app.PendingIntent getDisplayIntent(); - method public int getGravity(); - method public boolean getHintHideIcon(); - method public boolean getHintShowBackgroundOnly(); - method public android.app.Notification[] getPages(); - method public boolean getStartScrollBottom(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - field public static final int SIZE_DEFAULT = 0; // 0x0 - field public static final int SIZE_LARGE = 4; // 0x4 - field public static final int SIZE_MEDIUM = 3; // 0x3 - field public static final int SIZE_SMALL = 2; // 0x2 - field public static final int SIZE_XSMALL = 1; // 0x1 - field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff - } - - public static final class WearableNotificationExtensions.Builder { - ctor public WearableNotificationExtensions.Builder(); - ctor public WearableNotificationExtensions.Builder(android.app.wearable.WearableNotificationExtensions); - method public android.app.wearable.WearableNotificationExtensions.Builder addAction(android.app.Notification.Action); - method public android.app.wearable.WearableNotificationExtensions.Builder addActions(java.util.List<android.app.Notification.Action>); - method public android.app.wearable.WearableNotificationExtensions.Builder addPage(android.app.Notification); - method public android.app.wearable.WearableNotificationExtensions.Builder addPages(java.util.List<android.app.Notification>); - method public android.app.wearable.WearableNotificationExtensions build(); - method public android.app.wearable.WearableNotificationExtensions.Builder clearActions(); - method public android.app.wearable.WearableNotificationExtensions.Builder clearPages(); - method public android.app.wearable.WearableNotificationExtensions.Builder setBackground(android.graphics.Bitmap); - method public android.app.wearable.WearableNotificationExtensions.Builder setContentAction(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setContentIcon(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setContentIconGravity(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setContentIntentAvailableOffline(boolean); - method public android.app.wearable.WearableNotificationExtensions.Builder setCustomContentHeight(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setCustomSizePreset(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setDisplayIntent(android.app.PendingIntent); - method public android.app.wearable.WearableNotificationExtensions.Builder setGravity(int); - method public android.app.wearable.WearableNotificationExtensions.Builder setHintHideIcon(boolean); - method public android.app.wearable.WearableNotificationExtensions.Builder setHintShowBackgroundOnly(boolean); - method public android.app.wearable.WearableNotificationExtensions.Builder setStartScrollBottom(boolean); - } - -} - package android.appwidget { public class AppWidgetHost { @@ -11954,7 +11934,6 @@ package android.hardware { method public int getMinDelay(); method public java.lang.String getName(); method public float getPower(); - method public java.lang.String getRequiredPermission(); method public float getResolution(); method public java.lang.String getStringType(); method public int getType(); @@ -17123,11 +17102,9 @@ package android.net.wifi { } public static final class WifiEnterpriseConfig.Eap { - field public static final int AKA = 5; // 0x5 field public static final int NONE = -1; // 0xffffffff field public static final int PEAP = 0; // 0x0 field public static final int PWD = 3; // 0x3 - field public static final int SIM = 4; // 0x4 field public static final int TLS = 1; // 0x1 field public static final int TTLS = 2; // 0x2 } @@ -17547,14 +17524,17 @@ package android.net.wifi.p2p.nsd { package android.net.wifi.passpoint { public class WifiPasspointCredential implements android.os.Parcelable { - ctor public WifiPasspointCredential(java.lang.String, android.net.wifi.WifiEnterpriseConfig); + ctor public WifiPasspointCredential(java.lang.String, java.lang.String, android.net.wifi.WifiEnterpriseConfig); method public int describeContents(); + method public java.lang.String getCaRootCertPath(); method public java.lang.String getClientCertPath(); method public int getEapMethod(); method public java.lang.String getFqdn(); - method public java.lang.String getImsi(); method public java.lang.String getRealm(); method public java.lang.String getUserName(); + method public void setCredential(android.net.wifi.WifiEnterpriseConfig); + method public void setFqdn(java.lang.String); + method public void setRealm(java.lang.String); method public void writeToParcel(android.os.Parcel, int); } @@ -25061,7 +25041,9 @@ package android.provider { field public static final java.lang.String COLUMN_DESCRIPTION = "description"; field public static final java.lang.String COLUMN_DISPLAY_NAME = "display_name"; field public static final java.lang.String COLUMN_DISPLAY_NUMBER = "display_number"; + field public static final java.lang.String COLUMN_LOCKED = "locked"; field public static final java.lang.String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id"; + field public static final java.lang.String COLUMN_SEARCHABLE = "searchable"; field public static final java.lang.String COLUMN_SERVICE_ID = "service_id"; field public static final java.lang.String COLUMN_SERVICE_NAME = "service_name"; field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type"; @@ -25099,10 +25081,12 @@ package android.provider { } public static final class TvContract.Programs implements android.provider.TvContract.BaseTvColumns { + field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language"; field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id"; field public static final java.lang.String COLUMN_DATA = "data"; field public static final java.lang.String COLUMN_DESCRIPTION = "description"; field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; + field public static final java.lang.String COLUMN_GENRE = "genre"; field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description"; field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; field public static final java.lang.String COLUMN_TITLE = "title"; @@ -32904,6 +32888,7 @@ package android.view { method public void requestLayout(); method public boolean requestRectangleOnScreen(android.graphics.Rect); method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean); + method public final void requestUnbufferedDispatch(android.view.MotionEvent); method public static int resolveSize(int, int); method public static int resolveSizeAndState(int, int, int); method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>); @@ -36597,6 +36582,10 @@ package android.widget { method public void setRowCount(int); method public void setRowOrderPreserved(boolean); method public void setUseDefaultMargins(boolean); + method public static android.widget.GridLayout.Spec spec(int, int, android.widget.GridLayout.Alignment, float); + method public static android.widget.GridLayout.Spec spec(int, android.widget.GridLayout.Alignment, float); + method public static android.widget.GridLayout.Spec spec(int, int, float); + method public static android.widget.GridLayout.Spec spec(int, float); method public static android.widget.GridLayout.Spec spec(int, int, android.widget.GridLayout.Alignment); method public static android.widget.GridLayout.Spec spec(int, android.widget.GridLayout.Alignment); method public static android.widget.GridLayout.Spec spec(int, int); 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, diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 835a648..2d72494 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -172,8 +172,10 @@ LOCAL_C_INCLUDES += \ $(call include-path-for, bluedroid) \ $(call include-path-for, libhardware)/hardware \ $(call include-path-for, libhardware_legacy)/hardware_legacy \ + $(TOP)/bionic/libc/dns/include \ $(TOP)/frameworks/av/include \ $(TOP)/system/media/camera/include \ + $(TOP)/system/netd/include \ external/pdfium/core/include/fpdfapi \ external/pdfium/core/include/fpdfdoc \ external/pdfium/fpdfsdk/include \ @@ -232,6 +234,7 @@ LOCAL_SHARED_LIBRARIES := \ libaudioutils \ libpdfium \ libimg_utils \ + libnetd_client \ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp index b389d9e..0cfcaef 100644 --- a/core/jni/android/graphics/Shader.cpp +++ b/core/jni/android/graphics/Shader.cpp @@ -50,26 +50,16 @@ static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvAr /////////////////////////////////////////////////////////////////////////////////////////////// -static void Shader_destructor(JNIEnv* env, jobject o, jlong shaderHandle, jlong skiaShaderHandle) +static void Shader_destructor(JNIEnv* env, jobject o, jlong shaderHandle) { SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - SkiaShader* skiaShader = reinterpret_cast<SkiaShader*>(skiaShaderHandle); SkSafeUnref(shader); - // skiaShader == NULL when not !USE_OPENGL_RENDERER, so no need to delete it outside the ifdef -#ifdef USE_OPENGL_RENDERER - if (android::uirenderer::Caches::hasInstance()) { - android::uirenderer::Caches::getInstance().resourceCache.destructor(skiaShader); - } else { - delete skiaShader; - } -#endif } static void Shader_setLocalMatrix(JNIEnv* env, jobject o, jlong shaderHandle, - jlong skiaShaderHandle, jlong matrixHandle) + jlong matrixHandle) { SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - SkiaShader* skiaShader = reinterpret_cast<SkiaShader*>(skiaShaderHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); if (shader) { if (NULL == matrix) { @@ -78,9 +68,6 @@ static void Shader_setLocalMatrix(JNIEnv* env, jobject o, jlong shaderHandle, else { shader->setLocalMatrix(*matrix); } -#ifdef USE_OPENGL_RENDERER - skiaShader->setMatrix(const_cast<SkMatrix*>(matrix)); -#endif } } @@ -98,20 +85,6 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong bitmapHandle return reinterpret_cast<jlong>(s); } -static jlong BitmapShader_postConstructor(JNIEnv* env, jobject o, jlong shaderHandle, - jlong bitmapHandle, jint tileModeX, jint tileModeY) { - SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); -#ifdef USE_OPENGL_RENDERER - SkiaShader* skiaShader = new SkiaBitmapShader(bitmap, shader, - static_cast<SkShader::TileMode>(tileModeX), static_cast<SkShader::TileMode>(tileModeY), - NULL, !shader->isOpaque()); - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - /////////////////////////////////////////////////////////////////////////////////////////////// static jlong LinearGradient_create1(JNIEnv* env, jobject o, @@ -141,105 +114,6 @@ static jlong LinearGradient_create1(JNIEnv* env, jobject o, return reinterpret_cast<jlong>(shader); } -static jlong LinearGradient_postCreate1(JNIEnv* env, jobject o, jlong shaderHandle, - jfloat x0, jfloat y0, jfloat x1, jfloat y1, jintArray colorArray, - jfloatArray posArray, jint tileMode) { -#ifdef USE_OPENGL_RENDERER - SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - size_t count = env->GetArrayLength(colorArray); - const jint* colorValues = env->GetIntArrayElements(colorArray, NULL); - - jfloat* storedBounds = new jfloat[4]; - storedBounds[0] = x0; storedBounds[1] = y0; - storedBounds[2] = x1; storedBounds[3] = y1; - - bool missFirst = false; - bool missLast = false; - size_t stopCount = count; - - jfloat* storedPositions = NULL; - if (posArray) { - AutoJavaFloatArray autoPos(env, posArray, count); - const float* posValues = autoPos.ptr(); - - missFirst = posValues[0] != 0.0f; - missLast = posValues[count - 1] != 1.0f; - - stopCount += missFirst + missLast; - storedPositions = new jfloat[stopCount]; - - if (missFirst) { - storedPositions[0] = 0.0f; - } - - for (size_t i = missFirst; i < count + missFirst; i++) { - storedPositions[i] = posValues[i - missFirst]; - } - - if (missLast) { - storedPositions[stopCount - 1] = 1.0f; - } - } else { - storedPositions = new jfloat[count]; - storedPositions[0] = 0.0f; - const jfloat step = 1.0f / (count - 1); - for (size_t i = 1; i < count - 1; i++) { - storedPositions[i] = step * i; - } - storedPositions[count - 1] = 1.0f; - } - - uint32_t* storedColors = new uint32_t[stopCount]; - - if (missFirst) { - storedColors[0] = static_cast<uint32_t>(colorValues[0]); - } - - for (size_t i = missFirst; i < count + missFirst; i++) { - storedColors[i] = static_cast<uint32_t>(colorValues[i - missFirst]); - } - - if (missLast) { - storedColors[stopCount - 1] = static_cast<uint32_t>(colorValues[count - 1]); - } - - SkiaShader* skiaShader = new SkiaLinearGradientShader(storedBounds, storedColors, - storedPositions, stopCount, shader, static_cast<SkShader::TileMode>(tileMode), NULL, - !shader->isOpaque()); - - env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT); - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - -static jlong LinearGradient_postCreate2(JNIEnv* env, jobject o, jlong shaderHandle, - jfloat x0, jfloat y0, jfloat x1, jfloat y1, jint color0, jint color1, jint tileMode) { -#ifdef USE_OPENGL_RENDERER - SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - float* storedBounds = new float[4]; - storedBounds[0] = x0; storedBounds[1] = y0; - storedBounds[2] = x1; storedBounds[3] = y1; - - float* storedPositions = new float[2]; - storedPositions[0] = 0.0f; - storedPositions[1] = 1.0f; - - uint32_t* storedColors = new uint32_t[2]; - storedColors[0] = static_cast<uint32_t>(color0); - storedColors[1] = static_cast<uint32_t>(color1); - - SkiaShader* skiaShader = new SkiaLinearGradientShader(storedBounds, storedColors, - storedPositions, 2, shader, static_cast<SkShader::TileMode>(tileMode), NULL, - !shader->isOpaque()); - - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - static jlong LinearGradient_create2(JNIEnv* env, jobject o, jfloat x0, jfloat y0, jfloat x1, jfloat y1, jint color0, jint color1, jint tileMode) @@ -300,67 +174,6 @@ static jlong RadialGradient_create2(JNIEnv* env, jobject, jfloat x, jfloat y, jf return reinterpret_cast<jlong>(s); } -static jlong RadialGradient_postCreate1(JNIEnv* env, jobject o, jlong shaderHandle, - jfloat x, jfloat y, jfloat radius, jintArray colorArray, jfloatArray posArray, jint tileMode) { -#ifdef USE_OPENGL_RENDERER - SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - size_t count = env->GetArrayLength(colorArray); - const jint* colorValues = env->GetIntArrayElements(colorArray, NULL); - - jfloat* storedPositions = new jfloat[count]; - uint32_t* storedColors = new uint32_t[count]; - for (size_t i = 0; i < count; i++) { - storedColors[i] = static_cast<uint32_t>(colorValues[i]); - } - - if (posArray) { - AutoJavaFloatArray autoPos(env, posArray, count); - const float* posValues = autoPos.ptr(); - for (size_t i = 0; i < count; i++) { - storedPositions[i] = posValues[i]; - } - } else { - storedPositions[0] = 0.0f; - const jfloat step = 1.0f / (count - 1); - for (size_t i = 1; i < count - 1; i++) { - storedPositions[i] = step * i; - } - storedPositions[count - 1] = 1.0f; - } - - SkiaShader* skiaShader = new SkiaCircularGradientShader(x, y, radius, storedColors, - storedPositions, count, shader, (SkShader::TileMode) tileMode, NULL, - !shader->isOpaque()); - - env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT); - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - -static jlong RadialGradient_postCreate2(JNIEnv* env, jobject o, jlong shaderHandle, - jfloat x, jfloat y, jfloat radius, jint color0, jint color1, jint tileMode) { -#ifdef USE_OPENGL_RENDERER - SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - float* storedPositions = new float[2]; - storedPositions[0] = 0.0f; - storedPositions[1] = 1.0f; - - uint32_t* storedColors = new uint32_t[2]; - storedColors[0] = static_cast<uint32_t>(color0); - storedColors[1] = static_cast<uint32_t>(color1); - - SkiaShader* skiaShader = new SkiaCircularGradientShader(x, y, radius, storedColors, - storedPositions, 2, shader, (SkShader::TileMode) tileMode, NULL, - !shader->isOpaque()); - - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - /////////////////////////////////////////////////////////////////////////////// static jlong SweepGradient_create1(JNIEnv* env, jobject, jfloat x, jfloat y, @@ -393,65 +206,6 @@ static jlong SweepGradient_create2(JNIEnv* env, jobject, jfloat x, jfloat y, return reinterpret_cast<jlong>(s); } -static jlong SweepGradient_postCreate1(JNIEnv* env, jobject o, jlong shaderHandle, - jfloat x, jfloat y, jintArray colorArray, jfloatArray posArray) { -#ifdef USE_OPENGL_RENDERER - SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - size_t count = env->GetArrayLength(colorArray); - const jint* colorValues = env->GetIntArrayElements(colorArray, NULL); - - jfloat* storedPositions = new jfloat[count]; - uint32_t* storedColors = new uint32_t[count]; - for (size_t i = 0; i < count; i++) { - storedColors[i] = static_cast<uint32_t>(colorValues[i]); - } - - if (posArray) { - AutoJavaFloatArray autoPos(env, posArray, count); - const float* posValues = autoPos.ptr(); - for (size_t i = 0; i < count; i++) { - storedPositions[i] = posValues[i]; - } - } else { - storedPositions[0] = 0.0f; - const jfloat step = 1.0f / (count - 1); - for (size_t i = 1; i < count - 1; i++) { - storedPositions[i] = step * i; - } - storedPositions[count - 1] = 1.0f; - } - - SkiaShader* skiaShader = new SkiaSweepGradientShader(x, y, storedColors, storedPositions, count, - shader, NULL, !shader->isOpaque()); - - env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT); - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - -static jlong SweepGradient_postCreate2(JNIEnv* env, jobject o, jlong shaderHandle, - jfloat x, jfloat y, jint color0, jint color1) { -#ifdef USE_OPENGL_RENDERER - SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle); - float* storedPositions = new float[2]; - storedPositions[0] = 0.0f; - storedPositions[1] = 1.0f; - - uint32_t* storedColors = new uint32_t[2]; - storedColors[0] = static_cast<uint32_t>(color0); - storedColors[1] = static_cast<uint32_t>(color1); - - SkiaShader* skiaShader = new SkiaSweepGradientShader(x, y, storedColors, storedPositions, 2, - shader, NULL, !shader->isOpaque()); - - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - /////////////////////////////////////////////////////////////////////////////////////////////// static jlong ComposeShader_create1(JNIEnv* env, jobject o, @@ -476,40 +230,6 @@ static jlong ComposeShader_create2(JNIEnv* env, jobject o, return reinterpret_cast<jlong>(shader); } -static jlong ComposeShader_postCreate2(JNIEnv* env, jobject o, jlong shaderHandle, - jlong shaderAHandle, jlong shaderBHandle, jint porterDuffModeHandle) { -#ifdef USE_OPENGL_RENDERER - SkShader* shader = reinterpret_cast<SkShader *>(shaderHandle); - SkiaShader* shaderA = reinterpret_cast<SkiaShader *>(shaderAHandle); - SkiaShader* shaderB = reinterpret_cast<SkiaShader *>(shaderBHandle); - SkPorterDuff::Mode porterDuffMode = static_cast<SkPorterDuff::Mode>(porterDuffModeHandle); - SkXfermode::Mode mode = SkPorterDuff::ToXfermodeMode(porterDuffMode); - SkiaShader* skiaShader = new SkiaComposeShader(shaderA, shaderB, mode, shader); - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - -static jlong ComposeShader_postCreate1(JNIEnv* env, jobject o, jlong shaderHandle, - jlong shaderAHandle, jlong shaderBHandle, jlong modeHandle) { -#ifdef USE_OPENGL_RENDERER - SkShader* shader = reinterpret_cast<SkShader *>(shaderHandle); - SkiaShader* shaderA = reinterpret_cast<SkiaShader *>(shaderAHandle); - SkiaShader* shaderB = reinterpret_cast<SkiaShader *>(shaderBHandle); - SkXfermode* mode = reinterpret_cast<SkXfermode *>(modeHandle); - SkXfermode::Mode skiaMode; - if (!SkXfermode::AsMode(mode, &skiaMode)) { - // TODO: Support other modes - skiaMode = SkXfermode::kSrcOver_Mode; - } - SkiaShader* skiaShader = new SkiaComposeShader(shaderA, shaderB, skiaMode, shader); - return reinterpret_cast<jlong>(skiaShader); -#else - return NULL; -#endif -} - /////////////////////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gColorMethods[] = { @@ -518,41 +238,32 @@ static JNINativeMethod gColorMethods[] = { }; static JNINativeMethod gShaderMethods[] = { - { "nativeDestructor", "(JJ)V", (void*)Shader_destructor }, - { "nativeSetLocalMatrix", "(JJJ)V", (void*)Shader_setLocalMatrix } + { "nativeDestructor", "(J)V", (void*)Shader_destructor }, + { "nativeSetLocalMatrix", "(JJ)V", (void*)Shader_setLocalMatrix } }; static JNINativeMethod gBitmapShaderMethods[] = { { "nativeCreate", "(JII)J", (void*)BitmapShader_constructor }, - { "nativePostCreate", "(JJII)J", (void*)BitmapShader_postConstructor } }; static JNINativeMethod gLinearGradientMethods[] = { { "nativeCreate1", "(FFFF[I[FI)J", (void*)LinearGradient_create1 }, { "nativeCreate2", "(FFFFIII)J", (void*)LinearGradient_create2 }, - { "nativePostCreate1", "(JFFFF[I[FI)J", (void*)LinearGradient_postCreate1 }, - { "nativePostCreate2", "(JFFFFIII)J", (void*)LinearGradient_postCreate2 } }; static JNINativeMethod gRadialGradientMethods[] = { { "nativeCreate1", "(FFF[I[FI)J", (void*)RadialGradient_create1 }, { "nativeCreate2", "(FFFIII)J", (void*)RadialGradient_create2 }, - { "nativePostCreate1", "(JFFF[I[FI)J", (void*)RadialGradient_postCreate1 }, - { "nativePostCreate2", "(JFFFIII)J", (void*)RadialGradient_postCreate2 } }; static JNINativeMethod gSweepGradientMethods[] = { { "nativeCreate1", "(FF[I[F)J", (void*)SweepGradient_create1 }, { "nativeCreate2", "(FFII)J", (void*)SweepGradient_create2 }, - { "nativePostCreate1", "(JFF[I[F)J", (void*)SweepGradient_postCreate1 }, - { "nativePostCreate2", "(JFFII)J", (void*)SweepGradient_postCreate2 } }; static JNINativeMethod gComposeShaderMethods[] = { { "nativeCreate1", "(JJJ)J", (void*)ComposeShader_create1 }, { "nativeCreate2", "(JJI)J", (void*)ComposeShader_create2 }, - { "nativePostCreate1", "(JJJJ)J", (void*)ComposeShader_postCreate1 }, - { "nativePostCreate2", "(JJJI)J", (void*)ComposeShader_postCreate2 } }; #include <android_runtime/AndroidRuntime.h> diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 6d23c32..bc5e1b3 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -18,6 +18,8 @@ #include "jni.h" #include "JNIHelp.h" +#include "NetdClient.h" +#include "resolv_netid.h" #include <utils/misc.h> #include <android_runtime/AndroidRuntime.h> #include <utils/Log.h> @@ -250,6 +252,36 @@ static void android_net_utils_markSocket(JNIEnv *env, jobject thiz, jint socket, } } +static void android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId) +{ + setNetworkForProcess(netId); +} + +static void android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz) +{ + setNetworkForProcess(NETID_UNSET); +} + +static jint android_net_utils_getNetworkBoundToProcess(JNIEnv *env, jobject thiz) +{ + return getNetworkForProcess(); +} + +static void android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz, jint netId) +{ + setNetworkForResolv(netId); +} + +static void android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz) +{ + setNetworkForResolv(NETID_UNSET); +} + +static void android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket, jint netId) +{ + setNetworkForSocket(netId, socket); +} + // ---------------------------------------------------------------------------- /* @@ -267,6 +299,12 @@ static JNINativeMethod gNetworkUtilMethods[] = { { "releaseDhcpLease", "(Ljava/lang/String;)Z", (void *)android_net_utils_releaseDhcpLease }, { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError }, { "markSocket", "(II)V", (void*) android_net_utils_markSocket }, + { "bindProcessToNetwork", "(I)V", (void*) android_net_utils_bindProcessToNetwork }, + { "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess }, + { "unbindProcessToNetwork", "()V", (void*) android_net_utils_unbindProcessToNetwork }, + { "bindProcessToNetworkForHostResolution", "(I)V", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, + { "unbindProcessToNetworkForHostResolution", "()V", (void*) android_net_utils_unbindProcessToNetworkForHostResolution }, + { "bindSocketToNetwork", "(II)V", (void*) android_net_utils_bindSocketToNetwork }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 27d3f39..c5dd06f 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -45,7 +45,6 @@ #include <DisplayListRenderer.h> #include <LayerRenderer.h> #include <OpenGLRenderer.h> -#include <SkiaShader.h> #include <Stencil.h> #include <Rect.h> #include <RenderNode.h> @@ -85,8 +84,6 @@ using namespace uirenderer; #define RENDERER_LOGD(...) #endif -#define MODIFIER_SHADER 2 - // ---------------------------------------------------------------------------- static struct { @@ -616,24 +613,6 @@ static void android_view_GLES20Canvas_drawLines(JNIEnv* env, jobject clazz, } // ---------------------------------------------------------------------------- -// Shaders and color filters -// ---------------------------------------------------------------------------- - -static void android_view_GLES20Canvas_resetModifiers(JNIEnv* env, jobject clazz, - jlong rendererPtr, jint modifiers) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - if (modifiers & MODIFIER_SHADER) renderer->resetShader(); -} - -static void android_view_GLES20Canvas_setupShader(JNIEnv* env, jobject clazz, - jlong rendererPtr, jlong shaderPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - SkiaShader* shader = reinterpret_cast<SkiaShader*>(shaderPtr); - renderer->setupShader(shader); -} - - -// ---------------------------------------------------------------------------- // Draw filters // ---------------------------------------------------------------------------- @@ -1091,9 +1070,6 @@ static JNINativeMethod gMethods[] = { { "nDrawPath", "(JJJ)V", (void*) android_view_GLES20Canvas_drawPath }, { "nDrawLines", "(J[FIIJ)V", (void*) android_view_GLES20Canvas_drawLines }, - { "nResetModifiers", "(JI)V", (void*) android_view_GLES20Canvas_resetModifiers }, - { "nSetupShader", "(JJ)V", (void*) android_view_GLES20Canvas_setupShader }, - { "nSetupPaintFilter", "(JII)V", (void*) android_view_GLES20Canvas_setupPaintFilter }, { "nResetPaintFilter", "(J)V", (void*) android_view_GLES20Canvas_resetPaintFilter }, diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 6ae02e0..a590dbf 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -211,8 +211,9 @@ static void pointerCoordsToNative(JNIEnv* env, jobject pointerCoordsObj, outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.orientation)); - uint64_t bits = env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits); - if (bits) { + BitSet64 bits = + BitSet64(env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits)); + if (!bits.isEmpty()) { jfloatArray valuesArray = jfloatArray(env->GetObjectField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisValues)); if (valuesArray) { @@ -221,11 +222,9 @@ static void pointerCoordsToNative(JNIEnv* env, jobject pointerCoordsObj, uint32_t index = 0; do { - uint32_t axis = __builtin_ctzll(bits); - uint64_t axisBit = 1LL << axis; - bits &= ~axisBit; + uint32_t axis = bits.clearFirstMarkedBit(); outRawPointerCoords->setAxisValue(axis, values[index++]); - } while (bits); + } while (!bits.isEmpty()); env->ReleasePrimitiveArrayCritical(valuesArray, values, JNI_ABORT); env->DeleteLocalRef(valuesArray); @@ -275,21 +274,19 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.orientation, rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); - const uint64_t unpackedAxisBits = 0 - | (1LL << AMOTION_EVENT_AXIS_X) - | (1LL << AMOTION_EVENT_AXIS_Y) - | (1LL << AMOTION_EVENT_AXIS_PRESSURE) - | (1LL << AMOTION_EVENT_AXIS_SIZE) - | (1LL << AMOTION_EVENT_AXIS_TOUCH_MAJOR) - | (1LL << AMOTION_EVENT_AXIS_TOUCH_MINOR) - | (1LL << AMOTION_EVENT_AXIS_TOOL_MAJOR) - | (1LL << AMOTION_EVENT_AXIS_TOOL_MINOR) - | (1LL << AMOTION_EVENT_AXIS_ORIENTATION); - uint64_t outBits = 0; - uint64_t remainingBits = rawPointerCoords->bits & ~unpackedAxisBits; - if (remainingBits) { - uint32_t packedAxesCount = __builtin_popcountll(remainingBits); + BitSet64 bits = BitSet64(rawPointerCoords->bits); + bits.clearBit(AMOTION_EVENT_AXIS_X); + bits.clearBit(AMOTION_EVENT_AXIS_Y); + bits.clearBit(AMOTION_EVENT_AXIS_PRESSURE); + bits.clearBit(AMOTION_EVENT_AXIS_SIZE); + bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MAJOR); + bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MINOR); + bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MAJOR); + bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MINOR); + bits.clearBit(AMOTION_EVENT_AXIS_ORIENTATION); + if (!bits.isEmpty()) { + uint32_t packedAxesCount = bits.count(); jfloatArray outValuesArray = obtainPackedAxisValuesArray(env, packedAxesCount, outPointerCoordsObj); if (!outValuesArray) { @@ -302,12 +299,10 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer const float* values = rawPointerCoords->values; uint32_t index = 0; do { - uint32_t axis = __builtin_ctzll(remainingBits); - uint64_t axisBit = 1LL << axis; - remainingBits &= ~axisBit; - outBits |= axisBit; + uint32_t axis = bits.clearFirstMarkedBit(); + outBits |= BitSet64::valueForBit(axis); outValues[index++] = rawPointerCoords->getAxisValue(axis); - } while (remainingBits); + } while (!bits.isEmpty()); env->ReleasePrimitiveArrayCritical(outValuesArray, outValues, 0); env->DeleteLocalRef(outValuesArray); diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml index 8f83ab2..3180e58 100644 --- a/core/res/res/values-sw600dp/dimens.xml +++ b/core/res/res/values-sw600dp/dimens.xml @@ -19,13 +19,9 @@ --> <resources> <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_width">200dp</dimen> + <dimen name="thumbnail_width">512dp</dimen> <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_height">177dp</dimen> - <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_width">512dp</dimen> - <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_height">512dp</dimen> + <dimen name="thumbnail_height">512dp</dimen> <!-- The maximum number of action buttons that should be permitted within an action bar/action mode. This will be used to determine how many showAsAction="ifRoom" items can fit. "always" items can override this. --> diff --git a/core/res/res/values-sw720dp/dimens.xml b/core/res/res/values-sw720dp/dimens.xml index 040bb5b..21235ec 100644 --- a/core/res/res/values-sw720dp/dimens.xml +++ b/core/res/res/values-sw720dp/dimens.xml @@ -35,13 +35,9 @@ <item type="dimen" name="dialog_fixed_height_minor">90%</item> <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_width">230dp</dimen> + <dimen name="thumbnail_width">640dp</dimen> <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_height">135dp</dimen> - <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_width">512dp</dimen> - <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_height">512dp</dimen> + <dimen name="thumbnail_height">640dp</dimen> <!-- Preference activity, vertical padding for the header list --> <dimen name="preference_screen_header_vertical_padding">32dp</dimen> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c05dfca..9eb6f74 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4057,6 +4057,9 @@ The default is one. See {@link android.widget.GridLayout.Spec}. --> <attr name="layout_rowSpan" format="integer" min="1" /> + <!-- The relative proportion of horizontal space that should be allocated to this view + during excess space distribution. --> + <attr name="layout_rowWeight" format="float" /> <!-- The column boundary delimiting the left of the group of cells occupied by this view. --> <attr name="layout_column" /> @@ -4065,6 +4068,9 @@ The default is one. See {@link android.widget.GridLayout.Spec}. --> <attr name="layout_columnSpan" format="integer" min="1" /> + <!-- The relative proportion of vertical space that should be allocated to this view + during excess space distribution. --> + <attr name="layout_columnWeight" format="float" /> <!-- Gravity specifies how a component should be placed in its group of cells. The default is LEFT | BASELINE. See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 52b021f..38f00fe 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -19,13 +19,9 @@ --> <resources> <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_width">164dp</dimen> + <dimen name="thumbnail_width">256dp</dimen> <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_height">145dp</dimen> - <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_width">256dp</dimen> - <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="recents_thumbnail_height">256dp</dimen> + <dimen name="thumbnail_height">256dp</dimen> <!-- The standard size (both width and height) of an application icon that will be displayed in the app launcher and elsewhere. --> <dimen name="app_icon_size">48dip</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index ce0d2d5..42ed318 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2178,12 +2178,11 @@ <public type="attr" name="contentInsetLeft" /> <public type="attr" name="contentInsetRight" /> <public type="attr" name="paddingMode" /> + <public type="attr" name="layout_rowWeight" /> + <public type="attr" name="layout_columnWeight" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> - <public type="dimen" name="recents_thumbnail_height" /> - <public type="dimen" name="recents_thumbnail_width" /> - <public-padding type="id" name="l_resource_pad" end="0x01020040" /> <public type="id" name="mask" /> diff --git a/core/tests/coretests/src/android/os/FileBridgeTest.java b/core/tests/coretests/src/android/os/FileBridgeTest.java new file mode 100644 index 0000000..d4f6b1f --- /dev/null +++ b/core/tests/coretests/src/android/os/FileBridgeTest.java @@ -0,0 +1,156 @@ +/* + * 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 android.os.FileBridge.FileBridgeOutputStream; +import android.test.AndroidTestCase; +import android.test.MoreAsserts; + +import libcore.io.Streams; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Random; + +public class FileBridgeTest extends AndroidTestCase { + + private File file; + private FileOutputStream fileOs; + private FileBridge bridge; + private FileBridgeOutputStream client; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + file = getContext().getFileStreamPath("meow.dat"); + file.delete(); + + fileOs = new FileOutputStream(file); + + bridge = new FileBridge(); + bridge.setTargetFile(fileOs.getFD()); + bridge.start(); + client = new FileBridgeOutputStream(bridge.getClientSocket()); + } + + @Override + protected void tearDown() throws Exception { + fileOs.close(); + file.delete(); + } + + private void assertOpen() throws Exception { + assertFalse("expected open", bridge.isClosed()); + } + + private void closeAndAssertClosed() throws Exception { + client.close(); + + // Wait a beat for things to settle down + SystemClock.sleep(200); + assertTrue("expected closed", bridge.isClosed()); + } + + private void assertContents(byte[] expected) throws Exception { + MoreAsserts.assertEquals(expected, Streams.readFully(new FileInputStream(file))); + } + + public void testNoWriteNoSync() throws Exception { + assertOpen(); + closeAndAssertClosed(); + } + + public void testNoWriteSync() throws Exception { + assertOpen(); + client.flush(); + closeAndAssertClosed(); + } + + public void testWriteNoSync() throws Exception { + assertOpen(); + client.write("meow".getBytes(StandardCharsets.UTF_8)); + closeAndAssertClosed(); + assertContents("meow".getBytes(StandardCharsets.UTF_8)); + } + + public void testWriteSync() throws Exception { + assertOpen(); + client.write("cake".getBytes(StandardCharsets.UTF_8)); + client.flush(); + closeAndAssertClosed(); + assertContents("cake".getBytes(StandardCharsets.UTF_8)); + } + + public void testWriteSyncWrite() throws Exception { + assertOpen(); + client.write("meow".getBytes(StandardCharsets.UTF_8)); + client.flush(); + client.write("cake".getBytes(StandardCharsets.UTF_8)); + closeAndAssertClosed(); + assertContents("meowcake".getBytes(StandardCharsets.UTF_8)); + } + + public void testEmptyWrite() throws Exception { + assertOpen(); + client.write(new byte[0]); + closeAndAssertClosed(); + assertContents(new byte[0]); + } + + public void testWriteAfterClose() throws Exception { + assertOpen(); + client.write("meow".getBytes(StandardCharsets.UTF_8)); + closeAndAssertClosed(); + try { + client.write("cake".getBytes(StandardCharsets.UTF_8)); + fail("wrote after close!"); + } catch (IOException expected) { + } + assertContents("meow".getBytes(StandardCharsets.UTF_8)); + } + + public void testRandomWrite() throws Exception { + final Random r = new Random(); + final ByteArrayOutputStream result = new ByteArrayOutputStream(); + + for (int i = 0; i < 512; i++) { + final byte[] test = new byte[r.nextInt(24169)]; + r.nextBytes(test); + result.write(test); + client.write(test); + client.flush(); + } + + closeAndAssertClosed(); + assertContents(result.toByteArray()); + } + + public void testGiantWrite() throws Exception { + final byte[] test = new byte[263401]; + new Random().nextBytes(test); + + assertOpen(); + client.write(test); + closeAndAssertClosed(); + assertContents(test); + } +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java index 20fe465..7b83999 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java @@ -31,7 +31,7 @@ import java.io.RandomAccessFile; * Empty service for testing legacy multidex. Access more than 64k methods but some are required at * init, some only at verification and others during execution. */ -public abstract class AbstractService extends Service { +public abstract class AbstractService extends Service implements Runnable { private final String TAG = "MultidexLegacyTestService" + getId(); private int instanceFieldNotInited; @@ -47,6 +47,12 @@ public abstract class AbstractService extends Service { @Override public void onCreate() { Log.i(TAG, "onCreate"); + new Thread(this).start(); + + } + + @Override + public void run() { Context applicationContext = getApplicationContext(); File resultFile = new File(applicationContext.getFilesDir(), getId()); try { @@ -84,7 +90,6 @@ public abstract class AbstractService extends Service { } catch (IOException e) { e.printStackTrace(); } - } @Override diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java index 0f343b1..459bed5 100644 --- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java +++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java @@ -25,8 +25,9 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; -import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController; +import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; +import com.android.internal.inputmethod.InputMethodUtils; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +40,7 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe private static final boolean DUMMY_FORCE_DEFAULT = false; private static final int DUMMY_IS_DEFAULT_RES_ID = 0; private static final String SYSTEM_LOCALE = "en_US"; + private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static InputMethodSubtype createDummySubtype(final String locale) { final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); @@ -64,142 +66,141 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe si.exported = true; si.nonLocalizedLabel = imeLabel; ri.serviceInfo = si; - final List<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); - for (String subtypeLocale : subtypeLocales) { - subtypes.add(createDummySubtype(subtypeLocale)); + List<InputMethodSubtype> subtypes = null; + if (subtypeLocales != null) { + subtypes = new ArrayList<InputMethodSubtype>(); + for (String subtypeLocale : subtypeLocales) { + subtypes.add(createDummySubtype(subtypeLocale)); + } } final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod); - for (int i = 0; i < subtypes.size(); ++i) { - final String subtypeLocale = subtypeLocales.get(i); - items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, - SYSTEM_LOCALE)); + if (subtypes == null) { + items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, + NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE)); + } else { + for (int i = 0; i < subtypes.size(); ++i) { + final String subtypeLocale = subtypeLocales.get(i); + items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, + SYSTEM_LOCALE)); + } } } - private static List<ImeSubtypeListItem> createTestData() { + private static List<ImeSubtypeListItem> createEnabledImeSubtypes() { final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); - addDummyImeSubtypeListItems(items, "switchAwareLatinIme", "switchAwareLatinIme", - Arrays.asList("en_US", "es_US", "fr"), + addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "nonSwitchAwareLatinIme", "nonSwitchAwareLatinIme", + addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme", Arrays.asList("en_UK", "hi"), false /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "switchAwareJapaneseIme", "switchAwareJapaneseIme", - Arrays.asList("ja_JP"), + addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null, + false /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "nonSwitchAwareJapaneseIme", "nonSwitchAwareJapaneseIme", - Arrays.asList("ja_JP"), + addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme", + Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/); + return items; + } + + private static List<ImeSubtypeListItem> createDisabledImeSubtypes() { + final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); + addDummyImeSubtypeListItems(items, + "UnknownIme", "UnknownIme", + Arrays.asList("en_US", "hi"), + true /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, + "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme", + Arrays.asList("en_US"), + false /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme", + "UnknownSubtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); return items; } - @SmallTest - public void testGetNextInputMethodImplWithNotOnlyCurrentIme() throws Exception { - final List<ImeSubtypeListItem> imList = createTestData(); - - final boolean ONLY_CURRENT_IME = false; - ImeSubtypeListItem currentIme; - ImeSubtypeListItem nextIme; - - // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" - currentIme = imList.get(0); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(1), nextIme); - // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" - currentIme = imList.get(1); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(2), nextIme); - // "switchAwareLatinIme/fr" -> "switchAwareJapaneseIme/ja_JP" - currentIme = imList.get(2); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(5), nextIme); - // "switchAwareJapaneseIme/ja_JP" -> "switchAwareLatinIme/en_US" - currentIme = imList.get(5); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(0), nextIme); - - // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" - currentIme = imList.get(3); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(4), nextIme); - // "nonSwitchAwareLatinIme/hi" -> "nonSwitchAwareJapaneseIme/ja_JP" - currentIme = imList.get(4); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(6), nextIme); - // "nonSwitchAwareJapaneseIme/ja_JP" -> "nonSwitchAwareLatinIme/en_UK" - currentIme = imList.get(6); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(3), nextIme); + private void assertNextInputMethod(final ControllerImpl controller, + final boolean onlyCurrentIme, + final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) { + InputMethodSubtype subtype = null; + if (currentItem.mSubtypeName != null) { + subtype = createDummySubtype(currentItem.mSubtypeName.toString()); + } + final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme, + currentItem.mImi, subtype); + assertEquals(nextItem, nextIme); } @SmallTest - public void testGetNextInputMethodImplWithOnlyCurrentIme() throws Exception { - final List<ImeSubtypeListItem> imList = createTestData(); - - final boolean ONLY_CURRENT_IME = true; - ImeSubtypeListItem currentIme; - ImeSubtypeListItem nextIme; - - // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" - currentIme = imList.get(0); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(1), nextIme); - // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" - currentIme = imList.get(1); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(2), nextIme); - // "switchAwareLatinIme/fr" -> "switchAwareLatinIme/en_US" - currentIme = imList.get(2); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(0), nextIme); - - // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" - currentIme = imList.get(3); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(4), nextIme); - // "nonSwitchAwareLatinIme/hi" -> "switchAwareLatinIme/en_UK" - currentIme = imList.get(4); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(3), nextIme); - - // "switchAwareJapaneseIme/ja_JP" -> null - currentIme = imList.get(5); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertNull(nextIme); - - // "nonSwitchAwareJapaneseIme/ja_JP" -> null - currentIme = imList.get(6); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertNull(nextIme); + public void testControllerImpl() throws Exception { + final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); + final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0); + final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1); + final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2); + final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3); + + final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); + final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); + final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); + final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); + final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); + final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); + + final ControllerImpl controller = new ControllerImpl(enabledItems); + + // switching-aware loop + assertNextInputMethod(controller, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + latinIme_fr, japaneseIme_ja_JP); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + japaneseIme_ja_JP, latinIme_en_US); + + // switching-unaware loop + assertNextInputMethod(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_hi, subtypeUnawareIme); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + subtypeUnawareIme, switchUnawareJapaneseIme_ja_JP); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + switchUnawareJapaneseIme_ja_JP, switchingUnawarelatinIme_en_UK); + + // test onlyCurrentIme == true + assertNextInputMethod(controller, true /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + switchingUnawarelatinIme_hi, switchingUnawarelatinIme_en_UK); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + subtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + japaneseIme_ja_JP, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + switchUnawareJapaneseIme_ja_JP, null); + + // Make sure that disabled IMEs are not accepted. + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledIme_en_US, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledIme_hi, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledSwitchingUnawareIme, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledSubtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledIme_en_US, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledIme_hi, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledSwitchingUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledSubtypeUnawareIme, null); } } diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk index 452c575..c6bccfe 100644 --- a/data/fonts/Android.mk +++ b/data/fonts/Android.mk @@ -105,8 +105,11 @@ font_src_files := \ ifeq ($(MINIMAL_FONT_FOOTPRINT),true) +$(eval $(call create-font-symlink,Roboto-Black.ttf,Roboto-Bold.ttf)) $(eval $(call create-font-symlink,Roboto-Light.ttf,Roboto-Regular.ttf)) $(eval $(call create-font-symlink,Roboto-LightItalic.ttf,Roboto-Italic.ttf)) +$(eval $(call create-font-symlink,Roboto-Medium.ttf,Roboto-Regular.ttf)) +$(eval $(call create-font-symlink,Roboto-MediumItalic.ttf,Roboto-Italic.ttf)) $(eval $(call create-font-symlink,Roboto-Thin.ttf,Roboto-Regular.ttf)) $(eval $(call create-font-symlink,Roboto-ThinItalic.ttf,Roboto-Italic.ttf)) $(eval $(call create-font-symlink,RobotoCondensed-Regular.ttf,Roboto-Regular.ttf)) @@ -116,8 +119,11 @@ $(eval $(call create-font-symlink,RobotoCondensed-BoldItalic.ttf,Roboto-BoldItal else # !MINIMAL_FONT font_src_files += \ + Roboto-Black.ttf \ Roboto-Light.ttf \ Roboto-LightItalic.ttf \ + Roboto-Medium.ttf \ + Roboto-MediumItalic.ttf \ Roboto-Thin.ttf \ Roboto-ThinItalic.ttf \ RobotoCondensed-Regular.ttf \ diff --git a/data/fonts/Roboto-Black.ttf b/data/fonts/Roboto-Black.ttf Binary files differnew file mode 100644 index 0000000..18cf2ae --- /dev/null +++ b/data/fonts/Roboto-Black.ttf diff --git a/data/fonts/Roboto-Bold.ttf b/data/fonts/Roboto-Bold.ttf Binary files differindex c5b9c67..7e43dd2 100644 --- a/data/fonts/Roboto-Bold.ttf +++ b/data/fonts/Roboto-Bold.ttf diff --git a/data/fonts/Roboto-BoldItalic.ttf b/data/fonts/Roboto-BoldItalic.ttf Binary files differindex 0320214..b8f3165 100644 --- a/data/fonts/Roboto-BoldItalic.ttf +++ b/data/fonts/Roboto-BoldItalic.ttf diff --git a/data/fonts/Roboto-Italic.ttf b/data/fonts/Roboto-Italic.ttf Binary files differindex 38ba570..b84e93c 100644 --- a/data/fonts/Roboto-Italic.ttf +++ b/data/fonts/Roboto-Italic.ttf diff --git a/data/fonts/Roboto-Light.ttf b/data/fonts/Roboto-Light.ttf Binary files differindex 271606b..46c6fbc 100644 --- a/data/fonts/Roboto-Light.ttf +++ b/data/fonts/Roboto-Light.ttf diff --git a/data/fonts/Roboto-LightItalic.ttf b/data/fonts/Roboto-LightItalic.ttf Binary files differindex 17ef355..e11331d 100644 --- a/data/fonts/Roboto-LightItalic.ttf +++ b/data/fonts/Roboto-LightItalic.ttf diff --git a/data/fonts/Roboto-Medium.ttf b/data/fonts/Roboto-Medium.ttf Binary files differnew file mode 100644 index 0000000..433bca4 --- /dev/null +++ b/data/fonts/Roboto-Medium.ttf diff --git a/data/fonts/Roboto-MediumItalic.ttf b/data/fonts/Roboto-MediumItalic.ttf Binary files differnew file mode 100644 index 0000000..7166eb7 --- /dev/null +++ b/data/fonts/Roboto-MediumItalic.ttf diff --git a/data/fonts/Roboto-Regular.ttf b/data/fonts/Roboto-Regular.ttf Binary files differindex 7469063..7180d51 100644 --- a/data/fonts/Roboto-Regular.ttf +++ b/data/fonts/Roboto-Regular.ttf diff --git a/data/fonts/Roboto-Thin.ttf b/data/fonts/Roboto-Thin.ttf Binary files differindex 74efe4d..9792ed1 100644 --- a/data/fonts/Roboto-Thin.ttf +++ b/data/fonts/Roboto-Thin.ttf diff --git a/data/fonts/Roboto-ThinItalic.ttf b/data/fonts/Roboto-ThinItalic.ttf Binary files differindex f08ea51..5366fe5 100644 --- a/data/fonts/Roboto-ThinItalic.ttf +++ b/data/fonts/Roboto-ThinItalic.ttf diff --git a/data/fonts/RobotoCondensed-Bold.ttf b/data/fonts/RobotoCondensed-Bold.ttf Binary files differindex 1252d00..fe52e7d 100644 --- a/data/fonts/RobotoCondensed-Bold.ttf +++ b/data/fonts/RobotoCondensed-Bold.ttf diff --git a/data/fonts/RobotoCondensed-BoldItalic.ttf b/data/fonts/RobotoCondensed-BoldItalic.ttf Binary files differindex e914a07..2d29ffb 100644 --- a/data/fonts/RobotoCondensed-BoldItalic.ttf +++ b/data/fonts/RobotoCondensed-BoldItalic.ttf diff --git a/data/fonts/RobotoCondensed-Italic.ttf b/data/fonts/RobotoCondensed-Italic.ttf Binary files differindex 8a570cf..1d7eb08 100644 --- a/data/fonts/RobotoCondensed-Italic.ttf +++ b/data/fonts/RobotoCondensed-Italic.ttf diff --git a/data/fonts/RobotoCondensed-Light.ttf b/data/fonts/RobotoCondensed-Light.ttf Binary files differindex 41d212a..1209024 100644 --- a/data/fonts/RobotoCondensed-Light.ttf +++ b/data/fonts/RobotoCondensed-Light.ttf diff --git a/data/fonts/RobotoCondensed-LightItalic.ttf b/data/fonts/RobotoCondensed-LightItalic.ttf Binary files differindex dd54971..ed1ccea 100755..100644 --- a/data/fonts/RobotoCondensed-LightItalic.ttf +++ b/data/fonts/RobotoCondensed-LightItalic.ttf diff --git a/data/fonts/RobotoCondensed-Regular.ttf b/data/fonts/RobotoCondensed-Regular.ttf Binary files differindex a16b9cb..4a9a6f9 100644 --- a/data/fonts/RobotoCondensed-Regular.ttf +++ b/data/fonts/RobotoCondensed-Regular.ttf diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk index 2312a04..e5573bb 100644 --- a/data/fonts/fonts.mk +++ b/data/fonts/fonts.mk @@ -24,8 +24,11 @@ PRODUCT_PACKAGES := \ Roboto-Bold.ttf \ Roboto-Italic.ttf \ Roboto-BoldItalic.ttf \ + Roboto-Black.ttf \ Roboto-Light.ttf \ Roboto-LightItalic.ttf \ + Roboto-Medium.ttf \ + Roboto-MediumItalic.ttf \ Roboto-Thin.ttf \ Roboto-ThinItalic.ttf \ RobotoCondensed-Regular.ttf \ diff --git a/data/fonts/system_fonts.xml b/data/fonts/system_fonts.xml index 97b7fd8..646b33b 100644 --- a/data/fonts/system_fonts.xml +++ b/data/fonts/system_fonts.xml @@ -68,6 +68,25 @@ <family> <nameset> + <name>sans-serif-medium</name> + </nameset> + <fileset> + <file>Roboto-Medium.ttf</file> + <file>Roboto-MediumItalic.ttf</file> + </fileset> + </family> + + <family> + <nameset> + <name>sans-serif-black</name> + </nameset> + <fileset> + <file>Roboto-Black.ttf</file> + </fileset> + </family> + + <family> + <nameset> <name>sans-serif-condensed-light</name> </nameset> <fileset> diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index b7673d8..3ab57c1 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -44,7 +44,6 @@ public class BitmapShader extends Shader { mTileY = tileY; final long b = bitmap.ni(); native_instance = nativeCreate(b, tileX.nativeInt, tileY.nativeInt); - native_shader = nativePostCreate(native_instance, b, tileX.nativeInt, tileY.nativeInt); } /** @@ -59,6 +58,4 @@ public class BitmapShader extends Shader { private static native long nativeCreate(long native_bitmap, int shaderTileModeX, int shaderTileModeY); - private static native long nativePostCreate(long native_shader, long native_bitmap, - int shaderTileModeX, int shaderTileModeY); } diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java index 5109ffd..d7b2071 100644 --- a/graphics/java/android/graphics/ComposeShader.java +++ b/graphics/java/android/graphics/ComposeShader.java @@ -55,14 +55,6 @@ public class ComposeShader extends Shader { mXferMode = mode; native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance, (mode != null) ? mode.native_instance : 0); - if (mode instanceof PorterDuffXfermode) { - PorterDuff.Mode pdMode = ((PorterDuffXfermode) mode).mode; - native_shader = nativePostCreate2(native_instance, shaderA.native_shader, - shaderB.native_shader, pdMode != null ? pdMode.nativeInt : 0); - } else { - native_shader = nativePostCreate1(native_instance, shaderA.native_shader, - shaderB.native_shader, mode != null ? mode.native_instance : 0); - } } /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode. @@ -79,8 +71,6 @@ public class ComposeShader extends Shader { mPorterDuffMode = mode; native_instance = nativeCreate2(shaderA.native_instance, shaderB.native_instance, mode.nativeInt); - native_shader = nativePostCreate2(native_instance, shaderA.native_shader, - shaderB.native_shader, mode.nativeInt); } /** @@ -108,8 +98,4 @@ public class ComposeShader extends Shader { long native_mode); private static native long nativeCreate2(long native_shaderA, long native_shaderB, int porterDuffMode); - private static native long nativePostCreate1(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, long native_mode); - private static native long nativePostCreate2(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, int porterDuffMode); } diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java index 0eae67c..90cb217 100644 --- a/graphics/java/android/graphics/LinearGradient.java +++ b/graphics/java/android/graphics/LinearGradient.java @@ -66,8 +66,6 @@ public class LinearGradient extends Shader { mPositions = positions; mTileMode = tile; native_instance = nativeCreate1(x0, y0, x1, y1, colors, positions, tile.nativeInt); - native_shader = nativePostCreate1(native_instance, x0, y0, x1, y1, colors, positions, - tile.nativeInt); } /** Create a shader that draws a linear gradient along a line. @@ -90,8 +88,6 @@ public class LinearGradient extends Shader { mColor1 = color1; mTileMode = tile; native_instance = nativeCreate2(x0, y0, x1, y1, color0, color1, tile.nativeInt); - native_shader = nativePostCreate2(native_instance, x0, y0, x1, y1, color0, color1, - tile.nativeInt); } /** @@ -120,8 +116,4 @@ public class LinearGradient extends Shader { int colors[], float positions[], int tileMode); private native long nativeCreate2(float x0, float y0, float x1, float y1, int color0, int color1, int tileMode); - private native long nativePostCreate1(long native_shader, float x0, float y0, float x1, float y1, - int colors[], float positions[], int tileMode); - private native long nativePostCreate2(long native_shader, float x0, float y0, float x1, float y1, - int color0, int color1, int tileMode); } diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java index c00c612..75c951a 100644 --- a/graphics/java/android/graphics/RadialGradient.java +++ b/graphics/java/android/graphics/RadialGradient.java @@ -66,8 +66,6 @@ public class RadialGradient extends Shader { mPositions = positions; mTileMode = tile; native_instance = nativeCreate1(x, y, radius, colors, positions, tile.nativeInt); - native_shader = nativePostCreate1(native_instance, x, y, radius, colors, positions, - tile.nativeInt); } /** Create a shader that draws a radial gradient given the center and radius. @@ -91,8 +89,6 @@ public class RadialGradient extends Shader { mColor1 = color1; mTileMode = tile; native_instance = nativeCreate2(x, y, radius, color0, color1, tile.nativeInt); - native_shader = nativePostCreate2(native_instance, x, y, radius, color0, color1, - tile.nativeInt); } /** @@ -121,10 +117,5 @@ public class RadialGradient extends Shader { int colors[], float positions[], int tileMode); private static native long nativeCreate2(float x, float y, float radius, int color0, int color1, int tileMode); - - private static native long nativePostCreate1(long native_shader, float x, float y, float radius, - int colors[], float positions[], int tileMode); - private static native long nativePostCreate2(long native_shader, float x, float y, float radius, - int color0, int color1, int tileMode); } diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index 94b4c4a..6870ab4 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -29,10 +29,6 @@ public class Shader { * @hide */ public long native_instance; - /** - * @hide - */ - public long native_shader; private Matrix mLocalMatrix; @@ -78,7 +74,7 @@ public class Shader { */ public void setLocalMatrix(Matrix localM) { mLocalMatrix = localM; - nativeSetLocalMatrix(native_instance, native_shader, + nativeSetLocalMatrix(native_instance, localM == null ? 0 : localM.native_instance); } @@ -86,7 +82,7 @@ public class Shader { try { super.finalize(); } finally { - nativeDestructor(native_instance, native_shader); + nativeDestructor(native_instance); } } @@ -112,7 +108,7 @@ public class Shader { } } - private static native void nativeDestructor(long native_shader, long native_skiaShader); + private static native void nativeDestructor(long native_shader); private static native void nativeSetLocalMatrix(long native_shader, - long native_skiaShader, long matrix_instance); + long matrix_instance); } diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java index 21239f7..18a748f 100644 --- a/graphics/java/android/graphics/SweepGradient.java +++ b/graphics/java/android/graphics/SweepGradient.java @@ -63,7 +63,6 @@ public class SweepGradient extends Shader { mColors = colors; mPositions = positions; native_instance = nativeCreate1(cx, cy, colors, positions); - native_shader = nativePostCreate1(native_instance, cx, cy, colors, positions); } /** @@ -81,7 +80,6 @@ public class SweepGradient extends Shader { mColor0 = color0; mColor1 = color1; native_instance = nativeCreate2(cx, cy, color0, color1); - native_shader = nativePostCreate2(native_instance, cx, cy, color0, color1); } /** @@ -108,10 +106,5 @@ public class SweepGradient extends Shader { private static native long nativeCreate1(float x, float y, int colors[], float positions[]); private static native long nativeCreate2(float x, float y, int color0, int color1); - - private static native long nativePostCreate1(long native_shader, float cx, float cy, - int[] colors, float[] positions); - private static native long nativePostCreate2(long native_shader, float cx, float cy, - int color0, int color1); } diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 2e2ee15..5367663 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -33,6 +33,7 @@ #include "thread/TaskManager.h" #include "AssetAtlas.h" +#include "Extensions.h" #include "FontRenderer.h" #include "GammaFontRenderer.h" #include "TextureCache.h" diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index 3016814..937bf8d 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -230,6 +230,11 @@ public: return false; } + if (op->mPaint && mOps[0].op->mPaint && + op->mPaint->getShader() != mOps[0].op->mPaint->getShader()) { + return false; + } + /* Draw Modifiers compatibility check * * Shadows are ignored, as only text uses them, and in that case they are drawn @@ -244,7 +249,6 @@ public: */ const DrawModifiers& lhsMod = lhs->mDrawModifiers; const DrawModifiers& rhsMod = rhs->mDrawModifiers; - if (lhsMod.mShader != rhsMod.mShader) return false; // Draw filter testing expects bit fields to be clear if filter not set. if (lhsMod.mHasDrawFilter != rhsMod.mHasDrawFilter) return false; diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index dac86cb..96c6292 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -58,11 +58,6 @@ void DisplayListData::cleanupResources() { caches.resourceCache.decrementRefcountLocked(patchResources.itemAt(i)); } - for (size_t i = 0; i < shaders.size(); i++) { - caches.resourceCache.decrementRefcountLocked(shaders.itemAt(i)); - caches.resourceCache.destructorLocked(shaders.itemAt(i)); - } - for (size_t i = 0; i < sourcePaths.size(); i++) { caches.resourceCache.decrementRefcountLocked(sourcePaths.itemAt(i)); } @@ -92,7 +87,6 @@ void DisplayListData::cleanupResources() { bitmapResources.clear(); ownedBitmapResources.clear(); patchResources.clear(); - shaders.clear(); sourcePaths.clear(); paints.clear(); regions.clear(); diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index b2ead5b..11e78b0 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -56,7 +56,6 @@ class DisplayListRenderer; class OpenGLRenderer; class Rect; class Layer; -class SkiaShader; class ClipRectOp; class SaveLayerOp; @@ -127,7 +126,6 @@ public: SortedVector<const SkPath*> sourcePaths; Vector<const SkRegion*> regions; Vector<const SkMatrix*> matrices; - Vector<SkiaShader*> shaders; Vector<Layer*> layers; uint32_t functorCount; bool hasDrawOps; diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index e4867220..ea3e7a8 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -208,9 +208,16 @@ protected: if (!state.mMatrix.isSimple()) return false; // check state/paint for transparency - if (state.mDrawModifiers.mShader || - state.mAlpha != 1.0f || - (mPaint && mPaint->getAlpha() != 0xFF)) return false; + if (mPaint) { + if (mPaint->getShader() && !mPaint->getShader()->isOpaque()) { + return false; + } + if (mPaint->getAlpha() != 0xFF) { + return false; + } + } + + if (state.mAlpha != 1.0f) return false; SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint); return (mode == SkXfermode::kSrcOver_Mode || @@ -592,37 +599,6 @@ private: const SkRegion* mRegion; }; -class ResetShaderOp : public StateOp { -public: - virtual void applyState(OpenGLRenderer& renderer, int saveCount) const { - renderer.resetShader(); - } - - virtual void output(int level, uint32_t logFlags) const { - OP_LOGS("ResetShader"); - } - - virtual const char* name() { return "ResetShader"; } -}; - -class SetupShaderOp : public StateOp { -public: - SetupShaderOp(SkiaShader* shader) - : mShader(shader) {} - virtual void applyState(OpenGLRenderer& renderer, int saveCount) const { - renderer.setupShader(mShader); - } - - virtual void output(int level, uint32_t logFlags) const { - OP_LOG("SetupShader, shader %p", mShader); - } - - virtual const char* name() { return "SetupShader"; } - -private: - SkiaShader* mShader; -}; - class ResetPaintFilterOp : public StateOp { public: virtual void applyState(OpenGLRenderer& renderer, int saveCount) const { diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 606c67e..229afdf 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -47,7 +47,6 @@ DisplayListRenderer::~DisplayListRenderer() { /////////////////////////////////////////////////////////////////////////////// DisplayListData* DisplayListRenderer::finishRecording() { - mShaderMap.clear(); mPaintMap.clear(); mRegionMap.clear(); mPathMap.clear(); @@ -394,15 +393,6 @@ status_t DisplayListRenderer::drawRects(const float* rects, int count, const SkP return DrawGlInfo::kStatusDone; } -void DisplayListRenderer::resetShader() { - addStateOp(new (alloc()) ResetShaderOp()); -} - -void DisplayListRenderer::setupShader(SkiaShader* shader) { - shader = refShader(shader); - addStateOp(new (alloc()) SetupShaderOp(shader)); -} - void DisplayListRenderer::resetPaintFilter() { addStateOp(new (alloc()) ResetPaintFilterOp()); } diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index d814111..f0ae00f 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -95,9 +95,6 @@ public: virtual bool clipRegion(const SkRegion* region, SkRegion::Op op); // Misc - should be implemented with SkPaint inspection - virtual void resetShader(); - virtual void setupShader(SkiaShader* shader); - virtual void resetPaintFilter(); virtual void setupPaintFilter(int clearBits, int setBits); @@ -269,21 +266,6 @@ private: return bitmap; } - inline SkiaShader* refShader(SkiaShader* shader) { - if (!shader) return NULL; - - SkiaShader* shaderCopy = mShaderMap.valueFor(shader); - // TODO: We also need to handle generation ID changes in compose shaders - if (shaderCopy == NULL || shaderCopy->getGenerationId() != shader->getGenerationId()) { - shaderCopy = shader->copy(); - // replaceValueFor() performs an add if the entry doesn't exist - mShaderMap.replaceValueFor(shader, shaderCopy); - mDisplayListData->shaders.add(shaderCopy); - mCaches.resourceCache.incrementRefcount(shaderCopy); - } - return shaderCopy; - } - inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) { mDisplayListData->patchResources.add(patch); mCaches.resourceCache.incrementRefcount(patch); @@ -293,7 +275,6 @@ private: DefaultKeyedVector<const SkPaint*, const SkPaint*> mPaintMap; DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap; DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap; - DefaultKeyedVector<SkiaShader*, SkiaShader*> mShaderMap; Caches& mCaches; DisplayListData* mDisplayListData; diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 647c281..4407ab0 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -73,7 +73,7 @@ status_t TextSetupFunctor::operator ()(int what, void* data) { } } renderer->setupDrawColorFilter(paint->getColorFilter()); - renderer->setupDrawShader(); + renderer->setupDrawShader(paint->getShader()); renderer->setupDrawBlending(paint); renderer->setupDrawProgram(); renderer->setupDrawModelView(kModelViewMode_Translate, false, @@ -85,7 +85,7 @@ status_t TextSetupFunctor::operator ()(int what, void* data) { renderer->setupDrawTexture(0); renderer->setupDrawPureColorUniforms(); renderer->setupDrawColorFilterUniforms(paint->getColorFilter()); - renderer->setupDrawShaderUniforms(pureTranslate); + renderer->setupDrawShaderUniforms(paint->getShader(), pureTranslate); renderer->setupDrawTextGammaUniforms(); return NO_ERROR; diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 691f1c9..71836dd 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -21,6 +21,7 @@ #include <sys/types.h> #include <SkCanvas.h> +#include <SkShader.h> #include <SkTypeface.h> #include <utils/Log.h> @@ -37,6 +38,7 @@ #include "PathTessellator.h" #include "Properties.h" #include "ShadowTessellator.h" +#include "SkiaShader.h" #include "utils/GLUtils.h" #include "Vector.h" #include "VertexBuffer.h" @@ -1053,6 +1055,45 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) #define DRAW_DOUBLE_STENCIL(DRAW_COMMAND) DRAW_DOUBLE_STENCIL_IF(true, DRAW_COMMAND) +// This class is purely for inspection. It inherits from SkShader, but Skia does not know how to +// use it. The OpenGLRenderer will look at it to find its Layer and whether it is opaque. +class LayerShader : public SkShader { +public: + LayerShader(Layer* layer, const SkMatrix* localMatrix) + : INHERITED(localMatrix) + , mLayer(layer) { + } + + virtual bool asACustomShader(void** data) const { + if (data) { + *data = static_cast<void*>(mLayer); + } + return true; + } + + virtual bool isOpaque() const { + return !mLayer->isBlend(); + } + +protected: + virtual void shadeSpan(int x, int y, SkPMColor[], int count) { + LOG_ALWAYS_FATAL("LayerShader should never be drawn with raster backend."); + } + + virtual void flatten(SkWriteBuffer&) const { + LOG_ALWAYS_FATAL("LayerShader should never be flattened."); + } + + virtual Factory getFactory() const { + LOG_ALWAYS_FATAL("LayerShader should never be created from a stream."); + return NULL; + } +private: + // Unowned. + Layer* mLayer; + typedef SkShader INHERITED; +}; + void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { if (CC_UNLIKELY(layer->region.isEmpty())) return; // nothing to draw @@ -1066,21 +1107,19 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { paint.setAntiAlias(true); paint.setColor(SkColorSetARGB(int(getLayerAlpha(layer) * 255), 0, 0, 0)); - SkiaShader* oldShader = mDrawModifiers.mShader; - // create LayerShader to map SaveLayer content into subsequent draw SkMatrix shaderMatrix; shaderMatrix.setTranslate(rect.left, rect.bottom); shaderMatrix.preScale(1, -1); - SkiaLayerShader layerShader(layer, &shaderMatrix); - mDrawModifiers.mShader = &layerShader; + LayerShader layerShader(layer, &shaderMatrix); + paint.setShader(&layerShader); // Since the drawing primitive is defined in local drawing space, // we don't need to modify the draw matrix const SkPath* maskPath = layer->getConvexMask(); DRAW_DOUBLE_STENCIL(drawConvexPath(*maskPath, &paint)); - mDrawModifiers.mShader = oldShader; + paint.setShader(NULL); restore(); return; @@ -1627,9 +1666,9 @@ void OpenGLRenderer::setupDrawColor(float r, float g, float b, float a) { mSetShaderColor = mDescription.setColorModulate(a); } -void OpenGLRenderer::setupDrawShader() { - if (mDrawModifiers.mShader) { - mDrawModifiers.mShader->describe(mDescription, mExtensions); +void OpenGLRenderer::setupDrawShader(const SkShader* shader) { + if (shader != NULL) { + SkiaShader::describe(&mCaches, mDescription, mExtensions, *shader); } } @@ -1655,15 +1694,21 @@ void OpenGLRenderer::accountForClear(SkXfermode::Mode mode) { } } +static bool isBlendedColorFilter(const SkColorFilter* filter) { + if (filter == NULL) { + return false; + } + return (filter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag) == 0; +} + void OpenGLRenderer::setupDrawBlending(const Layer* layer, bool swapSrcDst) { SkXfermode::Mode mode = layer->getMode(); // When the blending mode is kClear_Mode, we need to use a modulate color // argb=1,0,0,0 accountForClear(mode); + // TODO: check shader blending, once we have shader drawing support for layers. bool blend = layer->isBlend() || getLayerAlpha(layer) < 1.0f || - (mColorSet && mColorA < 1.0f) || - (mDrawModifiers.mShader && mDrawModifiers.mShader->blend()) || - layer->getColorFilter(); + (mColorSet && mColorA < 1.0f) || isBlendedColorFilter(layer->getColorFilter()); chooseBlending(blend, mode, mDescription, swapSrcDst); } @@ -1673,8 +1718,8 @@ void OpenGLRenderer::setupDrawBlending(const SkPaint* paint, bool blend, bool sw // argb=1,0,0,0 accountForClear(mode); blend |= (mColorSet && mColorA < 1.0f) || - (mDrawModifiers.mShader && mDrawModifiers.mShader->blend()) || - (paint && paint->getColorFilter()); + (getShader(paint) && !getShader(paint)->isOpaque()) || + isBlendedColorFilter(getColorFilter(paint)); chooseBlending(blend, mode, mDescription, swapSrcDst); } @@ -1717,8 +1762,8 @@ void OpenGLRenderer::setupDrawModelView(ModelViewMode mode, bool offset, } } -void OpenGLRenderer::setupDrawColorUniforms() { - if ((mColorSet && !mDrawModifiers.mShader) || (mDrawModifiers.mShader && mSetShaderColor)) { +void OpenGLRenderer::setupDrawColorUniforms(bool hasShader) { + if ((mColorSet && !hasShader) || (hasShader && mSetShaderColor)) { mCaches.currentProgram->setColor(mColorR, mColorG, mColorB, mColorA); } } @@ -1729,20 +1774,22 @@ void OpenGLRenderer::setupDrawPureColorUniforms() { } } -void OpenGLRenderer::setupDrawShaderUniforms(bool ignoreTransform) { - if (mDrawModifiers.mShader) { - if (ignoreTransform) { - // if ignoreTransform=true was passed to setupDrawModelView, undo currentTransform() - // because it was built into modelView / the geometry, and the SkiaShader needs to - // compensate. - mat4 modelViewWithoutTransform; - modelViewWithoutTransform.loadInverse(*currentTransform()); - modelViewWithoutTransform.multiply(mModelViewMatrix); - mModelViewMatrix.load(modelViewWithoutTransform); - } - mDrawModifiers.mShader->setupProgram(mCaches.currentProgram, - mModelViewMatrix, *mSnapshot, &mTextureUnit); +void OpenGLRenderer::setupDrawShaderUniforms(const SkShader* shader, bool ignoreTransform) { + if (shader == NULL) { + return; + } + + if (ignoreTransform) { + // if ignoreTransform=true was passed to setupDrawModelView, undo currentTransform() + // because it was built into modelView / the geometry, and the description needs to + // compensate. + mat4 modelViewWithoutTransform; + modelViewWithoutTransform.loadInverse(*currentTransform()); + modelViewWithoutTransform.multiply(mModelViewMatrix); + mModelViewMatrix.load(modelViewWithoutTransform); } + + SkiaShader::setupProgram(&mCaches, mModelViewMatrix, &mTextureUnit, mExtensions, *shader); } void OpenGLRenderer::setupDrawColorFilterUniforms(const SkColorFilter* filter) { @@ -2201,7 +2248,7 @@ status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, // Apply a scale transform on the canvas only when a shader is in use // Skia handles the ratio between the dst and src rects as a scale factor // when a shader is set - bool useScaleTransform = mDrawModifiers.mShader && scaled; + bool useScaleTransform = getShader(paint) && scaled; bool ignoreTransform = false; if (CC_LIKELY(currentTransform()->isPureTranslate() && !useScaleTransform)) { @@ -2359,13 +2406,13 @@ status_t OpenGLRenderer::drawVertexBuffer(VertexBufferMode mode, if (isAA) setupDrawAA(); setupDrawColor(color, ((color >> 24) & 0xFF) * mSnapshot->alpha); setupDrawColorFilter(getColorFilter(paint)); - setupDrawShader(); + setupDrawShader(getShader(paint)); setupDrawBlending(paint, isAA); setupDrawProgram(); setupDrawModelView(kModelViewMode_Translate, useOffset, 0, 0, 0, 0); - setupDrawColorUniforms(); + setupDrawColorUniforms(getShader(paint)); setupDrawColorFilterUniforms(getColorFilter(paint)); - setupDrawShaderUniforms(); + setupDrawShaderUniforms(getShader(paint)); const void* vertices = vertexBuffer.getBuffer(); bool force = mCaches.unbindMeshBuffer(); @@ -2670,7 +2717,7 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, const float sy = y - shadow->top + textShadow.dy; const int shadowAlpha = ((textShadow.color >> 24) & 0xFF) * mSnapshot->alpha; - if (mDrawModifiers.mShader) { + if (getShader(paint)) { textShadow.color = SK_ColorWHITE; } @@ -2678,7 +2725,7 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, setupDrawWithTexture(true); setupDrawAlpha8Color(textShadow.color, shadowAlpha < 255 ? shadowAlpha : alpha); setupDrawColorFilter(getColorFilter(paint)); - setupDrawShader(); + setupDrawShader(getShader(paint)); setupDrawBlending(paint, true); setupDrawProgram(); setupDrawModelView(kModelViewMode_TranslateAndScale, false, @@ -2686,7 +2733,7 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, setupDrawTexture(shadow->id); setupDrawPureColorUniforms(); setupDrawColorFilterUniforms(getColorFilter(paint)); - setupDrawShaderUniforms(); + setupDrawShaderUniforms(getShader(paint)); setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset); glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); @@ -3008,21 +3055,6 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { } /////////////////////////////////////////////////////////////////////////////// -// Shaders -/////////////////////////////////////////////////////////////////////////////// - -void OpenGLRenderer::resetShader() { - mDrawModifiers.mShader = NULL; -} - -void OpenGLRenderer::setupShader(SkiaShader* shader) { - mDrawModifiers.mShader = shader; - if (mDrawModifiers.mShader) { - mDrawModifiers.mShader->setCaches(mCaches); - } -} - -/////////////////////////////////////////////////////////////////////////////// // Draw filters /////////////////////////////////////////////////////////////////////////////// @@ -3080,7 +3112,7 @@ void OpenGLRenderer::drawPathTexture(const PathTexture* texture, setupDrawWithTexture(true); setupDrawAlpha8Color(paint->getColor(), alpha); setupDrawColorFilter(getColorFilter(paint)); - setupDrawShader(); + setupDrawShader(getShader(paint)); setupDrawBlending(paint, true); setupDrawProgram(); setupDrawModelView(kModelViewMode_TranslateAndScale, false, @@ -3088,7 +3120,7 @@ void OpenGLRenderer::drawPathTexture(const PathTexture* texture, setupDrawTexture(texture->id); setupDrawPureColorUniforms(); setupDrawColorFilterUniforms(getColorFilter(paint)); - setupDrawShaderUniforms(); + setupDrawShaderUniforms(getShader(paint)); setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset); glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); @@ -3254,7 +3286,7 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, const SkP int color = paint->getColor(); // If a shader is set, preserve only the alpha - if (mDrawModifiers.mShader) { + if (getShader(paint)) { color |= 0x00ffffff; } @@ -3290,15 +3322,15 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, const SkP setupDraw(); setupDrawNoTexture(); setupDrawColor(color, ((color >> 24) & 0xFF) * currentSnapshot()->alpha); - setupDrawShader(); + setupDrawShader(getShader(paint)); setupDrawColorFilter(getColorFilter(paint)); setupDrawBlending(paint); setupDrawProgram(); setupDrawDirtyRegionsDisabled(); setupDrawModelView(kModelViewMode_Translate, false, 0.0f, 0.0f, 0.0f, 0.0f, ignoreTransform); - setupDrawColorUniforms(); - setupDrawShaderUniforms(); + setupDrawColorUniforms(getShader(paint)); + setupDrawShaderUniforms(getShader(paint)); setupDrawColorFilterUniforms(getColorFilter(paint)); if (dirty && hasLayer()) { @@ -3314,21 +3346,21 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot const SkPaint* paint, bool ignoreTransform) { int color = paint->getColor(); // If a shader is set, preserve only the alpha - if (mDrawModifiers.mShader) { + if (getShader(paint)) { color |= 0x00ffffff; } setupDraw(); setupDrawNoTexture(); setupDrawColor(color, ((color >> 24) & 0xFF) * currentSnapshot()->alpha); - setupDrawShader(); + setupDrawShader(getShader(paint)); setupDrawColorFilter(getColorFilter(paint)); setupDrawBlending(paint); setupDrawProgram(); setupDrawModelView(kModelViewMode_TranslateAndScale, false, left, top, right, bottom, ignoreTransform); - setupDrawColorUniforms(); - setupDrawShaderUniforms(ignoreTransform); + setupDrawColorUniforms(getShader(paint)); + setupDrawShaderUniforms(getShader(paint), ignoreTransform); setupDrawColorFilterUniforms(getColorFilter(paint)); setupDrawSimpleMesh(); @@ -3441,7 +3473,7 @@ void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, f setupDrawAlpha8Color(color, alpha); } setupDrawColorFilter(getColorFilter(paint)); - setupDrawShader(); + setupDrawShader(getShader(paint)); setupDrawBlending(paint, true); setupDrawProgram(); if (!dirty) setupDrawDirtyRegionsDisabled(); @@ -3449,7 +3481,7 @@ void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, f setupDrawTexture(texture); setupDrawPureColorUniforms(); setupDrawColorFilterUniforms(getColorFilter(paint)); - setupDrawShaderUniforms(ignoreTransform); + setupDrawShaderUniforms(getShader(paint), ignoreTransform); setupDrawMesh(vertices, texCoords); glDrawArrays(drawMode, 0, elementsCount); diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index c6d9071..fc27947 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -26,7 +26,6 @@ #include <SkMatrix.h> #include <SkPaint.h> #include <SkRegion.h> -#include <SkShader.h> #include <SkXfermode.h> #include <utils/Blur.h> @@ -45,13 +44,15 @@ #include "Program.h" #include "Rect.h" #include "Renderer.h" -#include "StatefulBaseRenderer.h" #include "Snapshot.h" +#include "StatefulBaseRenderer.h" #include "UvMapper.h" #include "Vertex.h" #include "Caches.h" #include "CanvasProperty.h" +class SkShader; + namespace android { namespace uirenderer { @@ -59,7 +60,6 @@ class DeferredDisplayState; class RenderNode; class TextSetupFunctor; class VertexBuffer; -class SkiaShader; struct DrawModifiers { DrawModifiers() { @@ -70,7 +70,6 @@ struct DrawModifiers { memset(this, 0, sizeof(DrawModifiers)); } - SkiaShader* mShader; float mOverrideLayerAlpha; // Draw filters @@ -217,9 +216,6 @@ public: status_t drawShadow(const mat4& casterTransformXY, const mat4& casterTransformZ, float casterAlpha, bool casterUnclipped, const SkPath* casterPerimeter); - virtual void resetShader(); - virtual void setupShader(SkiaShader* shader); - virtual void resetPaintFilter(); virtual void setupPaintFilter(int clearBits, int setBits); @@ -467,6 +463,14 @@ protected: } /** + * Safely retrieves the Shader from the given Paint. If the paint is + * null then null is returned. + */ + static inline const SkShader* getShader(const SkPaint* paint) { + return paint ? paint->getShader() : NULL; + } + + /** * Set to true to suppress error checks at the end of a frame. */ virtual bool suppressErrorChecks() const { @@ -838,7 +842,7 @@ private: void setupDrawColor(float r, float g, float b, float a); void setupDrawAlpha8Color(int color, int alpha); void setupDrawTextGamma(const SkPaint* paint); - void setupDrawShader(); + void setupDrawShader(const SkShader* shader); void setupDrawColorFilter(const SkColorFilter* filter); void setupDrawBlending(const Layer* layer, bool swapSrcDst = false); void setupDrawBlending(const SkPaint* paint, bool blend = true, bool swapSrcDst = false); @@ -862,9 +866,17 @@ private: */ void setupDrawModelView(ModelViewMode mode, bool offset, float left, float top, float right, float bottom, bool ignoreTransform = false); - void setupDrawColorUniforms(); + void setupDrawColorUniforms(bool hasShader); void setupDrawPureColorUniforms(); - void setupDrawShaderUniforms(bool ignoreTransform = false); + + /** + * Setup uniforms for the current shader. + * + * @param shader SkShader on the current paint. + * + * @param ignoreTransform Set to true to ignore the transform in shader. + */ + void setupDrawShaderUniforms(const SkShader* shader, bool ignoreTransform = false); void setupDrawColorFilterUniforms(const SkColorFilter* paint); void setupDrawSimpleMesh(); void setupDrawTexture(GLuint texture); diff --git a/libs/hwui/Renderer.h b/libs/hwui/Renderer.h index e191a26..23cab0e 100644 --- a/libs/hwui/Renderer.h +++ b/libs/hwui/Renderer.h @@ -35,7 +35,6 @@ class RenderNode; class Layer; class Matrix4; class SkiaColorFilter; -class SkiaShader; class Patch; enum DrawOpMode { @@ -183,9 +182,6 @@ public: virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) = 0; // Misc - should be implemented with SkPaint inspection - virtual void resetShader() = 0; - virtual void setupShader(SkiaShader* shader) = 0; - virtual void resetPaintFilter() = 0; virtual void setupPaintFilter(int clearBits, int setBits) = 0; diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index 13a3e8e..8b553d1 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -71,11 +71,6 @@ void ResourceCache::incrementRefcount(const SkPath* pathResource) { incrementRefcount((void*) pathResource, kPath); } -void ResourceCache::incrementRefcount(SkiaShader* shaderResource) { - SkSafeRef(shaderResource->getSkShader()); - incrementRefcount((void*) shaderResource, kShader); -} - void ResourceCache::incrementRefcount(const Res_png_9patch* patchResource) { incrementRefcount((void*) patchResource, kNinePatch); } @@ -104,11 +99,6 @@ void ResourceCache::incrementRefcountLocked(const SkPath* pathResource) { incrementRefcountLocked((void*) pathResource, kPath); } -void ResourceCache::incrementRefcountLocked(SkiaShader* shaderResource) { - SkSafeRef(shaderResource->getSkShader()); - incrementRefcountLocked((void*) shaderResource, kShader); -} - void ResourceCache::incrementRefcountLocked(const Res_png_9patch* patchResource) { incrementRefcountLocked((void*) patchResource, kNinePatch); } @@ -132,11 +122,6 @@ void ResourceCache::decrementRefcount(const SkPath* pathResource) { decrementRefcount((void*) pathResource); } -void ResourceCache::decrementRefcount(SkiaShader* shaderResource) { - SkSafeUnref(shaderResource->getSkShader()); - decrementRefcount((void*) shaderResource); -} - void ResourceCache::decrementRefcount(const Res_png_9patch* patchResource) { decrementRefcount((void*) patchResource); } @@ -168,11 +153,6 @@ void ResourceCache::decrementRefcountLocked(const SkPath* pathResource) { decrementRefcountLocked((void*) pathResource); } -void ResourceCache::decrementRefcountLocked(SkiaShader* shaderResource) { - SkSafeUnref(shaderResource->getSkShader()); - decrementRefcountLocked((void*) shaderResource); -} - void ResourceCache::decrementRefcountLocked(const Res_png_9patch* patchResource) { decrementRefcountLocked((void*) patchResource); } @@ -227,25 +207,6 @@ void ResourceCache::destructorLocked(const SkBitmap* resource) { } } -void ResourceCache::destructor(SkiaShader* resource) { - Mutex::Autolock _l(mLock); - destructorLocked(resource); -} - -void ResourceCache::destructorLocked(SkiaShader* resource) { - ssize_t index = mCache->indexOfKey(resource); - ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL; - if (ref == NULL) { - // If we're not tracking this resource, just delete it - delete resource; - return; - } - ref->destroyed = true; - if (ref->refCount == 0) { - deleteResourceReferenceLocked(resource, ref); - } -} - void ResourceCache::destructor(Res_png_9patch* resource) { Mutex::Autolock _l(mLock); destructorLocked(resource); @@ -333,11 +294,6 @@ void ResourceCache::deleteResourceReferenceLocked(const void* resource, Resource } } break; - case kShader: { - SkiaShader* shader = (SkiaShader*) resource; - delete shader; - } - break; case kNinePatch: { if (Caches::hasInstance()) { Caches::getInstance().patchCache.removeDeferred((Res_png_9patch*) resource); diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h index 4097ba4..3864d4b 100644 --- a/libs/hwui/ResourceCache.h +++ b/libs/hwui/ResourceCache.h @@ -20,7 +20,6 @@ #include <cutils/compiler.h> #include <SkBitmap.h> -#include <SkiaShader.h> #include <utils/KeyedVector.h> @@ -36,7 +35,6 @@ namespace uirenderer { */ enum ResourceType { kBitmap, - kShader, kNinePatch, kPath, kLayer @@ -70,36 +68,30 @@ public: void incrementRefcount(const SkPath* resource); void incrementRefcount(const SkBitmap* resource); - void incrementRefcount(SkiaShader* resource); void incrementRefcount(const Res_png_9patch* resource); void incrementRefcount(Layer* resource); void incrementRefcountLocked(const SkPath* resource); void incrementRefcountLocked(const SkBitmap* resource); - void incrementRefcountLocked(SkiaShader* resource); void incrementRefcountLocked(const Res_png_9patch* resource); void incrementRefcountLocked(Layer* resource); void decrementRefcount(const SkBitmap* resource); void decrementRefcount(const SkPath* resource); - void decrementRefcount(SkiaShader* resource); void decrementRefcount(const Res_png_9patch* resource); void decrementRefcount(Layer* resource); void decrementRefcountLocked(const SkBitmap* resource); void decrementRefcountLocked(const SkPath* resource); - void decrementRefcountLocked(SkiaShader* resource); void decrementRefcountLocked(const Res_png_9patch* resource); void decrementRefcountLocked(Layer* resource); void destructor(SkPath* resource); void destructor(const SkBitmap* resource); - void destructor(SkiaShader* resource); void destructor(Res_png_9patch* resource); void destructorLocked(SkPath* resource); void destructorLocked(const SkBitmap* resource); - void destructorLocked(SkiaShader* resource); void destructorLocked(Res_png_9patch* resource); bool recycle(SkBitmap* resource); diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 6a4a0c8..c672bc4 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -21,9 +21,10 @@ #include <SkMatrix.h> #include "Caches.h" +#include "Layer.h" +#include "Matrix.h" #include "SkiaShader.h" #include "Texture.h" -#include "Matrix.h" namespace android { namespace uirenderer { @@ -54,89 +55,142 @@ static inline void bindUniformColor(int slot, uint32_t color) { a); } -/////////////////////////////////////////////////////////////////////////////// -// Base shader -/////////////////////////////////////////////////////////////////////////////// - -void SkiaShader::copyFrom(const SkiaShader& shader) { - mType = shader.mType; - mKey = shader.mKey; - mTileX = shader.mTileX; - mTileY = shader.mTileY; - mBlend = shader.mBlend; - mUnitMatrix = shader.mUnitMatrix; - mShaderMatrix = shader.mShaderMatrix; - mGenerationId = shader.mGenerationId; +static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) { + caches->bindTexture(texture->id); + texture->setWrapST(wrapS, wrapT); } -SkiaShader::SkiaShader(): mCaches(NULL) { -} +/** + * Compute the matrix to transform to screen space. + * @param screenSpace Output param for the computed matrix. + * @param unitMatrix The unit matrix for gradient shaders, as returned by SkShader::asAGradient, + * or identity. + * @param localMatrix Local matrix, as returned by SkShader::getLocalMatrix(). + * @param modelViewMatrix Model view matrix, as supplied by the OpenGLRenderer. + */ +static void computeScreenSpaceMatrix(mat4& screenSpace, const SkMatrix& unitMatrix, + const SkMatrix& localMatrix, const mat4& modelViewMatrix) { + mat4 shaderMatrix; + // uses implicit construction + shaderMatrix.loadInverse(localMatrix); + // again, uses implicit construction + screenSpace.loadMultiply(unitMatrix, shaderMatrix); + screenSpace.multiply(modelViewMatrix); +} + +// Returns true if one is a bitmap and the other is a gradient +static bool bitmapAndGradient(SkiaShaderType type1, SkiaShaderType type2) { + return (type1 == kBitmap_SkiaShaderType && type2 == kGradient_SkiaShaderType) + || (type2 == kBitmap_SkiaShaderType && type1 == kGradient_SkiaShaderType); +} + +SkiaShaderType SkiaShader::getType(const SkShader& shader) { + // First check for a gradient shader. + switch (shader.asAGradient(NULL)) { + case SkShader::kNone_GradientType: + // Not a gradient shader. Fall through to check for other types. + break; + case SkShader::kLinear_GradientType: + case SkShader::kRadial_GradientType: + case SkShader::kSweep_GradientType: + return kGradient_SkiaShaderType; + default: + // This is a Skia gradient that has no SkiaShader equivalent. Return None to skip. + return kNone_SkiaShaderType; + } -SkiaShader::SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX, - SkShader::TileMode tileY, const SkMatrix* matrix, bool blend): - mType(type), mKey(key), mTileX(tileX), mTileY(tileY), mBlend(blend), - mCaches(NULL) { - setMatrix(matrix); - mGenerationId = 0; -} + // The shader is not a gradient. Check for a bitmap shader. + if (shader.asABitmap(NULL, NULL, NULL) == SkShader::kDefault_BitmapType) { + return kBitmap_SkiaShaderType; + } -SkiaShader::~SkiaShader() { -} + // Check for a ComposeShader. + SkShader::ComposeRec rec; + if (shader.asACompose(&rec)) { + const SkiaShaderType shaderAType = getType(*rec.fShaderA); + const SkiaShaderType shaderBType = getType(*rec.fShaderB); + + // Compose is only supported if one is a bitmap and the other is a + // gradient. Otherwise, return None to skip. + if (!bitmapAndGradient(shaderAType, shaderBType)) { + return kNone_SkiaShaderType; + } + return kCompose_SkiaShaderType; + } -void SkiaShader::describe(ProgramDescription& description, const Extensions& extensions) { -} + if (shader.asACustomShader(NULL)) { + return kLayer_SkiaShaderType; + } -void SkiaShader::setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, - GLuint* textureUnit) { + return kNone_SkiaShaderType; } -void SkiaShader::bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT) { - mCaches->bindTexture(texture->id); - texture->setWrapST(wrapS, wrapT); +typedef void (*describeProc)(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader); + +describeProc gDescribeProc[] = { + InvalidSkiaShader::describe, + SkiaBitmapShader::describe, + SkiaGradientShader::describe, + SkiaComposeShader::describe, + SkiaLayerShader::describe, +}; + +typedef void (*setupProgramProc)(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader); + +setupProgramProc gSetupProgramProc[] = { + InvalidSkiaShader::setupProgram, + SkiaBitmapShader::setupProgram, + SkiaGradientShader::setupProgram, + SkiaComposeShader::setupProgram, + SkiaLayerShader::setupProgram, +}; + +void SkiaShader::describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader) { + gDescribeProc[getType(shader)](caches, description, extensions, shader); } -void SkiaShader::computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView) { - screenSpace.loadMultiply(mUnitMatrix, mShaderMatrix); - screenSpace.multiply(modelView); +void SkiaShader::setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader) { + + gSetupProgramProc[getType(shader)](caches, modelViewMatrix, textureUnit, extensions, shader); } /////////////////////////////////////////////////////////////////////////////// // Layer shader /////////////////////////////////////////////////////////////////////////////// -SkiaLayerShader::SkiaLayerShader(Layer* layer, const SkMatrix* matrix): - SkiaShader(kBitmap, NULL, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, - matrix, layer->isBlend()), mLayer(layer) { - updateLocalMatrix(matrix); -} - -SkiaShader* SkiaLayerShader::copy() { - SkiaLayerShader* copy = new SkiaLayerShader(); - copy->copyFrom(*this); - copy->mLayer = mLayer; - return copy; -} - -void SkiaLayerShader::describe(ProgramDescription& description, const Extensions& extensions) { +void SkiaLayerShader::describe(Caches*, ProgramDescription& description, + const Extensions&, const SkShader& shader) { description.hasBitmap = true; } -void SkiaLayerShader::setupProgram(Program* program, const mat4& modelView, - const Snapshot& snapshot, GLuint* textureUnit) { +void SkiaLayerShader::setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions&, const SkShader& shader) { + Layer* layer; + if (!shader.asACustomShader(reinterpret_cast<void**>(&layer))) { + LOG_ALWAYS_FATAL("SkiaLayerShader::setupProgram called on the wrong type of shader!"); + } + GLuint textureSlot = (*textureUnit)++; - Caches::getInstance().activeTexture(textureSlot); + caches->activeTexture(textureSlot); - const float width = mLayer->getWidth(); - const float height = mLayer->getHeight(); + const float width = layer->getWidth(); + const float height = layer->getHeight(); mat4 textureTransform; - computeScreenSpaceMatrix(textureTransform, modelView); + computeScreenSpaceMatrix(textureTransform, SkMatrix::I(), shader.getLocalMatrix(), + modelViewMatrix); + // Uniforms - mLayer->bindTexture(); - mLayer->setWrap(GL_CLAMP_TO_EDGE); - mLayer->setFilter(GL_LINEAR); + layer->bindTexture(); + layer->setWrap(GL_CLAMP_TO_EDGE); + layer->setFilter(GL_LINEAR); + Program* program = caches->currentProgram; glUniform1i(program->getUniform("bitmapSampler"), textureSlot); glUniformMatrix4fv(program->getUniform("textureTransform"), 1, GL_FALSE, &textureTransform.data[0]); @@ -147,67 +201,99 @@ void SkiaLayerShader::setupProgram(Program* program, const mat4& modelView, // Bitmap shader /////////////////////////////////////////////////////////////////////////////// -SkiaBitmapShader::SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX, - SkShader::TileMode tileY, SkMatrix* matrix, bool blend): - SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap), mTexture(NULL) { - updateLocalMatrix(matrix); -} - -SkiaShader* SkiaBitmapShader::copy() { - SkiaBitmapShader* copy = new SkiaBitmapShader(); - copy->copyFrom(*this); - copy->mBitmap = mBitmap; - return copy; -} +struct BitmapShaderInfo { + float width; + float height; + GLenum wrapS; + GLenum wrapT; + Texture* texture; +}; -void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) { - Texture* texture = mCaches->textureCache.get(mBitmap); - if (!texture) return; - mTexture = texture; +static bool bitmapShaderHelper(Caches* caches, ProgramDescription* description, + BitmapShaderInfo* shaderInfo, + const Extensions& extensions, + const SkBitmap& bitmap, SkShader::TileMode tileModes[2]) { + Texture* texture = caches->textureCache.get(&bitmap); + if (!texture) return false; const float width = texture->width; const float height = texture->height; + GLenum wrapS, wrapT; - description.hasBitmap = true; + if (description) { + description->hasBitmap = true; + } // The driver does not support non-power of two mirrored/repeated // textures, so do it ourselves if (!extensions.hasNPot() && (!isPowerOfTwo(width) || !isPowerOfTwo(height)) && - (mTileX != SkShader::kClamp_TileMode || mTileY != SkShader::kClamp_TileMode)) { - description.isBitmapNpot = true; - description.bitmapWrapS = gTileModes[mTileX]; - description.bitmapWrapT = gTileModes[mTileY]; - mWrapS = GL_CLAMP_TO_EDGE; - mWrapT = GL_CLAMP_TO_EDGE; + (tileModes[0] != SkShader::kClamp_TileMode || + tileModes[1] != SkShader::kClamp_TileMode)) { + if (description) { + description->isBitmapNpot = true; + description->bitmapWrapS = gTileModes[tileModes[0]]; + description->bitmapWrapT = gTileModes[tileModes[1]]; + } + wrapS = GL_CLAMP_TO_EDGE; + wrapT = GL_CLAMP_TO_EDGE; } else { - mWrapS = gTileModes[mTileX]; - mWrapT = gTileModes[mTileY]; + wrapS = gTileModes[tileModes[0]]; + wrapT = gTileModes[tileModes[1]]; + } + + if (shaderInfo) { + shaderInfo->width = width; + shaderInfo->height = height; + shaderInfo->wrapS = wrapS; + shaderInfo->wrapT = wrapT; + shaderInfo->texture = texture; } + return true; } -void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView, - const Snapshot&, GLuint* textureUnit) { +void SkiaBitmapShader::describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader) { + SkBitmap bitmap; + SkShader::TileMode xy[2]; + if (shader.asABitmap(&bitmap, NULL, xy) != SkShader::kDefault_BitmapType) { + LOG_ALWAYS_FATAL("SkiaBitmapShader::describe called with a different kind of shader!"); + } + bitmapShaderHelper(caches, &description, NULL, extensions, bitmap, xy); +} + +void SkiaBitmapShader::setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader) { + SkBitmap bitmap; + SkShader::TileMode xy[2]; + if (shader.asABitmap(&bitmap, NULL, xy) != SkShader::kDefault_BitmapType) { + LOG_ALWAYS_FATAL("SkiaBitmapShader::setupProgram called with a different kind of shader!"); + } + GLuint textureSlot = (*textureUnit)++; Caches::getInstance().activeTexture(textureSlot); - Texture* texture = mTexture; - mTexture = NULL; - if (!texture) return; - const AutoTexture autoCleanup(texture); + BitmapShaderInfo shaderInfo; + if (!bitmapShaderHelper(caches, NULL, &shaderInfo, extensions, bitmap, xy)) { + return; + } - const float width = texture->width; - const float height = texture->height; + Program* program = caches->currentProgram; + Texture* texture = shaderInfo.texture; + + const AutoTexture autoCleanup(texture); mat4 textureTransform; - computeScreenSpaceMatrix(textureTransform, modelView); + computeScreenSpaceMatrix(textureTransform, SkMatrix::I(), shader.getLocalMatrix(), + modelViewMatrix); // Uniforms - bindTexture(texture, mWrapS, mWrapT); + bindTexture(caches, texture, shaderInfo.wrapS, shaderInfo.wrapT); texture->setFilter(GL_LINEAR); glUniform1i(program->getUniform("bitmapSampler"), textureSlot); glUniformMatrix4fv(program->getUniform("textureTransform"), 1, GL_FALSE, &textureTransform.data[0]); - glUniform2f(program->getUniform("textureDimension"), 1.0f / width, 1.0f / height); + glUniform2f(program->getUniform("textureDimension"), 1.0f / shaderInfo.width, + 1.0f / shaderInfo.height); } /////////////////////////////////////////////////////////////////////////////// @@ -225,74 +311,6 @@ static void toUnitMatrix(const SkPoint pts[2], SkMatrix* matrix) { matrix->postScale(inv, inv); } -SkiaLinearGradientShader::SkiaLinearGradientShader(float* bounds, uint32_t* colors, - float* positions, int count, SkShader* key, SkShader::TileMode tileMode, - SkMatrix* matrix, bool blend): - SkiaShader(kLinearGradient, key, tileMode, tileMode, matrix, blend), - mBounds(bounds), mColors(colors), mPositions(positions), mCount(count) { - SkPoint points[2]; - points[0].set(bounds[0], bounds[1]); - points[1].set(bounds[2], bounds[3]); - - SkMatrix unitMatrix; - toUnitMatrix(points, &unitMatrix); - mUnitMatrix.load(unitMatrix); - - updateLocalMatrix(matrix); - - mIsSimple = count == 2 && tileMode == SkShader::kClamp_TileMode; -} - -SkiaLinearGradientShader::~SkiaLinearGradientShader() { - delete[] mBounds; - delete[] mColors; - delete[] mPositions; -} - -SkiaShader* SkiaLinearGradientShader::copy() { - SkiaLinearGradientShader* copy = new SkiaLinearGradientShader(); - copy->copyFrom(*this); - copy->mBounds = new float[4]; - memcpy(copy->mBounds, mBounds, sizeof(float) * 4); - copy->mColors = new uint32_t[mCount]; - memcpy(copy->mColors, mColors, sizeof(uint32_t) * mCount); - copy->mPositions = new float[mCount]; - memcpy(copy->mPositions, mPositions, sizeof(float) * mCount); - copy->mCount = mCount; - copy->mIsSimple = mIsSimple; - return copy; -} - -void SkiaLinearGradientShader::describe(ProgramDescription& description, - const Extensions& extensions) { - description.hasGradient = true; - description.gradientType = ProgramDescription::kGradientLinear; - description.isSimpleGradient = mIsSimple; -} - -void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelView, - const Snapshot&, GLuint* textureUnit) { - if (CC_UNLIKELY(!mIsSimple)) { - GLuint textureSlot = (*textureUnit)++; - Caches::getInstance().activeTexture(textureSlot); - - Texture* texture = mCaches->gradientCache.get(mColors, mPositions, mCount); - - // Uniforms - bindTexture(texture, gTileModes[mTileX], gTileModes[mTileY]); - glUniform1i(program->getUniform("gradientSampler"), textureSlot); - } else { - bindUniformColor(program->getUniform("startColor"), mColors[0]); - bindUniformColor(program->getUniform("endColor"), mColors[1]); - } - - Caches::getInstance().dither.setupProgram(program, textureUnit); - - mat4 screenSpace; - computeScreenSpaceMatrix(screenSpace, modelView); - glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]); -} - /////////////////////////////////////////////////////////////////////////////// // Circular gradient shader /////////////////////////////////////////////////////////////////////////////// @@ -304,37 +322,6 @@ static void toCircularUnitMatrix(const float x, const float y, const float radiu matrix->postScale(inv, inv); } -SkiaCircularGradientShader::SkiaCircularGradientShader(float x, float y, float radius, - uint32_t* colors, float* positions, int count, SkShader* key, SkShader::TileMode tileMode, - SkMatrix* matrix, bool blend): - SkiaSweepGradientShader(kCircularGradient, colors, positions, count, key, - tileMode, matrix, blend) { - SkMatrix unitMatrix; - toCircularUnitMatrix(x, y, radius, &unitMatrix); - mUnitMatrix.load(unitMatrix); - - updateLocalMatrix(matrix); -} - -SkiaShader* SkiaCircularGradientShader::copy() { - SkiaCircularGradientShader* copy = new SkiaCircularGradientShader(); - copy->copyFrom(*this); - copy->mColors = new uint32_t[mCount]; - memcpy(copy->mColors, mColors, sizeof(uint32_t) * mCount); - copy->mPositions = new float[mCount]; - memcpy(copy->mPositions, mPositions, sizeof(float) * mCount); - copy->mCount = mCount; - copy->mIsSimple = mIsSimple; - return copy; -} - -void SkiaCircularGradientShader::describe(ProgramDescription& description, - const Extensions& extensions) { - description.hasGradient = true; - description.gradientType = ProgramDescription::kGradientCircular; - description.isSimpleGradient = mIsSimple; -} - /////////////////////////////////////////////////////////////////////////////// // Sweep gradient shader /////////////////////////////////////////////////////////////////////////////// @@ -343,74 +330,103 @@ static void toSweepUnitMatrix(const float x, const float y, SkMatrix* matrix) { matrix->setTranslate(-x, -y); } -SkiaSweepGradientShader::SkiaSweepGradientShader(float x, float y, uint32_t* colors, - float* positions, int count, SkShader* key, SkMatrix* matrix, bool blend): - SkiaShader(kSweepGradient, key, SkShader::kClamp_TileMode, - SkShader::kClamp_TileMode, matrix, blend), - mColors(colors), mPositions(positions), mCount(count) { - SkMatrix unitMatrix; - toSweepUnitMatrix(x, y, &unitMatrix); - mUnitMatrix.load(unitMatrix); - - updateLocalMatrix(matrix); - - mIsSimple = count == 2; -} - -SkiaSweepGradientShader::SkiaSweepGradientShader(Type type, uint32_t* colors, - float* positions, int count, SkShader* key, SkShader::TileMode tileMode, - SkMatrix* matrix, bool blend): - SkiaShader(type, key, tileMode, tileMode, matrix, blend), - mColors(colors), mPositions(positions), mCount(count) { - // protected method, that doesn't setup mUnitMatrix - should be handled by subclass - - mIsSimple = count == 2 && tileMode == SkShader::kClamp_TileMode; -} - -SkiaSweepGradientShader::~SkiaSweepGradientShader() { - delete[] mColors; - delete[] mPositions; -} - -SkiaShader* SkiaSweepGradientShader::copy() { - SkiaSweepGradientShader* copy = new SkiaSweepGradientShader(); - copy->copyFrom(*this); - copy->mColors = new uint32_t[mCount]; - memcpy(copy->mColors, mColors, sizeof(uint32_t) * mCount); - copy->mPositions = new float[mCount]; - memcpy(copy->mPositions, mPositions, sizeof(float) * mCount); - copy->mCount = mCount; - copy->mIsSimple = mIsSimple; - return copy; -} +/////////////////////////////////////////////////////////////////////////////// +// Common gradient code +/////////////////////////////////////////////////////////////////////////////// -void SkiaSweepGradientShader::describe(ProgramDescription& description, - const Extensions& extensions) { +static bool isSimpleGradient(const SkShader::GradientInfo& gradInfo) { + return gradInfo.fColorCount == 2 && gradInfo.fTileMode == SkShader::kClamp_TileMode; +} + +void SkiaGradientShader::describe(Caches*, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader) { + SkShader::GradientInfo gradInfo; + gradInfo.fColorCount = 0; + gradInfo.fColors = NULL; + gradInfo.fColorOffsets = NULL; + + switch (shader.asAGradient(&gradInfo)) { + case SkShader::kLinear_GradientType: + description.gradientType = ProgramDescription::kGradientLinear; + break; + case SkShader::kRadial_GradientType: + description.gradientType = ProgramDescription::kGradientCircular; + break; + case SkShader::kSweep_GradientType: + description.gradientType = ProgramDescription::kGradientSweep; + break; + default: + // Do nothing. This shader is unsupported. + return; + } description.hasGradient = true; - description.gradientType = ProgramDescription::kGradientSweep; - description.isSimpleGradient = mIsSimple; -} - -void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelView, - const Snapshot& snapshot, GLuint* textureUnit) { - if (CC_UNLIKELY(!mIsSimple)) { + description.isSimpleGradient = isSimpleGradient(gradInfo); +} + +void SkiaGradientShader::setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions&, const SkShader& shader) { + // SkShader::GradientInfo.fColorCount is an in/out parameter. As input, it tells asAGradient + // how much space has been allocated for fColors and fColorOffsets. 10 was chosen + // arbitrarily, but should be >= 2. + // As output, it tells the number of actual colors/offsets in the gradient. + const int COLOR_COUNT = 10; + SkAutoSTMalloc<COLOR_COUNT, SkColor> colorStorage(COLOR_COUNT); + SkAutoSTMalloc<COLOR_COUNT, SkScalar> positionStorage(COLOR_COUNT); + + SkShader::GradientInfo gradInfo; + gradInfo.fColorCount = COLOR_COUNT; + gradInfo.fColors = colorStorage.get(); + gradInfo.fColorOffsets = positionStorage.get(); + + SkShader::GradientType gradType = shader.asAGradient(&gradInfo); + + Program* program = caches->currentProgram; + if (CC_UNLIKELY(!isSimpleGradient(gradInfo))) { + if (gradInfo.fColorCount > COLOR_COUNT) { + // There was not enough room in our arrays for all the colors and offsets. Try again, + // now that we know the true number of colors. + gradInfo.fColors = colorStorage.reset(gradInfo.fColorCount); + gradInfo.fColorOffsets = positionStorage.reset(gradInfo.fColorCount); + + shader.asAGradient(&gradInfo); + } GLuint textureSlot = (*textureUnit)++; - Caches::getInstance().activeTexture(textureSlot); + caches->activeTexture(textureSlot); - Texture* texture = mCaches->gradientCache.get(mColors, mPositions, mCount); +#ifndef SK_SCALAR_IS_FLOAT + #error Need to convert gradInfo.fColorOffsets to float! +#endif + Texture* texture = caches->gradientCache.get(gradInfo.fColors, gradInfo.fColorOffsets, + gradInfo.fColorCount); // Uniforms - bindTexture(texture, gTileModes[mTileX], gTileModes[mTileY]); + bindTexture(caches, texture, gTileModes[gradInfo.fTileMode], gTileModes[gradInfo.fTileMode]); glUniform1i(program->getUniform("gradientSampler"), textureSlot); } else { - bindUniformColor(program->getUniform("startColor"), mColors[0]); - bindUniformColor(program->getUniform("endColor"), mColors[1]); + bindUniformColor(program->getUniform("startColor"), gradInfo.fColors[0]); + bindUniformColor(program->getUniform("endColor"), gradInfo.fColors[1]); } - mCaches->dither.setupProgram(program, textureUnit); + caches->dither.setupProgram(program, textureUnit); + + SkMatrix unitMatrix; + switch (gradType) { + case SkShader::kLinear_GradientType: + toUnitMatrix(gradInfo.fPoint, &unitMatrix); + break; + case SkShader::kRadial_GradientType: + toCircularUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY, + gradInfo.fRadius[0], &unitMatrix); + break; + case SkShader::kSweep_GradientType: + toSweepUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY, &unitMatrix); + break; + default: + LOG_ALWAYS_FATAL("Invalid SkShader gradient type %d", gradType); + } mat4 screenSpace; - computeScreenSpaceMatrix(screenSpace, modelView); + computeScreenSpaceMatrix(screenSpace, unitMatrix, shader.getLocalMatrix(), modelViewMatrix); glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]); } @@ -418,49 +434,39 @@ void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelVi // Compose shader /////////////////////////////////////////////////////////////////////////////// -SkiaComposeShader::SkiaComposeShader(SkiaShader* first, SkiaShader* second, - SkXfermode::Mode mode, SkShader* key): - SkiaShader(kCompose, key, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, - NULL, first->blend() || second->blend()), - mFirst(first), mSecond(second), mMode(mode), mCleanup(false) { -} - -SkiaComposeShader::~SkiaComposeShader() { - if (mCleanup) { - delete mFirst; - delete mSecond; +void SkiaComposeShader::describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader) { + SkShader::ComposeRec rec; + if (!shader.asACompose(&rec)) { + LOG_ALWAYS_FATAL("SkiaComposeShader::describe called on the wrong shader type!"); } -} - -SkiaShader* SkiaComposeShader::copy() { - SkiaComposeShader* copy = new SkiaComposeShader(); - copy->copyFrom(*this); - copy->mFirst = mFirst->copy(); - copy->mSecond = mSecond->copy(); - copy->mMode = mMode; - copy->cleanup(); - return copy; -} - -void SkiaComposeShader::describe(ProgramDescription& description, const Extensions& extensions) { - mFirst->describe(description, extensions); - mSecond->describe(description, extensions); - if (mFirst->type() == kBitmap) { + SkiaShader::describe(caches, description, extensions, *rec.fShaderA); + SkiaShader::describe(caches, description, extensions, *rec.fShaderB); + if (SkiaShader::getType(*rec.fShaderA) == kBitmap_SkiaShaderType) { description.isBitmapFirst = true; } - description.shadersMode = mMode; + if (!SkXfermode::AsMode(rec.fMode, &description.shadersMode)) { + // TODO: Support other modes. + description.shadersMode = SkXfermode::kSrcOver_Mode; + } } -void SkiaComposeShader::setupProgram(Program* program, const mat4& modelView, - const Snapshot& snapshot, GLuint* textureUnit) { +void SkiaComposeShader::setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader) { + SkShader::ComposeRec rec; + if (!shader.asACompose(&rec)) { + LOG_ALWAYS_FATAL("SkiaComposeShader::setupProgram called on the wrong shader type!"); + } + // Apply this compose shader's local transform and pass it down to // the child shaders. They will in turn apply their local transform // to this matrix. mat4 transform; - computeScreenSpaceMatrix(transform, modelView); + computeScreenSpaceMatrix(transform, SkMatrix::I(), shader.getLocalMatrix(), + modelViewMatrix); - mFirst->setupProgram(program, transform, snapshot, textureUnit); - mSecond->setupProgram(program, transform, snapshot, textureUnit); + SkiaShader::setupProgram(caches, transform, textureUnit, extensions, *rec.fShaderA); + SkiaShader::setupProgram(caches, transform, textureUnit, extensions, *rec.fShaderB); } }; // namespace uirenderer diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h index 9f30257..034c3f6 100644 --- a/libs/hwui/SkiaShader.h +++ b/libs/hwui/SkiaShader.h @@ -28,249 +28,90 @@ #include "ProgramCache.h" #include "TextureCache.h" #include "GradientCache.h" -#include "Snapshot.h" namespace android { namespace uirenderer { class Caches; - -/////////////////////////////////////////////////////////////////////////////// -// Base shader -/////////////////////////////////////////////////////////////////////////////// +class Layer; /** - * Represents a Skia shader. A shader will modify the GL context and active - * program to recreate the original effect. + * Type of Skia shader in use. */ +enum SkiaShaderType { + kNone_SkiaShaderType, + kBitmap_SkiaShaderType, + kGradient_SkiaShaderType, + kCompose_SkiaShaderType, + kLayer_SkiaShaderType +}; + class SkiaShader { public: - /** - * Type of Skia shader in use. - */ - enum Type { - kNone, - kBitmap, - kLinearGradient, - kCircularGradient, - kSweepGradient, - kCompose - }; - - ANDROID_API SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX, - SkShader::TileMode tileY, const SkMatrix* matrix, bool blend); - virtual ~SkiaShader(); - - virtual SkiaShader* copy() = 0; - void copyFrom(const SkiaShader& shader); - - virtual void describe(ProgramDescription& description, const Extensions& extensions); - virtual void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, - GLuint* textureUnit); - - inline SkShader* getSkShader() { - return mKey; - } - - inline bool blend() const { - return mBlend; - } - - Type type() const { - return mType; - } - - virtual void setCaches(Caches& caches) { - mCaches = &caches; - } - - uint32_t getGenerationId() { - return mGenerationId; - } - - void setMatrix(const SkMatrix* matrix) { - updateLocalMatrix(matrix); - mGenerationId++; + static SkiaShaderType getType(const SkShader& shader); + static void describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader); + static void setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader); +}; + +class InvalidSkiaShader { +public: + static void describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader) { + // This shader is unsupported. Skip it. } - - void updateLocalMatrix(const SkMatrix* matrix) { - if (matrix) { - mat4 localMatrix(*matrix); - mShaderMatrix.loadInverse(localMatrix); - } else { - mShaderMatrix.loadIdentity(); - } + static void setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader) { + // This shader is unsupported. Skip it. } - void computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView); - -protected: - SkiaShader(); - - /** - * The appropriate texture unit must have been activated prior to invoking - * this method. - */ - inline void bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT); - - Type mType; - SkShader* mKey; - SkShader::TileMode mTileX; - SkShader::TileMode mTileY; - bool mBlend; - - Caches* mCaches; - - mat4 mUnitMatrix; - mat4 mShaderMatrix; - -private: - uint32_t mGenerationId; -}; // struct SkiaShader - - -/////////////////////////////////////////////////////////////////////////////// -// Implementations -/////////////////////////////////////////////////////////////////////////////// - +}; /** * A shader that draws a layer. */ -struct SkiaLayerShader: public SkiaShader { - SkiaLayerShader(Layer* layer, const SkMatrix* matrix); - SkiaShader* copy(); - - void describe(ProgramDescription& description, const Extensions& extensions); - void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, - GLuint* textureUnit); - -private: - SkiaLayerShader() { - } - - Layer* mLayer; -}; // struct SkiaLayerShader +class SkiaLayerShader { +public: + static void describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader); + static void setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader); +}; // class SkiaLayerShader /** * A shader that draws a bitmap. */ -struct SkiaBitmapShader: public SkiaShader { - ANDROID_API SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX, - SkShader::TileMode tileY, SkMatrix* matrix, bool blend); - SkiaShader* copy(); - - void describe(ProgramDescription& description, const Extensions& extensions); - void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, - GLuint* textureUnit); - -private: - SkiaBitmapShader() : mBitmap(NULL), mTexture(NULL) { - } - - SkBitmap* mBitmap; - Texture* mTexture; - GLenum mWrapS; - GLenum mWrapT; -}; // struct SkiaBitmapShader - -/** - * A shader that draws a linear gradient. - */ -struct SkiaLinearGradientShader: public SkiaShader { - ANDROID_API SkiaLinearGradientShader(float* bounds, uint32_t* colors, float* positions, - int count, SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend); - ~SkiaLinearGradientShader(); - SkiaShader* copy(); - - void describe(ProgramDescription& description, const Extensions& extensions); - void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, - GLuint* textureUnit); +class SkiaBitmapShader { +public: + static void describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader); + static void setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader); -private: - SkiaLinearGradientShader() { - } - bool mIsSimple; - float* mBounds; - uint32_t* mColors; - float* mPositions; - int mCount; -}; // struct SkiaLinearGradientShader +}; // class SkiaBitmapShader /** - * A shader that draws a sweep gradient. + * A shader that draws one of three types of gradient, depending on shader param. */ -struct SkiaSweepGradientShader: public SkiaShader { - ANDROID_API SkiaSweepGradientShader(float x, float y, uint32_t* colors, float* positions, - int count, SkShader* key, SkMatrix* matrix, bool blend); - ~SkiaSweepGradientShader(); - SkiaShader* copy(); - - virtual void describe(ProgramDescription& description, const Extensions& extensions); - void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, - GLuint* textureUnit); - -protected: - SkiaSweepGradientShader(Type type, uint32_t* colors, float* positions, - int count, SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend); - SkiaSweepGradientShader() { - } - - bool mIsSimple; - uint32_t* mColors; - float* mPositions; - int mCount; -}; // struct SkiaSweepGradientShader - -/** - * A shader that draws a circular gradient. - */ -struct SkiaCircularGradientShader: public SkiaSweepGradientShader { - ANDROID_API SkiaCircularGradientShader(float x, float y, float radius, uint32_t* colors, - float* positions, int count, SkShader* key,SkShader::TileMode tileMode, - SkMatrix* matrix, bool blend); - SkiaShader* copy(); - - void describe(ProgramDescription& description, const Extensions& extensions); - -private: - SkiaCircularGradientShader() { - } -}; // struct SkiaCircularGradientShader +class SkiaGradientShader { +public: + static void describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader); + static void setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader); +}; /** * A shader that draws two shaders, composited with an xfermode. */ -struct SkiaComposeShader: public SkiaShader { - ANDROID_API SkiaComposeShader(SkiaShader* first, SkiaShader* second, SkXfermode::Mode mode, - SkShader* key); - ~SkiaComposeShader(); - SkiaShader* copy(); - - void setCaches(Caches& caches) { - SkiaShader::setCaches(caches); - mFirst->setCaches(caches); - mSecond->setCaches(caches); - } - - void describe(ProgramDescription& description, const Extensions& extensions); - void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, - GLuint* textureUnit); - -private: - SkiaComposeShader(): mCleanup(false) { - } - - void cleanup() { - mCleanup = true; - } - - SkiaShader* mFirst; - SkiaShader* mSecond; - SkXfermode::Mode mMode; - - bool mCleanup; -}; // struct SkiaComposeShader +class SkiaComposeShader { +public: + static void describe(Caches* caches, ProgramDescription& description, + const Extensions& extensions, const SkShader& shader); + static void setupProgram(Caches* caches, const mat4& modelViewMatrix, + GLuint* textureUnit, const Extensions& extensions, const SkShader& shader); +}; // class SkiaComposeShader }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 34e2265..60b4b96 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -34,7 +34,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// TextureCache::TextureCache(): - mCache(LruCache<const SkBitmap*, Texture*>::kUnlimitedCapacity), + mCache(LruCache<const SkPixelRef*, Texture*>::kUnlimitedCapacity), mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)), mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE) { char property[PROPERTY_VALUE_MAX]; @@ -58,7 +58,7 @@ TextureCache::TextureCache(): } TextureCache::TextureCache(uint32_t maxByteSize): - mCache(LruCache<const SkBitmap*, Texture*>::kUnlimitedCapacity), + mCache(LruCache<const SkPixelRef*, Texture*>::kUnlimitedCapacity), mSize(0), mMaxSize(maxByteSize) { init(); } @@ -103,7 +103,7 @@ void TextureCache::setFlushRate(float flushRate) { // Callbacks /////////////////////////////////////////////////////////////////////////////// -void TextureCache::operator()(const SkBitmap*&, Texture*& texture) { +void TextureCache::operator()(const SkPixelRef*&, Texture*& texture) { // This will be called already locked if (texture) { mSize -= texture->bitmapSize; @@ -122,7 +122,7 @@ void TextureCache::operator()(const SkBitmap*&, Texture*& texture) { /////////////////////////////////////////////////////////////////////////////// void TextureCache::resetMarkInUse() { - LruCache<const SkBitmap*, Texture*>::Iterator iter(mCache); + LruCache<const SkPixelRef*, Texture*>::Iterator iter(mCache); while (iter.next()) { iter.value()->isInUse = false; } @@ -140,7 +140,7 @@ bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) { // Returns a prepared Texture* that either is already in the cache or can fit // in the cache (and is thus added to the cache) Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) { - Texture* texture = mCache.get(bitmap); + Texture* texture = mCache.get(bitmap->pixelRef()); if (!texture) { if (!canMakeTextureFromBitmap(bitmap)) { @@ -170,7 +170,7 @@ Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) { if (mDebugEnabled) { ALOGD("Texture created, size = %d", size); } - mCache.put(bitmap, texture); + mCache.put(bitmap->pixelRef(), texture); } } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) { // Texture was in the cache but is dirty, re-upload @@ -218,7 +218,7 @@ Texture* TextureCache::getTransient(const SkBitmap* bitmap) { } void TextureCache::remove(const SkBitmap* bitmap) { - mCache.remove(bitmap); + mCache.remove(bitmap->pixelRef()); } void TextureCache::removeDeferred(const SkBitmap* bitmap) { @@ -231,7 +231,7 @@ void TextureCache::clearGarbage() { size_t count = mGarbage.size(); for (size_t i = 0; i < count; i++) { const SkBitmap* bitmap = mGarbage.itemAt(i); - mCache.remove(bitmap); + mCache.remove(bitmap->pixelRef()); delete bitmap; } mGarbage.clear(); diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h index 48a10c2..e5b5c1a 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -49,7 +49,7 @@ namespace uirenderer { * Any texture added to the cache causing the cache to grow beyond the maximum * allowed size will also cause the oldest texture to be kicked out. */ -class TextureCache: public OnEntryRemoved<const SkBitmap*, Texture*> { +class TextureCache: public OnEntryRemoved<const SkPixelRef*, Texture*> { public: TextureCache(); TextureCache(uint32_t maxByteSize); @@ -59,7 +59,7 @@ public: * Used as a callback when an entry is removed from the cache. * Do not invoke directly. */ - void operator()(const SkBitmap*& bitmap, Texture*& texture); + void operator()(const SkPixelRef*& pixelRef, Texture*& texture); /** * Resets all Textures to not be marked as in use @@ -147,7 +147,7 @@ private: void init(); - LruCache<const SkBitmap*, Texture*> mCache; + LruCache<const SkPixelRef*, Texture*> mCache; uint32_t mSize; uint32_t mMaxSize; diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java index 2685447..d2bf30c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java @@ -112,7 +112,9 @@ public class KeyguardHostView extends KeyguardViewBase { } public interface OnDismissAction { - /* returns true if the dismiss should be deferred */ + /** + * @return true if the dismiss should be deferred + */ boolean onDismiss(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index ef7fb89..a1e70b9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -83,7 +83,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> { boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) && (cb.enabledDesc != null); boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.enabledDesc == null); - state.enabled = wifiConnected; + state.enabled = cb.enabled; state.connected = wifiConnected; state.activityIn = cb.enabled && cb.activityIn; state.activityOut = cb.enabled && cb.activityOut; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 9435e85..cda84cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -84,6 +84,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; +import static com.android.keyguard.KeyguardHostView.OnDismissAction; + public abstract class BaseStatusBar extends SystemUI implements CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener { public static final String TAG = "StatusBar"; @@ -178,6 +180,7 @@ public abstract class BaseStatusBar extends SystemUI implements */ protected int mState; protected boolean mBouncerShowing; + protected boolean mShowLockscreenNotifications; protected NotificationOverflowContainer mKeyguardIconOverflowContainer; @@ -197,6 +200,9 @@ public abstract class BaseStatusBar extends SystemUI implements final int mode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); setZenMode(mode); + final boolean show = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1) != 0; + setShowLockscreenNotifications(show); } }; @@ -213,33 +219,47 @@ public abstract class BaseStatusBar extends SystemUI implements private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { @Override - public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { + public boolean onClickHandler( + final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { if (DEBUG) { Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); } final boolean isActivity = pendingIntent.isActivity(); if (isActivity) { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManagerNative.getDefault().resumeAppSwitches(); - // Also, notifications can be launched from the lock screen, - // so dismiss the lock screen when the activity starts. - ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); - } catch (RemoteException e) { - } - } + startNotificationActivity(new OnDismissAction() { + @Override + public boolean onDismiss() { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManagerNative.getDefault().resumeAppSwitches(); + // Also, notifications can be launched from the lock screen, + // so dismiss the lock screen when the activity starts. + ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); + } catch (RemoteException e) { + } - boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent); + boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent); - if (isActivity && handled) { - // close the shade if it was open - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); - visibilityChanged(false); + // close the shade if it was open + if (handled) { + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + visibilityChanged(false); + } + return handled; // Wait for activity start. + } + }); + return true; + } else { + return super.onClickHandler(view, pendingIntent, fillInIntent); } - return handled; + } + + private boolean superOnClickHandler(View view, PendingIntent pendingIntent, + Intent fillInIntent) { + return super.onClickHandler(view, pendingIntent, fillInIntent); } }; @@ -328,6 +348,9 @@ public abstract class BaseStatusBar extends SystemUI implements mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, mSettingsObserver); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, + mSettingsObserver); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), @@ -434,6 +457,14 @@ public abstract class BaseStatusBar extends SystemUI implements } } + /** + * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. + * @param action A dismiss action that is called if it's safe to start the activity. + */ + protected void startNotificationActivity(OnDismissAction action) { + action.onDismiss(); + } + @Override protected void onConfigurationChanged(Configuration newConfig) { final Locale locale = mContext.getResources().getConfiguration().locale; @@ -999,47 +1030,55 @@ public abstract class BaseStatusBar extends SystemUI implements mIsHeadsUp = forHun; } - public void onClick(View v) { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManagerNative.getDefault().resumeAppSwitches(); - // Also, notifications can be launched from the lock screen, - // so dismiss the lock screen when the activity starts. - ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); - } catch (RemoteException e) { - } + public void onClick(final View v) { + startNotificationActivity(new OnDismissAction() { + public boolean onDismiss() { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManagerNative.getDefault().resumeAppSwitches(); + // Also, notifications can be launched from the lock screen, + // so dismiss the lock screen when the activity starts. + ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); + } catch (RemoteException e) { + } - if (mIntent != null) { - int[] pos = new int[2]; - v.getLocationOnScreen(pos); - Intent overlay = new Intent(); - overlay.setSourceBounds( - new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); - try { - mIntent.send(mContext, 0, overlay); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. Just log the exception message. - Log.w(TAG, "Sending contentIntent failed: " + e); - } + boolean sent = false; + if (mIntent != null) { + int[] pos = new int[2]; + v.getLocationOnScreen(pos); + Intent overlay = new Intent(); + overlay.setSourceBounds(new Rect(pos[0], pos[1], + pos[0]+v.getWidth(), pos[1]+v.getHeight())); + try { + mIntent.send(mContext, 0, overlay); + sent = true; + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending contentIntent failed: " + e); + } + } - KeyguardTouchDelegate.getInstance(mContext).dismiss(); - } + try { + if (mIsHeadsUp) { + mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); + } + mBarService.onNotificationClick(mNotificationKey); + } catch (RemoteException ex) { + // system process is dead if we're here. + } - try { - if (mIsHeadsUp) { - mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); - } - mBarService.onNotificationClick(mNotificationKey); - } catch (RemoteException ex) { - // system process is dead if we're here. - } + // close the shade if it was open + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + visibilityChanged(false); - // close the shade if it was open - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); - visibilityChanged(false); + boolean waitForActivityLaunch = sent && mIntent.isActivity(); + return waitForActivityLaunch; + } + }); } } @@ -1218,6 +1257,10 @@ public abstract class BaseStatusBar extends SystemUI implements updateNotificationIcons(); } + protected void setShowLockscreenNotifications(boolean show) { + mShowLockscreenNotifications = show; + } + protected abstract void haltTicker(); protected abstract void setAreThereNotifications(); protected abstract void updateNotificationIcons(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index d8e1766..9138867 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -28,6 +28,7 @@ import com.android.keyguard.R; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.keyguard.KeyguardViewMediator; +import static com.android.keyguard.KeyguardHostView.OnDismissAction; import static com.android.keyguard.KeyguardSecurityModel.*; /** @@ -69,6 +70,12 @@ public class KeyguardBouncer { } } + public void showWithDismissAction(OnDismissAction r) { + ensureView(); + mKeyguardView.setOnDismissAction(r); + show(); + } + public void hide() { if (mKeyguardView != null) { mKeyguardView.cleanUp(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 772681e..b371b91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -22,6 +22,7 @@ import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.windowStateToString; +import static com.android.keyguard.KeyguardHostView.OnDismissAction; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; @@ -499,6 +500,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override + protected void setShowLockscreenNotifications(boolean show) { + super.setShowLockscreenNotifications(show); + updateStackScrollerState(); + } + + @Override public void start() { mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); @@ -2346,6 +2353,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }; + @Override + protected void startNotificationActivity(OnDismissAction action) { + if (mStatusBarKeyguardViewManager.isShowing()) { + mStatusBarKeyguardViewManager.dismissWithAction(action); + } else { + action.onDismiss(); + } + } + // SystemUIService notifies SystemBars of configuration changes, which then calls down here @Override protected void onConfigurationChanged(Configuration newConfig) { @@ -2775,7 +2791,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public void updateStackScrollerState() { + if (mStackScroller == null) return; mStackScroller.setDimmed(mState == StatusBarState.KEYGUARD, false /* animate */); + mStackScroller.setVisibility(!mShowLockscreenNotifications && mState == StatusBarState.KEYGUARD + ? View.INVISIBLE : View.VISIBLE); } public void userActivity() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 1040c15..3849d8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -29,6 +29,8 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; +import static com.android.keyguard.KeyguardHostView.OnDismissAction; + /** * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done, @@ -108,6 +110,13 @@ public class StatusBarKeyguardViewManager { updateStates(); } + public void dismissWithAction(OnDismissAction r) { + if (!mOccluded) { + mBouncer.showWithDismissAction(r); + } + updateStates(); + } + /** * Reset the state of the view. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 966c0b0..56402a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -339,7 +339,7 @@ public class NetworkControllerImpl extends BroadcastReceiver boolean wifiOut = wifiEnabled && mWifiSsid != null && (mWifiActivity == WifiManager.DATA_ACTIVITY_INOUT || mWifiActivity == WifiManager.DATA_ACTIVITY_OUT); - cb.onWifiSignalChanged(wifiEnabled, mQSWifiIconId, wifiIn, wifiOut, + cb.onWifiSignalChanged(mWifiEnabled, mQSWifiIconId, wifiIn, wifiOut, mContentDescriptionWifi, wifiDesc); boolean mobileIn = mDataConnected && (mDataActivity == TelephonyManager.DATA_ACTIVITY_INOUT diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 9977193..d909568 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -66,6 +66,7 @@ import android.os.Vibrator; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; +import android.telephony.TelephonyManager; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -1921,9 +1922,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { ServiceManager.checkService(DreamService.DREAM_SERVICE)); } - static ITelephony getTelephonyService() { - return ITelephony.Stub.asInterface( - ServiceManager.checkService(Context.TELEPHONY_SERVICE)); + TelephonyManager getTelephonyService() { + return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); } static IAudioService getAudioService() { @@ -2006,14 +2006,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If an incoming call is ringing, HOME is totally disabled. // (The user is already on the InCallScreen at this point, // and his ONLY options are to answer or reject the call.) - try { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null && telephonyService.isRinging()) { - Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); - return -1; - } - } catch (RemoteException ex) { - Log.w(TAG, "RemoteException from getPhoneInterface()", ex); + TelephonyManager telephonyManager = getTelephonyService(); + if (telephonyManager != null && telephonyManager.isRinging()) { + Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); + return -1; } // Delay handling home if a double-tap is possible. @@ -3957,37 +3953,33 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } if (down) { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null) { - try { - if (telephonyService.isRinging()) { - // If an incoming call is ringing, either VOLUME key means - // "silence ringer". We handle these keys here, rather than - // in the InCallScreen, to make sure we'll respond to them - // even if the InCallScreen hasn't come to the foreground yet. - // Look for the DOWN event here, to agree with the "fallback" - // behavior in the InCallScreen. - Log.i(TAG, "interceptKeyBeforeQueueing:" - + " VOLUME key-down while ringing: Silence ringer!"); - - // Silence the ringer. (It's safe to call this - // even if the ringer has already been silenced.) - telephonyService.silenceRinger(); - - // And *don't* pass this key thru to the current activity - // (which is probably the InCallScreen.) - result &= ~ACTION_PASS_TO_USER; - break; - } - if (telephonyService.isOffhook() - && (result & ACTION_PASS_TO_USER) == 0) { - // If we are in call but we decided not to pass the key to - // the application, handle the volume change here. - handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode); - break; - } - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); + TelephonyManager telephonyManager = getTelephonyService(); + if (telephonyManager != null) { + if (telephonyManager.isRinging()) { + // If an incoming call is ringing, either VOLUME key means + // "silence ringer". We handle these keys here, rather than + // in the InCallScreen, to make sure we'll respond to them + // even if the InCallScreen hasn't come to the foreground yet. + // Look for the DOWN event here, to agree with the "fallback" + // behavior in the InCallScreen. + Log.i(TAG, "interceptKeyBeforeQueueing:" + + " VOLUME key-down while ringing: Silence ringer!"); + + // Silence the ringer. (It's safe to call this + // even if the ringer has already been silenced.) + telephonyManager.silenceRinger(); + + // And *don't* pass this key thru to the current activity + // (which is probably the InCallScreen.) + result &= ~ACTION_PASS_TO_USER; + break; + } + if (telephonyManager.isOffhook() + && (result & ACTION_PASS_TO_USER) == 0) { + // If we are in call but we decided not to pass the key to + // the application, handle the volume change here. + handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode); + break; } } @@ -4004,14 +3996,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_ENDCALL: { result &= ~ACTION_PASS_TO_USER; if (down) { - ITelephony telephonyService = getTelephonyService(); + TelephonyManager telephonyManager = getTelephonyService(); boolean hungUp = false; - if (telephonyService != null) { - try { - hungUp = telephonyService.endCall(); - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); - } + if (telephonyManager != null) { + hungUp = telephonyManager.endCall(); } interceptPowerKeyDown(!interactive || hungUp); } else { @@ -4047,23 +4035,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { interceptScreenshotChord(); } - ITelephony telephonyService = getTelephonyService(); + TelephonyManager telephonyManager = getTelephonyService(); boolean hungUp = false; - if (telephonyService != null) { - try { - if (telephonyService.isRinging()) { - // Pressing Power while there's a ringing incoming - // call should silence the ringer. - telephonyService.silenceRinger(); - } else if ((mIncallPowerBehavior - & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 - && telephonyService.isOffhook() && interactive) { - // Otherwise, if "Power button ends call" is enabled, - // the Power button will hang up any current active call. - hungUp = telephonyService.endCall(); - } - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); + if (telephonyManager != null) { + if (telephonyManager.isRinging()) { + // Pressing Power while there's a ringing incoming + // call should silence the ringer. + telephonyManager.silenceRinger(); + } else if ((mIncallPowerBehavior + & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 + && telephonyManager.isOffhook() && interactive) { + // Otherwise, if "Power button ends call" is enabled, + // the Power button will hang up any current active call. + hungUp = telephonyManager.endCall(); } } interceptPowerKeyDown(!interactive || hungUp @@ -4096,16 +4080,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: if (down) { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null) { - try { - if (!telephonyService.isIdle()) { - // Suppress PLAY/PAUSE toggle when phone is ringing or in-call - // to avoid music playback. - break; - } - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); + TelephonyManager telephonyManager = getTelephonyService(); + if (telephonyManager != null) { + if (!telephonyManager.isIdle()) { + // Suppress PLAY/PAUSE toggle when phone is ringing or in-call + // to avoid music playback. + break; } } } @@ -4135,20 +4115,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_CALL: { if (down) { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null) { - try { - if (telephonyService.isRinging()) { - Log.i(TAG, "interceptKeyBeforeQueueing:" - + " CALL key-down while ringing: Answer the call!"); - telephonyService.answerRingingCall(); - - // And *don't* pass this key thru to the current activity - // (which is presumably the InCallScreen.) - result &= ~ACTION_PASS_TO_USER; - } - } catch (RemoteException ex) { - Log.w(TAG, "ITelephony threw RemoteException", ex); + TelephonyManager telephonyManager = getTelephonyService(); + if (telephonyManager != null) { + if (telephonyManager.isRinging()) { + Log.i(TAG, "interceptKeyBeforeQueueing:" + + " CALL key-down while ringing: Answer the call!"); + telephonyManager.answerRingingCall(); + + // And *don't* pass this key thru to the current activity + // (which is presumably the InCallScreen.) + result &= ~ACTION_PASS_TO_USER; } } } @@ -5565,8 +5541,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation); pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock); pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation); + mStatusBarController.dump(pw, prefix); mNavigationBarController.dump(pw, prefix); PolicyControl.dump(prefix, pw); + + if (mOrientationListener != null) { + mOrientationListener.dump(pw, prefix); + } } } diff --git a/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java b/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java index 0c77556..2cc33b5 100644 --- a/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java +++ b/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java @@ -26,6 +26,9 @@ import android.os.SystemProperties; import android.util.FloatMath; import android.util.Log; import android.util.Slog; +import android.util.TimeUtils; + +import java.io.PrintWriter; /** * A special helper class used by the WindowManager @@ -181,6 +184,19 @@ public abstract class WindowOrientationListener { */ public abstract void onProposedRotationChanged(int rotation); + public void dump(PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.println(prefix + TAG); + prefix += " "; + pw.println(prefix + "mEnabled=" + mEnabled); + pw.println(prefix + "mCurrentRotation=" + mCurrentRotation); + pw.println(prefix + "mSensor=" + mSensor); + pw.println(prefix + "mRate=" + mRate); + + mSensorEventListener.dumpLocked(pw, prefix); + } + } + /** * This class filters the raw accelerometer data and tries to detect actual changes in * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, @@ -342,6 +358,14 @@ public abstract class WindowOrientationListener { /* ROTATION_270 */ { -25, 65 } }; + // The tilt angle below which we conclude that the user is holding the device + // overhead reading in bed and lock into that state. + private final int TILT_OVERHEAD_ENTER = -40; + + // The tilt angle above which we conclude that the user would like a rotation + // change to occur and unlock from the overhead state. + private final int TILT_OVERHEAD_EXIT = -15; + // The gap angle in degrees between adjacent orientation angles for hysteresis. // This creates a "dead zone" between the current orientation and a proposed // adjacent orientation. No orientation proposal is made when the orientation @@ -364,12 +388,18 @@ public abstract class WindowOrientationListener { // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed). private long mFlatTimestampNanos; + private boolean mFlat; // Timestamp when the device last appeared to be swinging. private long mSwingTimestampNanos; + private boolean mSwinging; // Timestamp when the device last appeared to be undergoing external acceleration. private long mAccelerationTimestampNanos; + private boolean mAccelerating; + + // Whether we are locked into an overhead usage mode. + private boolean mOverhead; // History of observed tilt angles. private static final int TILT_HISTORY_SIZE = 40; @@ -381,6 +411,19 @@ public abstract class WindowOrientationListener { return mProposedRotation; } + public void dumpLocked(PrintWriter pw, String prefix) { + pw.println(prefix + "mProposedRotation=" + mProposedRotation); + pw.println(prefix + "mPredictedRotation=" + mPredictedRotation); + pw.println(prefix + "mLastFilteredX=" + mLastFilteredX); + pw.println(prefix + "mLastFilteredY=" + mLastFilteredY); + pw.println(prefix + "mLastFilteredZ=" + mLastFilteredZ); + pw.println(prefix + "mTiltHistory={last: " + getLastTiltLocked() + "}"); + pw.println(prefix + "mFlat=" + mFlat); + pw.println(prefix + "mSwinging=" + mSwinging); + pw.println(prefix + "mAccelerating=" + mAccelerating); + pw.println(prefix + "mOverhead=" + mOverhead); + } + @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @@ -478,7 +521,18 @@ public abstract class WindowOrientationListener { // If the tilt angle is too close to horizontal then we cannot determine // the orientation angle of the screen. - if (Math.abs(tiltAngle) > MAX_TILT) { + if (tiltAngle <= TILT_OVERHEAD_ENTER) { + mOverhead = true; + } else if (tiltAngle >= TILT_OVERHEAD_EXIT) { + mOverhead = false; + } + if (mOverhead) { + if (LOG) { + Slog.v(TAG, "Ignoring sensor data, device is overhead: " + + "tiltAngle=" + tiltAngle); + } + clearPredictedRotationLocked(); + } else if (Math.abs(tiltAngle) > MAX_TILT) { if (LOG) { Slog.v(TAG, "Ignoring sensor data, tilt angle too high: " + "tiltAngle=" + tiltAngle); @@ -526,6 +580,9 @@ public abstract class WindowOrientationListener { } } } + mFlat = isFlat; + mSwinging = isSwinging; + mAccelerating = isAccelerating; // Determine new proposed rotation. oldProposedRotation = mProposedRotation; @@ -543,6 +600,7 @@ public abstract class WindowOrientationListener { + ", isAccelerating=" + isAccelerating + ", isFlat=" + isFlat + ", isSwinging=" + isSwinging + + ", isOverhead=" + mOverhead + ", timeUntilSettledMS=" + remainingMS(now, mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now, @@ -660,8 +718,12 @@ public abstract class WindowOrientationListener { mLastFilteredTimestampNanos = Long.MIN_VALUE; mProposedRotation = -1; mFlatTimestampNanos = Long.MIN_VALUE; + mFlat = false; mSwingTimestampNanos = Long.MIN_VALUE; + mSwinging = false; mAccelerationTimestampNanos = Long.MIN_VALUE; + mAccelerating = false; + mOverhead = false; clearPredictedRotationLocked(); clearTiltHistoryLocked(); } @@ -726,6 +788,11 @@ public abstract class WindowOrientationListener { return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1; } + private float getLastTiltLocked() { + int index = nextTiltHistoryIndexLocked(mTiltHistoryIndex); + return index >= 0 ? mTiltHistory[index] : Float.NaN; + } + private float remainingMS(long now, long until) { return now >= until ? 0 : (until - now) * 0.000001f; } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 7ecf248..1e21e1c 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -343,12 +343,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int EVENT_INET_CONDITION_HOLD_END = 5; /** - * used internally to set enable/disable cellular data - * arg1 = ENBALED or DISABLED - */ - private static final int EVENT_SET_MOBILE_DATA = 7; - - /** * used internally to clear a wakelock when transitioning * from one net to another */ @@ -1822,20 +1816,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { return true; } - /** - * @see ConnectivityManager#getMobileDataEnabled() - */ - public boolean getMobileDataEnabled() { - // TODO: This detail should probably be in DataConnectionTracker's - // which is where we store the value and maybe make this - // asynchronous. - enforceAccessPermission(); - boolean retVal = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MOBILE_DATA, 1) == 1; - if (VDBG) log("getMobileDataEnabled returning " + retVal); - return retVal; - } - public void setDataDependency(int networkType, boolean met) { enforceConnectivityInternalPermission(); @@ -1908,22 +1888,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } }; - /** - * @see ConnectivityManager#setMobileDataEnabled(boolean) - */ - public void setMobileDataEnabled(boolean enabled) { - enforceChangePermission(); - if (DBG) log("setMobileDataEnabled(" + enabled + ")"); - - mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA, - (enabled ? ENABLED : DISABLED), 0)); - } - - private void handleSetMobileData(boolean enabled) { - // TODO - handle this - probably generalize passing in a transport type and send to the - // factories? - } - @Override public void setPolicyDataEnable(int networkType, boolean enabled) { // only someone like NPMS should only be calling us @@ -3315,11 +3279,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleInetConditionHoldEnd(netType, sequence); break; } - case EVENT_SET_MOBILE_DATA: { - boolean enabled = (msg.arg1 == ENABLED); - handleSetMobileData(enabled); - break; - } case EVENT_APPLY_GLOBAL_HTTP_PROXY: { handleDeprecatedGlobalHttpProxy(); break; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ac30319..248b44d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6010,6 +6010,9 @@ public final class ActivityManagerService extends ActivityManagerNative IPackageManager pm, ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, "checkHoldingPermissionsLocked: uri=" + grantUri + " uid=" + uid); + if (UserHandle.getUserId(uid) != grantUri.sourceUserId) { + return false; + } if (pi.applicationInfo.uid == uid) { return true; diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 33e59a7..df80f02 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -33,6 +33,7 @@ import static com.android.server.am.ActivityManagerService.VALIDATE_TOKENS; import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP; import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; +import static com.android.server.am.ActivityStackSupervisor.DEBUG_SCREENSHOTS; import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; @@ -342,6 +343,10 @@ final class ActivityStack { mWindowManager = mService.mWindowManager; mStackId = activityContainer.mStackId; mCurrentUser = mService.mCurrentUserId; + // Get the activity screenshot thumbnail dimensions + Resources res = mService.mContext.getResources(); + mThumbnailWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + mThumbnailHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); } /** @@ -725,42 +730,54 @@ final class ActivityStack { } } + /** + * This resets the saved state from the last screenshot, forcing a new screenshot to be taken + * again when requested. + */ + private void invalidateLastScreenshot() { + mLastScreenshotActivity = null; + if (mLastScreenshotBitmap != null) { + mLastScreenshotBitmap.recycle(); + } + mLastScreenshotBitmap = null; + } + public final Bitmap screenshotActivities(ActivityRecord who) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "screenshotActivities: " + who); if (who.noDisplay) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tNo display"); return null; } TaskRecord tr = who.task; - if (mService.getMostRecentTask() != tr && tr.intent != null && - (tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) { - // If this task is being excluded from recents, we don't want to take - // the expense of capturing a thumbnail, since we will never show it. + if (mService.getMostRecentTask() != tr || isHomeStack()) { + // This is an optimization -- since we never show Home or Recents within Recents itself, + // we can just go ahead and skip taking the screenshot if this is the home stack. In + // the case where the most recent task is not the task that was supplied, then the stack + // has changed, so invalidate the last screenshot(). + invalidateLastScreenshot(); + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tIs Home stack? " + isHomeStack()); return null; } - Resources res = mService.mContext.getResources(); int w = mThumbnailWidth; int h = mThumbnailHeight; - if (w < 0) { - mThumbnailWidth = w = - res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_width); - mThumbnailHeight = h = - res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_height); - } - if (w > 0) { if (who != mLastScreenshotActivity || mLastScreenshotBitmap == null || mLastScreenshotActivity.state == ActivityState.RESUMED || mLastScreenshotBitmap.getWidth() != w || mLastScreenshotBitmap.getHeight() != h) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tUpdating screenshot"); mLastScreenshotActivity = who; mLastScreenshotBitmap = mWindowManager.screenshotApplications( who.appToken, Display.DEFAULT_DISPLAY, w, h, SCREENSHOT_FORCE_565); } if (mLastScreenshotBitmap != null) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tReusing last screenshot"); return mLastScreenshotBitmap.copy(mLastScreenshotBitmap.getConfig(), true); } } + Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h); return null; } @@ -1032,6 +1049,12 @@ final class ActivityStack { } else { next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process } + + // If we are resuming the activity that we had last screenshotted, then we know it will be + // updated, so invalidate the last screenshot to ensure we take a fresh one when requested + if (next == mLastScreenshotActivity) { + invalidateLastScreenshot(); + } } /** diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 252c0bb..adc7aff 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -106,6 +106,7 @@ public final class ActivityStackSupervisor implements DisplayListener { static final boolean DEBUG_SAVED_STATE = DEBUG || false; static final boolean DEBUG_STATES = DEBUG || false; static final boolean DEBUG_IDLE = DEBUG || false; + static final boolean DEBUG_SCREENSHOTS = DEBUG || false; public static final int HOME_STACK_ID = 0; diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java index 1bc278d..f8e9b7b 100644 --- a/services/core/java/com/android/server/hdmi/FeatureAction.java +++ b/services/core/java/com/android/server/hdmi/FeatureAction.java @@ -164,8 +164,13 @@ abstract class FeatureAction { mActionTimer.sendTimerMessage(state, delayMillis); } - protected final boolean sendCommand(HdmiCecMessage cmd) { - return mService.sendCecCommand(cmd); + protected final void sendCommand(HdmiCecMessage cmd) { + mService.sendCecCommand(cmd); + } + + protected final void sendCommand(HdmiCecMessage cmd, + HdmiControlService.SendMessageCallback callback) { + mService.sendCecCommand(cmd, callback); } /** diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index f99a01d..662159d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -20,9 +20,6 @@ import android.hardware.hdmi.HdmiCec; import android.hardware.hdmi.HdmiCecDeviceInfo; import android.hardware.hdmi.HdmiCecMessage; import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -176,115 +173,66 @@ final class HdmiCecController { * Otherwise, scan address will start from {@code preferredAddress} * @param callback callback interface to report allocated logical address to caller */ - void allocateLogicalAddress(int deviceType, int preferredAddress, - AllocateLogicalAddressCallback callback) { - Message msg = mIoHandler.obtainMessage(MSG_ALLOCATE_LOGICAL_ADDRESS); - msg.arg1 = deviceType; - msg.arg2 = preferredAddress; - msg.obj = callback; - mIoHandler.sendMessage(msg); - } - - private static byte[] buildBody(int opcode, byte[] params) { - byte[] body = new byte[params.length + 1]; - body[0] = (byte) opcode; - System.arraycopy(params, 0, body, 1, params.length); - return body; + void allocateLogicalAddress(final int deviceType, final int preferredAddress, + final AllocateLogicalAddressCallback callback) { + runOnIoThread(new Runnable() { + @Override + public void run() { + handleAllocateLogicalAddress(deviceType, preferredAddress, callback); + } + }); } - private final class IoHandler extends Handler { - private IoHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_SEND_CEC_COMMAND: - HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj; - byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); - nativeSendCecCommand(mNativePtr, cecMessage.getSource(), - cecMessage.getDestination(), body); - break; - case MSG_ALLOCATE_LOGICAL_ADDRESS: - int deviceType = msg.arg1; - int preferredAddress = msg.arg2; - AllocateLogicalAddressCallback callback = - (AllocateLogicalAddressCallback) msg.obj; - handleAllocateLogicalAddress(deviceType, preferredAddress, callback); - break; - default: - Slog.w(TAG, "Unsupported CEC Io request:" + msg.what); + private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress, + final AllocateLogicalAddressCallback callback) { + int startAddress = preferredAddress; + // If preferred address is "unregistered", start address will be the smallest + // address matched with the given device type. + if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) { + for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { + if (deviceType == HdmiCec.getTypeFromAddress(i)) { + startAddress = i; break; + } } } - private void handleAllocateLogicalAddress(int deviceType, int preferredAddress, - AllocateLogicalAddressCallback callback) { - int startAddress = preferredAddress; - // If preferred address is "unregistered", start_index will be the smallest - // address matched with the given device type. - if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) { - for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { - if (deviceType == HdmiCec.getTypeFromAddress(i)) { - startAddress = i; - break; - } + int logicalAddress = HdmiCec.ADDR_UNREGISTERED; + // Iterates all possible addresses which has the same device type. + for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { + int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS; + if (curAddress != HdmiCec.ADDR_UNREGISTERED + && deviceType == HdmiCec.getTypeFromAddress(i)) { + // <Polling Message> is a message which has empty body and + // uses same address for both source and destination address. + // If sending <Polling Message> failed (NAK), it becomes + // new logical address for the device because no device uses + // it as logical address of the device. + int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress, + EMPTY_BODY); + if (error != ERROR_SUCCESS) { + logicalAddress = curAddress; + break; } } + } - int logcialAddress = HdmiCec.ADDR_UNREGISTERED; - // Iterates all possible addresses which has the same device type. - for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { - int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS; - if (curAddress != HdmiCec.ADDR_UNREGISTERED - && deviceType == HdmiCec.getTypeFromAddress(i)) { - // <Polling Message> is a message which has empty body and - // uses same address for both source and destination address. - // If sending <Polling Message> failed (NAK), it becomes - // new logical address for the device because no device uses - // it as logical address of the device. - int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress, - EMPTY_BODY); - if (error != ERROR_SUCCESS) { - logcialAddress = curAddress; - break; - } + final int assignedAddress = logicalAddress; + if (callback != null) { + runOnServiceThread(new Runnable() { + @Override + public void run() { + callback.onAllocated(deviceType, assignedAddress); } - } - - Message msg = mControlHandler.obtainMessage(MSG_REPORT_LOGICAL_ADDRESS); - msg.arg1 = deviceType; - msg.arg2 = logcialAddress; - msg.obj = callback; - mControlHandler.sendMessage(msg); + }); } } - private final class ControlHandler extends Handler { - private ControlHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_RECEIVE_CEC_COMMAND: - // TODO: delegate it to HdmiControl service. - onReceiveCommand((HdmiCecMessage) msg.obj); - break; - case MSG_REPORT_LOGICAL_ADDRESS: - int deviceType = msg.arg1; - int logicalAddress = msg.arg2; - AllocateLogicalAddressCallback callback = - (AllocateLogicalAddressCallback) msg.obj; - callback.onAllocated(deviceType, logicalAddress); - break; - default: - Slog.i(TAG, "Unsupported message type:" + msg.what); - break; - } - } + private static byte[] buildBody(int opcode, byte[] params) { + byte[] body = new byte[params.length + 1]; + body[0] = (byte) opcode; + System.arraycopy(params, 0, body, 1, params.length); + return body; } /** @@ -411,10 +359,18 @@ final class HdmiCecController { return nativeGetVendorId(mNativePtr); } + private void runOnIoThread(Runnable runnable) { + mIoHandler.post(runnable); + } + + private void runOnServiceThread(Runnable runnable) { + mControlHandler.post(runnable); + } + private void init(HdmiControlService service, long nativePtr) { mService = service; - mIoHandler = new IoHandler(service.getServiceLooper()); - mControlHandler = new ControlHandler(service.getServiceLooper()); + mIoHandler = new Handler(service.getServiceLooper()); + mControlHandler = new Handler(service.getServiceLooper()); mNativePtr = nativePtr; } @@ -438,13 +394,27 @@ final class HdmiCecController { HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand (sourceAddress, message.getSource(), message.getOpcode(), HdmiCecMessageBuilder.ABORT_REFUSED); - sendCommand(cecMessage); - + sendCommand(cecMessage, null); } - boolean sendCommand(HdmiCecMessage cecMessage) { - Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage); - return mIoHandler.sendMessage(message); + void sendCommand(final HdmiCecMessage cecMessage, + final HdmiControlService.SendMessageCallback callback) { + runOnIoThread(new Runnable() { + @Override + public void run() { + byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); + final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(), + cecMessage.getDestination(), body); + if (callback != null) { + runOnServiceThread(new Runnable() { + @Override + public void run() { + callback.onSendCompleted(error); + } + }); + } + } + }); } /** @@ -453,12 +423,15 @@ final class HdmiCecController { private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { byte opcode = body[0]; byte params[] = Arrays.copyOfRange(body, 1, body.length); - HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params); + final HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params); // Delegate message to main handler so that it handles in main thread. - Message message = mControlHandler.obtainMessage( - MSG_RECEIVE_CEC_COMMAND, cecMessage); - mControlHandler.sendMessage(message); + runOnServiceThread(new Runnable() { + @Override + public void run() { + onReceiveCommand(cecMessage); + } + }); } /** diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 09153b9..d7a2c1c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -18,30 +18,25 @@ package com.android.server.hdmi; import android.annotation.Nullable; import android.content.Context; -import android.hardware.hdmi.IHdmiControlCallback; -import android.hardware.hdmi.IHdmiControlService; -import android.hardware.hdmi.IHdmiHotplugEventListener; import android.hardware.hdmi.HdmiCec; import android.hardware.hdmi.HdmiCecDeviceInfo; import android.hardware.hdmi.HdmiCecMessage; +import android.hardware.hdmi.IHdmiControlCallback; +import android.hardware.hdmi.IHdmiControlService; +import android.hardware.hdmi.IHdmiHotplugEventListener; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Slog; -import android.util.SparseArray; - import com.android.internal.annotations.GuardedBy; import com.android.server.SystemService; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; -import java.util.List; import java.util.Locale; /** @@ -54,6 +49,20 @@ public final class HdmiControlService extends SystemService { // TODO: Rename the permission to HDMI_CONTROL. private static final String PERMISSION = "android.permission.HDMI_CEC"; + /** + * Interface to report send result. + */ + interface SendMessageCallback { + /** + * Called when {@link HdmiControlService#sendCecCommand} is completed. + * + * @param error result of send request. 0 if succeed. Otherwise it will be + * negative value + */ + // TODO: define error code as constants and update javadoc. + void onSendCompleted(int error); + } + // A thread to handle synchronous IO of CEC and MHL control service. // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) // and sparse call it shares a thread to handle IO operations. @@ -205,10 +214,14 @@ public final class HdmiControlService extends SystemService { * Transmit a CEC command to CEC bus. * * @param command CEC command to send out - * @return {@code true} if succeeds to send command + * @param callback interface used to the result of send command */ - boolean sendCecCommand(HdmiCecMessage command) { - return mCecController.sendCommand(command); + void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { + mCecController.sendCommand(command, callback); + } + + void sendCecCommand(HdmiCecMessage command) { + mCecController.sendCommand(command, null); } /** diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java index 0a701f9..db9d28d 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiCecMessage; import android.util.Slog; /** @@ -37,17 +38,24 @@ final class RequestArcInitiationAction extends RequestArcAction { @Override boolean start() { - if (sendCommand( - HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress, mAvrAddress))) { - mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; - addTimer(mState, TIMEOUT_MS); - } else { - Slog.w(TAG, "Failed to send <Request ARC Initiation>"); - // If failed to send <Request ARC Initiation>, start "Disabled" ARC transmission - // action. - disableArcTransmission(); - finish(); - } + HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress, + mAvrAddress); + sendCommand(command, new HdmiControlService.SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + // success. + if (error == 0) { + mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; + addTimer(mState, TIMEOUT_MS); + } else { + Slog.w(TAG, "Failed to send <Request ARC Initiation>"); + // If failed to send <Request ARC Initiation>, start "Disabled" + // ARC transmission action. + disableArcTransmission(); + finish(); + } + } + }); return true; } } diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java index db1b992..7669f87 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiCecMessage; import android.util.Slog; /** @@ -37,17 +38,23 @@ final class RequestArcTerminationAction extends RequestArcAction { @Override boolean start() { - if (sendCommand( - HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress))) { - mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; - addTimer(mState, TIMEOUT_MS); - } else { - Slog.w(TAG, "Failed to send <Request ARC Initiation>"); - // If failed to send <Request ARC Termination>, start "Disabled" ARC transmission - // action. - disableArcTransmission(); - finish(); - } + HdmiCecMessage command = + HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress); + sendCommand(command, new HdmiControlService.SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + if (error == 0) { + mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; + addTimer(mState, TIMEOUT_MS); + } else { + Slog.w(TAG, "Failed to send <Request ARC Initiation>"); + // If failed to send <Request ARC Termination>, start "Disabled" ARC + // transmission action. + disableArcTransmission(); + finish(); + } + } + }); return true; } } diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index 6bf149b..94776a2 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -64,26 +64,7 @@ final class SetArcTransmissionStateAction extends FeatureAction { @Override boolean start() { if (mEnabled) { - if (sendCommand( - HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress))) { - // Enable ARC status immediately after sending <Report Arc Initiated>. - // If AVR responds with <Feature Abort>, disable ARC status again. - // This is different from spec that says that turns ARC status to "Enabled" - // if <Report ARC Initiated> is acknowledged and no <Feature Abort> is received. - // But implemented this way to save the time having to wait for <Feature Abort>. - setArcStatus(true); - // If succeeds to send <Report ARC Initiated>, wait general timeout - // to check whether there is no <Feature Abort> for <Report ARC Initiated>. - mState = STATE_WAITING_TIMEOUT; - addTimer(mState, TIMEOUT_MS); - } else { - // If fails to send <Report ARC Initiated>, disable ARC and - // send <Report ARC Terminated> directly. - Slog.w(TAG, "Failed to send <Report ARC Initiated>:[source:" + mSourceAddress - + ", avr Address:" + mAvrAddress + "]"); - setArcStatus(false); - finish(); - } + sendReportArcInitiated(); } else { setArcStatus(false); finish(); @@ -91,6 +72,38 @@ final class SetArcTransmissionStateAction extends FeatureAction { return true; } + private void sendReportArcInitiated() { + HdmiCecMessage command = + HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress); + sendCommand(command, new HdmiControlService.SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + if (error == 0) { + // Enable ARC status immediately after sending <Report Arc Initiated>. + // If AVR responds with <Feature Abort>, disable ARC status again. + // This is different from spec that says that turns ARC status to + // "Enabled" if <Report ARC Initiated> is acknowledged and no + // <Feature Abort> is received. + // But implemented this way to save the time having to wait for + // <Feature Abort>. + setArcStatus(true); + // If succeeds to send <Report ARC Initiated>, wait general timeout + // to check whether there is no <Feature Abort> for <Report ARC Initiated>. + mState = STATE_WAITING_TIMEOUT; + addTimer(mState, TIMEOUT_MS); + } else { + // If fails to send <Report ARC Initiated>, disable ARC and + // send <Report ARC Terminated> directly. + Slog.w(TAG, "Failed to send <Report ARC Initiated>:[source:" + + mSourceAddress + + ", avr Address:" + mAvrAddress + "]"); + setArcStatus(false); + finish(); + } + } + }); + } + private void setArcStatus(boolean enabled) { boolean wasEnabled = mService.setArcStatus(enabled); Slog.i(TAG, "Change arc status [old:" + wasEnabled + " ,new:" + enabled); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f90d7ab..3ed73f7 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -32,6 +32,7 @@ import android.content.pm.PackageParser.PackageLite; import android.content.pm.Signature; import android.os.Build; import android.os.Bundle; +import android.os.FileBridge; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; @@ -114,7 +115,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private boolean mPermissionsConfirmed; private boolean mInvalid; - private ArrayList<WritePipe> mPipes = new ArrayList<>(); + private ArrayList<FileBridge> mBridges = new ArrayList<>(); private IPackageInstallObserver2 mRemoteObserver; @@ -159,14 +160,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Quick sanity check of state, and allocate a pipe for ourselves. We // then do heavy disk allocation outside the lock, but this open pipe // will block any attempted install transitions. - final WritePipe pipe; + final FileBridge bridge; synchronized (mLock) { if (!mMutationsAllowed) { throw new IllegalStateException("Mutations not allowed"); } - pipe = new WritePipe(); - mPipes.add(pipe); + bridge = new FileBridge(); + mBridges.add(bridge); } try { @@ -194,9 +195,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); } - pipe.setTargetFd(targetFd); - pipe.start(); - return pipe.getWriteFd(); + bridge.setTargetFile(targetFd); + bridge.start(); + return new ParcelFileDescriptor(bridge.getClientSocket()); } catch (ErrnoException e) { throw new IllegalStateException("Failed to write", e); @@ -218,8 +219,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Verify that all writers are hands-off if (mMutationsAllowed) { - for (WritePipe pipe : mPipes) { - if (!pipe.isClosed()) { + for (FileBridge bridge : mBridges) { + if (!bridge.isClosed()) { throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED, "Files still open"); } @@ -482,52 +483,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - private static class WritePipe extends Thread { - private final ParcelFileDescriptor[] mPipe; - - private FileDescriptor mTargetFd; - - private volatile boolean mClosed; - - public WritePipe() { - try { - mPipe = ParcelFileDescriptor.createPipe(); - } catch (IOException e) { - throw new IllegalStateException("Failed to create pipe"); - } - } - - public boolean isClosed() { - return mClosed; - } - - public void setTargetFd(FileDescriptor targetFd) { - mTargetFd = targetFd; - } - - public ParcelFileDescriptor getWriteFd() { - return mPipe[1]; - } - - @Override - public void run() { - FileInputStream in = null; - FileOutputStream out = null; - try { - // TODO: look at switching to sendfile(2) to speed up - in = new FileInputStream(mPipe[0].getFileDescriptor()); - out = new FileOutputStream(mTargetFd); - Streams.copy(in, out); - } catch (IOException e) { - Slog.w(TAG, "Failed to stream data: " + e); - } finally { - IoUtils.closeQuietly(mPipe[0]); - IoUtils.closeQuietly(mTargetFd); - mClosed = true; - } - } - } - private class InstallFailedException extends Exception { private final int error; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0fa0b14..b06b090 100755 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6140,7 +6140,12 @@ public class PackageManagerService extends IPackageManager.Stub { } final String nativeLibraryPath = (new File(libDir, apkName)).getPath(); pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath; - pkgSetting.nativeLibraryPathString = nativeLibraryPath; + // pkgSetting might be null during rescan following uninstall of updates + // to a bundled app, so accommodate that possibility. The settings in + // that case will be established later from the parsed package. + if (pkgSetting != null) { + pkgSetting.nativeLibraryPathString = nativeLibraryPath; + } } // Deduces the required ABI of an upgraded system app. diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 986cdc1..1629a61 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -40,6 +40,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; +import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -326,8 +327,12 @@ public class TrustManagerService extends SystemService { for (int i = 0; i < mTrustListeners.size(); i++) { try { mTrustListeners.get(i).onTrustChanged(enabled, userId); + } catch (DeadObjectException e) { + if (DEBUG) Slog.d(TAG, "Removing dead TrustListener."); + mTrustListeners.remove(i); + i--; } catch (RemoteException e) { - Slog.e(TAG, "Exception while notifying TrustListener. Removing listener.", e); + Slog.e(TAG, "Exception while notifying TrustListener.", e); } } } diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl new file mode 100644 index 0000000..c439211 --- /dev/null +++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telecomm; + +/** + * Interface used to interact with Telecomm. Mostly this is used by TelephonyManager for passing + * commands that were previously handled by ITelephony. + * {@hide} + */ +oneway interface ITelecommService { + + /** + * Silence the ringer if an incoming call is currently ringing. + * (If vibrating, stop the vibrator also.) + * + * It's safe to call this if the ringer has already been silenced, or + * even if there's no incoming call. (If so, this method will do nothing.) + */ + void silenceRinger(); +} diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 5d485c5..4aed1fe 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -30,6 +30,7 @@ import android.os.SystemProperties; import android.telephony.Rlog; import android.util.Log; +import com.android.internal.telecomm.ITelecommService; import com.android.internal.telephony.IPhoneSubInfo; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.ITelephonyRegistry; @@ -65,6 +66,8 @@ import java.util.regex.Pattern; public class TelephonyManager { private static final String TAG = "TelephonyManager"; + private static final String TELECOMM_SERVICE_NAME = "telecomm"; + private static ITelephonyRegistry sRegistry; /** @@ -1536,6 +1539,10 @@ public class TelephonyManager { return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); } + private ITelecommService getTelecommService() { + return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME)); + } + // // // PhoneStateListener @@ -2016,9 +2023,9 @@ public class TelephonyManager { @PrivateApi public void silenceRinger() { try { - getITelephony().silenceRinger(); + getTelecommService().silenceRinger(); } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#silenceRinger", e); + Log.e(TAG, "Error calling ITelecommService#silenceRinger", e); } } @@ -2249,4 +2256,25 @@ public class TelephonyManager { } return false; } + + /** @hide */ + @PrivateApi + public void setDataEnabled(boolean enable) { + try { + getITelephony().setDataEnabled(enable); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setDataEnabled", e); + } + } + + /** @hide */ + @PrivateApi + public boolean getDataEnabled() { + try { + return getITelephony().getDataEnabled(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getDataEnabled", e); + } + return false; + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index baacb74..6d7f158 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -436,4 +436,18 @@ interface ITelephony { * @return true on success; false on any failure. */ boolean setPreferredNetworkType(int networkType); + + /** + * User enable/disable Mobile Data. + * + * @param enable true to turn on, else false + */ + void setDataEnabled(boolean enable); + + /** + * Get the user enabled state of Mobile Data. + * + * @return true on enabled + */ + boolean getDataEnabled(); } diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java index 6ad01a0..0f4e122 100644 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java @@ -302,6 +302,36 @@ public abstract class DisplayModifier { paint.setShader(ResourceModifiers.instance().mVertGradient); } }); + put("radGradient", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mRadGradient); + } + }); + put("sweepGradient", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mSweepGradient); + } + }); + put("composeShader", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mComposeShader); + } + }); + put("bad composeShader", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mBadComposeShader); + } + }); + put("bad composeShader 2", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mAnotherBadComposeShader); + } + }); } }); diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java index c705443..d522481 100644 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java @@ -23,7 +23,11 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Color; +import android.graphics.ComposeShader; import android.graphics.LinearGradient; +import android.graphics.PorterDuff; +import android.graphics.RadialGradient; +import android.graphics.SweepGradient; import android.graphics.Matrix; import android.graphics.Shader; @@ -38,6 +42,11 @@ public class ResourceModifiers { public final LinearGradient mHorGradient; public final LinearGradient mDiagGradient; public final LinearGradient mVertGradient; + public final RadialGradient mRadGradient; + public final SweepGradient mSweepGradient; + public final ComposeShader mComposeShader; + public final ComposeShader mBadComposeShader; + public final ComposeShader mAnotherBadComposeShader; public final Bitmap mBitmap; private final Matrix mMtx1; private final Matrix mMtx2; @@ -90,6 +99,12 @@ public class ResourceModifiers { mVertGradient = new LinearGradient(0.0f, 0.0f, 0.0f, mDrawHeight / 2.0f, Color.YELLOW, Color.MAGENTA, Shader.TileMode.MIRROR); + mSweepGradient = new SweepGradient(mDrawWidth / 2.0f, mDrawHeight / 2.0f, + Color.YELLOW, Color.MAGENTA); + + mComposeShader = new ComposeShader(mRepeatShader, mHorGradient, + PorterDuff.Mode.MULTIPLY); + final float width = mBitmap.getWidth() / 8.0f; final float height = mBitmap.getHeight() / 8.0f; @@ -106,6 +121,16 @@ public class ResourceModifiers { 0xff00ff00, 0xff0000ff, 0xffff0000, 0xff00ff00, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00ff0000, }; + + // Use a repeating gradient with many colors to test the non simple case. + mRadGradient = new RadialGradient(mDrawWidth / 4.0f, mDrawHeight / 4.0f, 4.0f, + mBitmapColors, null, Shader.TileMode.REPEAT); + + mBadComposeShader = new ComposeShader(mRadGradient, mComposeShader, + PorterDuff.Mode.MULTIPLY); + + mAnotherBadComposeShader = new ComposeShader(mRadGradient, mVertGradient, + PorterDuff.Mode.MULTIPLY); } } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index bafc71e..1157de7 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -509,8 +509,6 @@ public class WifiConfiguration implements Parcelable { * @hide */ public boolean isValid() { - if (SSID == null) - return false; if (allowedKeyManagement == null) return false; diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java index 1484d49..7debb93 100644 --- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java +++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java @@ -224,9 +224,9 @@ public class WifiEnterpriseConfig implements Parcelable { public static final int TTLS = 2; /** EAP-Password */ public static final int PWD = 3; - /** EAP-Subscriber Identity Module */ + /** EAP-Subscriber Identity Module {@hide} */ public static final int SIM = 4; - /** EAP-Authentication and Key Agreement */ + /** EAP-Authentication and Key Agreement {@hide} */ public static final int AKA = 5; /** @hide */ public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA" }; diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java index 08b430f..9a633b4 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java @@ -25,9 +25,13 @@ import java.util.Set; import java.util.Iterator; import java.util.Map; +/** + * A class representing a Wi-Fi Passpoint credential. + */ public class WifiPasspointCredential implements Parcelable { private final static String TAG = "PasspointCredential"; + private String mWifiTreePath; private String mWifiSPFQDN; private String mCredentialName; private String mUpdateIdentifier; @@ -88,10 +92,11 @@ public class WifiPasspointCredential implements Parcelable { /** * Constructor * @param realm Realm of the passpoint credential - * @param config Credential information, must be either EAP-TLS or EAP-TTLS. + * @param fqdn Fully qualified domain name (FQDN) of the credential + * @param config Credential information, must be either EAP-TLS or EAP-TTLS * @see WifiEnterpriseConfig */ - public WifiPasspointCredential(String realm, WifiEnterpriseConfig config) { + public WifiPasspointCredential(String realm, String fqdn, WifiEnterpriseConfig config) { mRealm = realm; switch (config.getEapMethod()) { case WifiEnterpriseConfig.Eap.TLS: @@ -325,10 +330,7 @@ public class WifiPasspointCredential implements Parcelable { return mPasswd; } - /** - * Get the IMSI of this Passpoint credential, for EAP-SIM / EAP-AKA only. - * @return IMSI - */ + /** @hide */ public String getImsi() { return mImsi; } @@ -343,8 +345,11 @@ public class WifiPasspointCredential implements Parcelable { return mMnc; } - /** @hide */ - public String getCaRootCert() { + /** + * Get the CA root certificate path of this Passpoint credential. + * @return CA root certificate path + */ + public String getCaRootCertPath() { return mCaRootCert; } @@ -357,13 +362,29 @@ public class WifiPasspointCredential implements Parcelable { } /** - * Get the realm of this Passpoint credential, for all EAP methods. + * Set credential information of this Passpoint credential. + * @param config Credential information, must be either EAP-TLS or EAP-TTLS + */ + public void setCredential(WifiEnterpriseConfig config) { + // TODO + } + + /** + * Get the realm of this Passpoint credential. * @return Realm */ public String getRealm() { return mRealm; } + /** + * Set the ream of this Passpoint credential. + * @param realm Realm + */ + public void setRealm(String realm) { + mRealm = realm; + } + /** @hide */ public int getPriority() { if (mUserPreferred) { @@ -374,14 +395,22 @@ public class WifiPasspointCredential implements Parcelable { } /** - * Get the fully qualified domain name (FQDN) of this Passpoint credential, - * for all EAP methods. + * Get the fully qualified domain name (FQDN) of this Passpoint credential. * @return FQDN */ public String getFqdn() { return mHomeSpFqdn; } + /** + * Set the fully qualified domain name (FQDN) of this Passpoint credential. + * @param fqdn FQDN + */ + public void setFqdn(String fqdn) { + mHomeSpFqdn = fqdn; + } + + /** @hide */ public String getOtherhomepartners() { return mOtherhomepartnerFqdn; diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java index ee4dc5a..ebaa8c9 100644 --- a/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java +++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java @@ -443,10 +443,9 @@ public class WifiPasspointManager { return null; } - /* TODO: add credential APIs */ - /** - * Give a list of all saved Passpoint credentials. + * Get a list of saved Passpoint credentials. Only those credentials owned + * by the caller will be returned. * * @return The list of credentials */ |
