diff options
Diffstat (limited to 'core/java/android')
148 files changed, 8423 insertions, 1130 deletions
diff --git a/core/java/android/animation/TypeEvaluator.java b/core/java/android/animation/TypeEvaluator.java index 2640457..429c435 100644 --- a/core/java/android/animation/TypeEvaluator.java +++ b/core/java/android/animation/TypeEvaluator.java @@ -29,7 +29,7 @@ public interface TypeEvaluator<T> { /** * This function returns the result of linearly interpolating the start and end values, with * <code>fraction</code> representing the proportion between the start and end values. The - * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>, + * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>, * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>, * and <code>t</code> is <code>fraction</code>. * diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 04f62e3..3c3df01 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -932,6 +932,66 @@ public abstract class ActionBar { */ public void setHomeActionContentDescription(int resId) { } + /** + * Enable hiding the action bar on content scroll. + * + * <p>If enabled, the action bar will scroll out of sight along with a + * {@link View#setNestedScrollingEnabled(boolean) nested scrolling child} view's content. + * The action bar must be in {@link Window#FEATURE_ACTION_BAR_OVERLAY overlay mode} + * to enable hiding on content scroll.</p> + * + * <p>When partially scrolled off screen the action bar is considered + * {@link #hide() hidden}. A call to {@link #show() show} will cause it to return to full view. + * </p> + * @param hideOnContentScroll true to enable hiding on content scroll. + */ + public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { + if (hideOnContentScroll) { + throw new UnsupportedOperationException("Hide on content scroll is not supported in " + + "this action bar configuration."); + } + } + + /** + * Return whether the action bar is configured to scroll out of sight along with + * a {@link View#setNestedScrollingEnabled(boolean) nested scrolling child}. + * + * @return true if hide-on-content-scroll is enabled + * @see #setHideOnContentScrollEnabled(boolean) + */ + public boolean isHideOnContentScrollEnabled() { + return false; + } + + /** + * Return the current vertical offset of the action bar. + * + * <p>The action bar's current hide offset is the distance that the action bar is currently + * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's + * current measured {@link #getHeight() height} (fully invisible).</p> + * + * @return The action bar's offset toward its fully hidden state in pixels + */ + public int getHideOffset() { + return 0; + } + + /** + * Set the current hide offset of the action bar. + * + * <p>The action bar's current hide offset is the distance that the action bar is currently + * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's + * current measured {@link #getHeight() height} (fully invisible).</p> + * + * @param offset The action bar's offset toward its fully hidden state in pixels. + */ + public void setHideOffset(int offset) { + if (offset != 0) { + throw new UnsupportedOperationException("Setting an explicit action bar hide offset " + + "is not supported in this action bar configuration."); + } + } + /** @hide */ public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) { } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 8981c88..af3a92c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -655,7 +655,8 @@ import java.util.HashMap; public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, - OnCreateContextMenuListener, ComponentCallbacks2 { + OnCreateContextMenuListener, ComponentCallbacks2, + Window.OnWindowDismissedCallback { private static final String TAG = "Activity"; private static final boolean DEBUG_LIFECYCLE = false; @@ -2519,7 +2520,9 @@ public class Activity extends ContextThemeWrapper /** * Called when the main window associated with the activity has been dismissed. + * @hide */ + @Override public void onWindowDismissed() { finish(); } @@ -3493,6 +3496,16 @@ public class Activity extends ContextThemeWrapper } theme.applyStyle(resid, false); } + + // Get the primary color and update the RecentsActivityValues for this activity + TypedArray a = getTheme().obtainStyledAttributes(com.android.internal.R.styleable.Theme); + int colorPrimary = a.getColor(com.android.internal.R.styleable.Theme_colorPrimary, 0); + a.recycle(); + if (colorPrimary != 0) { + ActivityManager.RecentsActivityValues v = new ActivityManager.RecentsActivityValues(); + v.colorPrimary = colorPrimary; + setRecentsActivityValues(v); + } } /** @@ -4779,31 +4792,26 @@ public class Activity extends ContextThemeWrapper } /** - * Set a label and icon to be used in the Recents task display. When {@link - * ActivityManager#getRecentTasks} is called, the activities of each task are - * traversed in order from the topmost activity to the bottommost. As soon as one activity is - * found with either a non-null label or a non-null icon set by this call the traversal is - * ended. For each task those values will be returned in {@link - * ActivityManager.RecentTaskInfo#activityLabel} and {@link - * ActivityManager.RecentTaskInfo#activityIcon}. + * Sets information describing this Activity for presentation inside the Recents System UI. When + * {@link ActivityManager#getRecentTasks} is called, the activities of each task are + * traversed in order from the topmost activity to the bottommost. The traversal continues for + * each property until a suitable value is found. For each task those values will be returned in + * {@link android.app.ActivityManager.RecentsActivityValues}. * * @see ActivityManager#getRecentTasks - * @see ActivityManager.RecentTaskInfo + * @see android.app.ActivityManager.RecentsActivityValues * - * @param activityLabel The label to use in the RecentTaskInfo. - * @param activityIcon The Bitmap to use in the RecentTaskInfo. + * @param values The Recents values that describe this activity. */ - public void setActivityLabelAndIcon(CharSequence activityLabel, Bitmap activityIcon) { - final Bitmap scaledIcon; - if (activityIcon != null) { + public void setRecentsActivityValues(ActivityManager.RecentsActivityValues values) { + ActivityManager.RecentsActivityValues activityValues = + new ActivityManager.RecentsActivityValues(values); + if (values.icon != null) { final int size = ActivityManager.getLauncherLargeIconSizeInner(this); - scaledIcon = Bitmap.createScaledBitmap(activityIcon, size, size, true); - } else { - scaledIcon = null; + activityValues.icon = Bitmap.createScaledBitmap(values.icon, size, size, true); } try { - ActivityManagerNative.getDefault().setActivityLabelAndIcon(mToken, activityLabel, - scaledIcon); + ActivityManagerNative.getDefault().setRecentsActivityValues(mToken, activityValues); } catch (RemoteException e) { } } @@ -5432,6 +5440,7 @@ public class Activity extends ContextThemeWrapper mWindow = PolicyManager.makeNewWindow(this); mWindow.setCallback(this); + mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 018e949..5d809d8 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -475,6 +475,111 @@ public class ActivityManager { } /** + * Information you can set and retrieve about the current activity within Recents. + */ + public static class RecentsActivityValues implements Parcelable { + public CharSequence label; + public Bitmap icon; + public int colorPrimary; + + public RecentsActivityValues(RecentsActivityValues values) { + copyFrom(values); + } + + /** + * Creates the RecentsActivityValues to the specified values. + * + * @param label A label and description of the current state of this activity. + * @param icon An icon that represents the current state of this activity. + * @param color A color to override the theme's primary color. + */ + public RecentsActivityValues(CharSequence label, Bitmap icon, int color) { + this.label = label; + this.icon = icon; + this.colorPrimary = color; + } + + /** + * Creates the RecentsActivityValues to the specified values. + * + * @param label A label and description of the current state of this activity. + * @param icon An icon that represents the current state of this activity. + */ + public RecentsActivityValues(CharSequence label, Bitmap icon) { + this(label, icon, 0); + } + + /** + * Creates the RecentsActivityValues to the specified values. + * + * @param label A label and description of the current state of this activity. + */ + public RecentsActivityValues(CharSequence label) { + this(label, null, 0); + } + + public RecentsActivityValues() { + this(null, null, 0); + } + + private RecentsActivityValues(Parcel source) { + readFromParcel(source); + } + + /** + * Do a shallow copy of another set of activity values. + * @hide + */ + public void copyFrom(RecentsActivityValues v) { + if (v != null) { + label = v.label; + icon = v.icon; + colorPrimary = v.colorPrimary; + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + TextUtils.writeToParcel(label, dest, + Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + if (icon == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + icon.writeToParcel(dest, 0); + } + dest.writeInt(colorPrimary); + } + + public void readFromParcel(Parcel source) { + label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + icon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null; + colorPrimary = source.readInt(); + } + + public static final Creator<RecentsActivityValues> CREATOR + = new Creator<RecentsActivityValues>() { + public RecentsActivityValues createFromParcel(Parcel source) { + return new RecentsActivityValues(source); + } + public RecentsActivityValues[] newArray(int size) { + return new RecentsActivityValues[size]; + } + }; + + @Override + public String toString() { + return "RecentsActivityValues Label: " + label + " Icon: " + icon + + " colorPrimary: " + colorPrimary; + } + } + + /** * Information you can retrieve about tasks that the user has most recently * started or visited. */ @@ -523,16 +628,10 @@ public class ActivityManager { public int userId; /** - * The label of the highest activity in the task stack to have set a label using - * {@link Activity#setActivityLabelAndIcon(CharSequence, android.graphics.Bitmap)}. - */ - public CharSequence activityLabel; - - /** - * The Bitmap icon of the highest activity in the task stack to set a Bitmap using - * {@link Activity#setActivityLabelAndIcon(CharSequence, android.graphics.Bitmap)}. + * The recent activity values for the highest activity in the stack to have set the values. + * {@link Activity#setRecentsActivityValues(android.app.ActivityManager.RecentsActivityValues)}. */ - public Bitmap activityIcon; + public RecentsActivityValues activityValues; public RecentTaskInfo() { } @@ -555,13 +654,11 @@ public class ActivityManager { ComponentName.writeToParcel(origActivity, dest); TextUtils.writeToParcel(description, dest, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); - TextUtils.writeToParcel(activityLabel, dest, - Parcelable.PARCELABLE_WRITE_RETURN_VALUE); - if (activityIcon == null) { - dest.writeInt(0); - } else { + if (activityValues != null) { dest.writeInt(1); - activityIcon.writeToParcel(dest, 0); + activityValues.writeToParcel(dest, 0); + } else { + dest.writeInt(0); } dest.writeInt(stackId); dest.writeInt(userId); @@ -573,8 +670,8 @@ public class ActivityManager { baseIntent = source.readInt() > 0 ? Intent.CREATOR.createFromParcel(source) : null; origActivity = ComponentName.readFromParcel(source); description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - activityLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - activityIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null; + activityValues = source.readInt() > 0 ? + RecentsActivityValues.CREATOR.createFromParcel(source) : null; stackId = source.readInt(); userId = source.readInt(); } @@ -791,42 +888,6 @@ public class ActivityManager { * activity -- the task may have been frozen by the system, so that it * can be restarted in its previous state when next brought to the * foreground. - * - * @param maxNum The maximum number of entries to return in the list. The - * actual number returned may be smaller, depending on how many tasks the - * user has started. - * - * @param flags Optional flags - * @param receiver Optional receiver for delayed thumbnails - * - * @return Returns a list of RunningTaskInfo records describing each of - * the running tasks. - * - * Some thumbnails may not be available at the time of this call. The optional - * receiver may be used to receive those thumbnails. - * - * @throws SecurityException Throws SecurityException if the caller does - * not hold the {@link android.Manifest.permission#GET_TASKS} permission. - * - * @hide - */ - public List<RunningTaskInfo> getRunningTasks(int maxNum, int flags, IThumbnailReceiver receiver) - throws SecurityException { - try { - return ActivityManagerNative.getDefault().getTasks(maxNum, flags, receiver); - } catch (RemoteException e) { - // System dead, we will be dead too soon! - return null; - } - } - - /** - * Return a list of the tasks that are currently running, with - * the most recent being first and older ones after in order. Note that - * "running" does not mean any of the task's code is currently loaded or - * activity -- the task may have been frozen by the system, so that it - * can be restarted in its previous state when next brought to the - * foreground. * * <p><b>Note: this method is only intended for debugging and presenting * task management user interfaces</b>. This should never be used for @@ -849,7 +910,12 @@ public class ActivityManager { */ public List<RunningTaskInfo> getRunningTasks(int maxNum) throws SecurityException { - return getRunningTasks(maxNum, 0, null); + try { + return ActivityManagerNative.getDefault().getTasks(maxNum, 0); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } } /** @@ -1627,13 +1693,6 @@ public class ActivityManager { public int lastTrimLevel; /** - * Constant for {@link #importance}: this is a persistent process. - * Only used when reporting to process observers. - * @hide - */ - public static final int IMPORTANCE_PERSISTENT = 50; - - /** * Constant for {@link #importance}: this process is running the * foreground UI. */ @@ -1748,9 +1807,16 @@ public class ActivityManager { */ public int importanceReasonImportance; + /** + * Current process state, as per PROCESS_STATE_* constants. + * @hide + */ + public int processState; + public RunningAppProcessInfo() { importance = IMPORTANCE_FOREGROUND; importanceReasonCode = REASON_UNKNOWN; + processState = PROCESS_STATE_IMPORTANT_FOREGROUND; } public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) { @@ -1776,6 +1842,7 @@ public class ActivityManager { dest.writeInt(importanceReasonPid); ComponentName.writeToParcel(importanceReasonComponent, dest); dest.writeInt(importanceReasonImportance); + dest.writeInt(processState); } public void readFromParcel(Parcel source) { @@ -1791,6 +1858,7 @@ public class ActivityManager { importanceReasonPid = source.readInt(); importanceReasonComponent = ComponentName.readFromParcel(source); importanceReasonImportance = source.readInt(); + processState = source.readInt(); } public static final Creator<RunningAppProcessInfo> CREATOR = diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index b1c37de..57da21e 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -509,11 +509,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); int maxNum = data.readInt(); int fl = data.readInt(); - IBinder receiverBinder = data.readStrongBinder(); - IThumbnailReceiver receiver = receiverBinder != null - ? IThumbnailReceiver.Stub.asInterface(receiverBinder) - : null; - List<ActivityManager.RunningTaskInfo> list = getTasks(maxNum, fl, receiver); + List<ActivityManager.RunningTaskInfo> list = getTasks(maxNum, fl); reply.writeNoException(); int N = list != null ? list.size() : -1; reply.writeInt(N); @@ -712,17 +708,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case REPORT_THUMBNAIL_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); - IBinder token = data.readStrongBinder(); - Bitmap thumbnail = data.readInt() != 0 - ? Bitmap.CREATOR.createFromParcel(data) : null; - CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data); - reportThumbnail(token, thumbnail, description); - reply.writeNoException(); - return true; - } - case GET_CONTENT_PROVIDER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); @@ -2134,13 +2119,12 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case SET_ACTIVITY_LABEL_ICON_TRANSACTION: { + case SET_RECENTS_ACTIVITY_VALUES_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); - CharSequence activityLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data); - Bitmap activityIcon = data.readInt() > 0 - ? Bitmap.CREATOR.createFromParcel(data) : null; - setActivityLabelAndIcon(token, activityLabel, activityIcon); + ActivityManager.RecentsActivityValues values = + ActivityManager.RecentsActivityValues.CREATOR.createFromParcel(data); + setRecentsActivityValues(token, values); reply.writeNoException(); return true; } @@ -2678,14 +2662,12 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } - public List getTasks(int maxNum, int flags, - IThumbnailReceiver receiver) throws RemoteException { + public List getTasks(int maxNum, int flags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(maxNum); data.writeInt(flags); - data.writeStrongBinder(receiver != null ? receiver.asBinder() : null); mRemote.transact(GET_TASKS_TRANSACTION, data, reply, 0); reply.readException(); ArrayList list = null; @@ -2964,25 +2946,6 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } - public void reportThumbnail(IBinder token, - Bitmap thumbnail, CharSequence description) throws RemoteException - { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - data.writeStrongBinder(token); - if (thumbnail != null) { - data.writeInt(1); - thumbnail.writeToParcel(data, 0); - } else { - data.writeInt(0); - } - TextUtils.writeToParcel(description, data, 0); - mRemote.transact(REPORT_THUMBNAIL_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); - reply.readException(); - data.recycle(); - reply.recycle(); - } public ContentProviderHolder getContentProvider(IApplicationThread caller, String name, int userId, boolean stable) throws RemoteException { Parcel data = Parcel.obtain(); @@ -4919,21 +4882,14 @@ class ActivityManagerProxy implements IActivityManager } @Override - public void setActivityLabelAndIcon(IBinder token, CharSequence activityLabel, - Bitmap activityIcon) throws RemoteException - { + public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues values) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); - TextUtils.writeToParcel(activityLabel, data, 0); - if (activityIcon != null) { - data.writeInt(1); - activityIcon.writeToParcel(data, 0); - } else { - data.writeInt(0); - } - mRemote.transact(SET_ACTIVITY_LABEL_ICON_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + values.writeToParcel(data, 0); + mRemote.transact(SET_RECENTS_ACTIVITY_VALUES_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); reply.readException(); data.recycle(); reply.recycle(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 85464c47..a49359f 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -649,13 +649,31 @@ public class ActivityOptions { /** * Called when the start state for shared elements is captured on enter. + * + * @param sharedElementNames The names of the shared elements that were accepted into + * the View hierarchy. + * @param sharedElements The shared elements that are part of the View hierarchy. + * @param sharedElementSnapshots The Views containing snap shots of the shared element + * from the launching Window. These elements will not + * be part of the scene, but will be positioned relative + * to the Window decor View. */ - public void onCaptureSharedElementStart() {} + public void onCaptureSharedElementStart(List<String> sharedElementNames, + List<View> sharedElements, List<View> sharedElementSnapshots) {} /** * Called when the end state for shared elements is captured on enter. + * + * @param sharedElementNames The names of the shared elements that were accepted into + * the View hierarchy. + * @param sharedElements The shared elements that are part of the View hierarchy. + * @param sharedElementSnapshots The Views containing snap shots of the shared element + * from the launching Window. These elements will not + * be part of the scene, but will be positioned relative + * to the Window decor View. */ - public void onCaptureSharedElementEnd() {} + public void onCaptureSharedElementEnd(List<String> sharedElementNames, + List<View> sharedElements, List<View> sharedElementSnapshots) {} /** * Called when the enter Transition has been started. @@ -700,6 +718,22 @@ public class ActivityOptions { * call. */ public Pair<View, String>[] getSharedElementsMapping() { return null; } + + /** + * Returns <code>true</code> if the ActivityTransitionListener will handle removing + * rejected shared elements from the scene. If <code>false</code> is returned, a default + * animation will be used to remove the rejected shared elements from the scene. + * + * @param rejectedSharedElements Views containing visual information of shared elements + * that are not part of the entering scene. These Views + * are positioned relative to the Window decor View. + * @return <code>false</code> if the default animation should be used to remove the + * rejected shared elements from the scene or <code>true</code> if the listener provides + * custom handling. + */ + public boolean handleRejectedSharedElements(List<View> rejectedSharedElements) { + return false; + } } private static class SharedElementMappingListener extends ActivityTransitionListener { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7dc21b4..3b2ff7f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -821,10 +821,6 @@ public final class ActivityThread { sendMessage(H.SUICIDE, null); } - public void requestThumbnail(IBinder token) { - sendMessage(H.REQUEST_THUMBNAIL, token); - } - public void scheduleConfigurationChanged(Configuration config) { updatePendingConfiguration(config); sendMessage(H.CONFIGURATION_CHANGED, config); @@ -1168,7 +1164,7 @@ public final class ActivityThread { public static final int CREATE_SERVICE = 114; public static final int SERVICE_ARGS = 115; public static final int STOP_SERVICE = 116; - public static final int REQUEST_THUMBNAIL = 117; + public static final int CONFIGURATION_CHANGED = 118; public static final int CLEAN_UP_CONTEXT = 119; public static final int GC_WHEN_IDLE = 120; @@ -1218,7 +1214,6 @@ public final class ActivityThread { case CREATE_SERVICE: return "CREATE_SERVICE"; case SERVICE_ARGS: return "SERVICE_ARGS"; case STOP_SERVICE: return "STOP_SERVICE"; - case REQUEST_THUMBNAIL: return "REQUEST_THUMBNAIL"; case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED"; case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT"; case GC_WHEN_IDLE: return "GC_WHEN_IDLE"; @@ -1367,11 +1362,6 @@ public final class ActivityThread { maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; - case REQUEST_THUMBNAIL: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestThumbnail"); - handleRequestThumbnail((IBinder)msg.obj); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; case CONFIGURATION_CHANGED: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi; @@ -3817,28 +3807,6 @@ public final class ActivityThread { handleLaunchActivity(r, currentIntent); } - private void handleRequestThumbnail(IBinder token) { - ActivityClientRecord r = mActivities.get(token); - Bitmap thumbnail = createThumbnailBitmap(r); - CharSequence description = null; - try { - description = r.activity.onCreateDescription(); - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to create description of activity " - + r.intent.getComponent().toShortString() - + ": " + e.toString(), e); - } - } - //System.out.println("Reporting top thumbnail " + thumbnail); - try { - ActivityManagerNative.getDefault().reportThumbnail( - token, thumbnail, description); - } catch (RemoteException ex) { - } - } - ArrayList<ComponentCallbacks2> collectComponentCallbacks( boolean allActivities, Configuration newConfig) { ArrayList<ComponentCallbacks2> callbacks diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index d8a356f..3eb2fea 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -15,6 +15,13 @@ */ package android.app; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; @@ -24,10 +31,13 @@ import android.transition.TransitionManager; import android.transition.TransitionSet; import android.util.ArrayMap; import android.util.Pair; +import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; import android.view.Window; +import android.widget.ImageView; import java.util.ArrayList; import java.util.Collection; @@ -129,6 +139,11 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { private static final String KEY_WIDTH = "shared_element:width"; private static final String KEY_HEIGHT = "shared_element:height"; private static final String KEY_NAME = "shared_element:name"; + private static final String KEY_BITMAP = "shared_element:bitmap"; + private static final String KEY_SCALE_TYPE = "shared_element:scaleType"; + private static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; + + private static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values(); /** * Sent by the exiting coordinator (either EnterTransitionCoordinator @@ -197,6 +212,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { private ResultReceiver mRemoteResultReceiver; private boolean mNotifiedSharedElementTransitionComplete; private boolean mNotifiedExitTransitionComplete; + private boolean mSharedElementTransitionStarted; private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); @@ -241,7 +257,11 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { onPrepareRestore(); break; case MSG_EXIT_TRANSITION_COMPLETE: - onRemoteSceneExitComplete(); + if (!mSharedElementTransitionStarted) { + send(resultCode, resultData); + } else { + onRemoteSceneExitComplete(); + } break; case MSG_TAKE_SHARED_ELEMENTS: ArrayList<String> sharedElementNames @@ -305,16 +325,25 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { setSharedElements(); reconcileSharedElements(sharedElementNames); mEnteringViews.removeAll(mSharedElements); - setSharedElementState(state); + final ArrayList<View> accepted = new ArrayList<View>(); + final ArrayList<View> rejected = new ArrayList<View>(); + createSharedElementImages(accepted, rejected, sharedElementNames, state); + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState = + setSharedElementState(state, accepted); + handleRejected(rejected); + if (getViewsTransition() != null) { setViewVisibility(mEnteringViews, View.INVISIBLE); } setViewVisibility(mSharedElements, View.VISIBLE); Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(), true); + setOriginalImageViewState(originalImageViewState); + if (allowOverlappingTransitions()) { onStartEnterTransition(transition, mEnteringViews); } + mRemoteResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); } @@ -440,9 +469,13 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { mTargetSharedNames.clear(); if (sharedElements == null) { ArrayMap<String, View> map = new ArrayMap<String, View>(); - setViewVisibility(mEnteringViews, View.VISIBLE); + if (getViewsTransition() != null) { + setViewVisibility(mEnteringViews, View.VISIBLE); + } getDecor().findSharedElements(map); - setViewVisibility(mEnteringViews, View.INVISIBLE); + if (getViewsTransition() != null) { + setViewVisibility(mEnteringViews, View.INVISIBLE); + } for (int i = 0; i < map.size(); i++) { View view = map.valueAt(i); String name = map.keyAt(i); @@ -513,39 +546,94 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { } private void reconcileSharedElements(ArrayList<String> sharedElementNames) { - Rect epicenter = null; - for (int i = mTargetSharedNames.size() - 1; i >= 0; i--) { - if (!sharedElementNames.contains(mTargetSharedNames.get(i))) { - mTargetSharedNames.remove(i); - mSharedElements.remove(i); + // keep only those that are in sharedElementNames. + int numSharedElements = sharedElementNames.size(); + int targetIndex = 0; + for (int i = 0; i < numSharedElements; i++) { + String name = sharedElementNames.get(i); + int index = mTargetSharedNames.indexOf(name); + if (index >= 0) { + // Swap the items at the indexes if necessary. + if (index != targetIndex) { + View temp = mSharedElements.get(index); + mSharedElements.set(index, mSharedElements.get(targetIndex)); + mSharedElements.set(targetIndex, temp); + mTargetSharedNames.set(index, mTargetSharedNames.get(targetIndex)); + mTargetSharedNames.set(targetIndex, name); + } + targetIndex++; } } - if (!mSharedElements.isEmpty()) { + for (int i = mSharedElements.size() - 1; i >= targetIndex; i--) { + mSharedElements.remove(i); + mTargetSharedNames.remove(i); + } + Rect epicenter = null; + if (!mTargetSharedNames.isEmpty() + && mTargetSharedNames.get(0).equals(sharedElementNames.get(0))) { epicenter = calcEpicenter(mSharedElements.get(0)); } mEpicenterCallback.setEpicenter(epicenter); } - private void setSharedElementState(Bundle sharedElementState) { + private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState( + Bundle sharedElementState, final ArrayList<View> acceptedOverlayViews) { + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState = + new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>(); + final int[] tempLoc = new int[2]; if (sharedElementState != null) { - int[] tempLoc = new int[2]; for (int i = 0; i < mSharedElements.size(); i++) { View sharedElement = mSharedElements.get(i); String name = mTargetSharedNames.get(i); + Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement, + name, sharedElementState); + if (originalState != null) { + originalImageState.put((ImageView) sharedElement, originalState); + } + View parent = (View) sharedElement.getParent(); + parent.getLocationOnScreen(tempLoc); setSharedElementState(sharedElement, name, sharedElementState, tempLoc); + sharedElement.requestLayout(); } } - mListener.onCaptureSharedElementStart(); + mListener.onCaptureSharedElementStart(mTargetSharedNames, mSharedElements, + acceptedOverlayViews); + getDecor().getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - mListener.onCaptureSharedElementEnd(); + mListener.onCaptureSharedElementEnd(mTargetSharedNames, mSharedElements, + acceptedOverlayViews); + mSharedElementTransitionStarted = true; return true; } } ); + return originalImageState; + } + + private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name, + Bundle transitionArgs) { + if (!(view instanceof ImageView)) { + return null; + } + Bundle bundle = transitionArgs.getBundle(name); + int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); + if (scaleTypeInt < 0) { + return null; + } + + ImageView imageView = (ImageView) view; + ImageView.ScaleType originalScaleType = imageView.getScaleType(); + + Matrix originalMatrix = null; + if (originalScaleType == ImageView.ScaleType.MATRIX) { + originalMatrix = new Matrix(imageView.getImageMatrix()); + } + + return Pair.create(originalScaleType, originalMatrix); } /** @@ -555,15 +643,30 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { * @param name The shared element name given from the source Activity. * @param transitionArgs A <code>Bundle</code> containing all placementinformation for named * shared elements in the scene. - * @param tempLoc A temporary int[2] for capturing the current location of views. + * @param parentLoc The x and y coordinates of the parent's screen position. */ private static void setSharedElementState(View view, String name, Bundle transitionArgs, - int[] tempLoc) { + int[] parentLoc) { Bundle sharedElementBundle = transitionArgs.getBundle(name); if (sharedElementBundle == null) { return; } + if (view instanceof ImageView) { + int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); + if (scaleTypeInt >= 0) { + ImageView imageView = (ImageView) view; + ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; + imageView.setScaleType(scaleType); + if (scaleType == ImageView.ScaleType.MATRIX) { + float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); + Matrix matrix = new Matrix(); + matrix.setValues(matrixValues); + imageView.setImageMatrix(matrix); + } + } + } + float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); view.setTranslationZ(z); @@ -576,15 +679,11 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); view.measure(widthSpec, heightSpec); - ViewGroup parent = (ViewGroup) view.getParent(); - parent.getLocationOnScreen(tempLoc); - int left = x - tempLoc[0]; - int top = y - tempLoc[1]; + int left = x - parentLoc[0]; + int top = y - parentLoc[1]; int right = left + width; int bottom = top + height; view.layout(left, top, right, bottom); - - view.requestLayout(); } /** @@ -615,6 +714,22 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { sharedElementBundle.putString(KEY_NAME, view.getSharedElementName()); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); + + if (view instanceof ImageView) { + ImageView imageView = (ImageView) view; + int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); + sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); + if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { + float[] matrix = new float[9]; + imageView.getImageMatrix().getValues(matrix); + sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); + } + } + transitionArgs.putBundle(name, sharedElementBundle); } @@ -723,6 +838,80 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return transition; } + private void handleRejected(final ArrayList<View> rejected) { + int numRejected = rejected.size(); + if (numRejected == 0) { + return; + } + boolean rejectionHandled = mListener.handleRejectedSharedElements(rejected); + if (rejectionHandled) { + return; + } + + ViewGroupOverlay overlay = getDecor().getOverlay(); + ObjectAnimator animator = null; + for (int i = 0; i < numRejected; i++) { + View view = rejected.get(i); + overlay.add(view); + animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0); + animator.start(); + } + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ViewGroupOverlay overlay = getDecor().getOverlay(); + for (int i = rejected.size() - 1; i >= 0; i--) { + overlay.remove(rejected.get(i)); + } + } + }); + } + + private void createSharedElementImages(ArrayList<View> accepted, ArrayList<View> rejected, + ArrayList<String> sharedElementNames, Bundle state) { + int numSharedElements = sharedElementNames.size(); + Context context = getWindow().getContext(); + int[] parentLoc = new int[2]; + getDecor().getLocationOnScreen(parentLoc); + for (int i = 0; i < numSharedElements; i++) { + String name = sharedElementNames.get(i); + Bundle sharedElementBundle = state.getBundle(name); + if (sharedElementBundle != null) { + Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); + ImageView imageView = new ImageView(context); + imageView.setId(com.android.internal.R.id.shared_element); + imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setImageBitmap(bitmap); + imageView.setSharedElementName(name); + setSharedElementState(imageView, name, state, parentLoc); + if (mTargetSharedNames.contains(name)) { + accepted.add(imageView); + } else { + rejected.add(imageView); + } + } + } + } + + private static void setOriginalImageViewState( + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) { + for (int i = 0; i < originalState.size(); i++) { + ImageView imageView = originalState.keyAt(i); + Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i); + imageView.setScaleType(state.first); + imageView.setImageMatrix(state.second); + } + } + + private static int scaleTypeToInt(ImageView.ScaleType scaleType) { + for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { + if (scaleType == SCALE_TYPE_VALUES[i]) { + return i; + } + } + return -1; + } + private static class FixedEpicenterCallback extends Transition.EpicenterCallback { private Rect mEpicenter; diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index a810134..097c64e 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -33,6 +33,7 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.TextureView; import android.view.TextureView.SurfaceTextureListener; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import dalvik.system.CloseGuard; @@ -51,6 +52,7 @@ public class ActivityView extends ViewGroup { private int mWidth; private int mHeight; private Surface mSurface; + private int mLastVisibility; // Only one IIntentSender or Intent may be queued at a time. Most recent one wins. IIntentSender mQueuedPendingIntent; @@ -95,6 +97,8 @@ public class ActivityView extends ViewGroup { mMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(mMetrics); + mLastVisibility = getVisibility(); + if (DEBUG) Log.v(TAG, "ctor()"); } @@ -103,6 +107,26 @@ public class ActivityView extends ViewGroup { mTextureView.layout(0, 0, r - l, b - t); } + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + + if (mSurface != null) { + try { + if (visibility == View.GONE) { + mActivityContainer.setSurface(null, mWidth, mHeight, mMetrics.densityDpi); + } else if (mLastVisibility == View.GONE) { + // Don't change surface when going between View.VISIBLE and View.INVISIBLE. + mActivityContainer.setSurface(mSurface, mWidth, mHeight, mMetrics.densityDpi); + } + } catch (RemoteException e) { + throw new RuntimeException( + "ActivityView: Unable to set surface of ActivityContainer. " + e); + } + } + mLastVisibility = visibility; + } + private boolean injectInputEvent(InputEvent event) { return mActivityContainer != null && mActivityContainer.injectEvent(event); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ab62427..efd3d86 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1440,6 +1440,31 @@ final class ApplicationPackageManager extends PackageManager { return null; } + /** + * @hide + */ + @Override + public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig, + int userIdDest) { + try { + mPM.addForwardingIntentFilter(filter, removable, userIdOrig, userIdDest); + } catch (RemoteException e) { + // Should never happen! + } + } + + /** + * @hide + */ + @Override + public void clearForwardingIntentFilters(int userIdOrig) { + try { + mPM.clearForwardingIntentFilters(userIdOrig); + } catch (RemoteException e) { + // Should never happen! + } + } + private final ContextImpl mContext; private final IPackageManager mPM; diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index fcc7f8e..7f2fb59 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -312,14 +312,6 @@ public abstract class ApplicationThreadNative extends Binder return true; } - case REQUEST_THUMBNAIL_TRANSACTION: - { - data.enforceInterface(IApplicationThread.descriptor); - IBinder b = data.readStrongBinder(); - requestThumbnail(b); - return true; - } - case SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -986,16 +978,6 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public final void requestThumbnail(IBinder token) - throws RemoteException { - Parcel data = Parcel.obtain(); - data.writeInterfaceToken(IApplicationThread.descriptor); - data.writeStrongBinder(token); - mRemote.transact(REQUEST_THUMBNAIL_TRANSACTION, data, null, - IBinder.FLAG_ONEWAY); - data.recycle(); - } - public final void scheduleConfigurationChanged(Configuration config) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index fe532bf..c621696 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -106,6 +106,9 @@ import android.os.storage.IMountService; import android.os.storage.StorageManager; import android.print.IPrintManager; import android.print.PrintManager; +import android.service.fingerprint.FingerprintManager; +import android.service.fingerprint.FingerprintManagerReceiver; +import android.service.fingerprint.FingerprintService; import android.telephony.TelephonyManager; import android.tv.ITvInputManager; import android.tv.TvInputManager; @@ -451,6 +454,11 @@ class ContextImpl extends Context { return new KeyguardManager(); }}); + registerService(FINGERPRINT_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return new FingerprintManager(ctx); + }}); + registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext()); diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 07583fd..12d4513 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -79,7 +79,7 @@ import java.lang.ref.WeakReference; * </div> */ public class Dialog implements DialogInterface, Window.Callback, - KeyEvent.Callback, OnCreateContextMenuListener { + KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { private static final String TAG = "Dialog"; private Activity mOwnerActivity; @@ -165,6 +165,7 @@ public class Dialog implements DialogInterface, Window.Callback, Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; w.setCallback(this); + w.setOnWindowDismissedCallback(this); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); @@ -708,6 +709,8 @@ public class Dialog implements DialogInterface, Window.Callback, public void onDetachedFromWindow() { } + /** @hide */ + @Override public void onWindowDismissed() { dismiss(); } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index aa097e0..cbb8359 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -23,8 +23,6 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.ResultReceiver; import android.transition.Transition; -import android.util.ArrayMap; -import android.util.Pair; import android.view.View; import android.view.ViewTreeObserver; import android.view.Window; @@ -135,11 +133,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator } @Override - protected void onRemoteSceneExitComplete() { - super.onRemoteSceneExitComplete(); - } - - @Override protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) { mEnteringSharedElementNames = new ArrayList<String>(); mEnteringSharedElementNames.addAll(sharedElementNames); @@ -149,6 +142,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator @Override protected void sharedElementTransitionComplete(Bundle bundle) { notifySharedElementTransitionComplete(bundle); + exitAfterSharedElementTransition(); } @Override @@ -223,6 +217,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator @Override protected void startExitTransition(ArrayList<String> sharedElements) { + mMakeOpaque = false; notifyPrepareRestore(); if (getDecor().getBackground() == null) { @@ -264,7 +259,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator mExitTransitionComplete = true; exitAfterSharedElementTransition(); super.onExitTransitionEnd(); - clearConnections(); } @Override @@ -281,12 +275,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator } private void exitAfterSharedElementTransition() { - if (mSharedElementTransitionComplete && mExitTransitionComplete) { + if (mSharedElementTransitionComplete && mExitTransitionComplete && mBackgroundFadedOut) { mActivity.finish(); if (mSupportsTransition) { mActivity.overridePendingTransition(0, 0); } notifyExitTransitionComplete(); + clearConnections(); } } } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 6b94c4e..2e9cdf3b7 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -111,8 +111,7 @@ public interface IActivityManager extends IInterface { public void activityDestroyed(IBinder token) throws RemoteException; public String getCallingPackage(IBinder token) throws RemoteException; public ComponentName getCallingActivity(IBinder token) throws RemoteException; - public List<RunningTaskInfo> getTasks(int maxNum, int flags, - IThumbnailReceiver receiver) throws RemoteException; + public List<RunningTaskInfo> getTasks(int maxNum, int flags) throws RemoteException; public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) throws RemoteException; public ActivityManager.TaskThumbnails getTaskThumbnails(int taskId) throws RemoteException; @@ -131,9 +130,6 @@ public interface IActivityManager extends IInterface { public boolean isInHomeStack(int taskId) throws RemoteException; public void setFocusedStack(int stackId) throws RemoteException; public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; - /* oneway */ - public void reportThumbnail(IBinder token, - Bitmap thumbnail, CharSequence description) throws RemoteException; public ContentProviderHolder getContentProvider(IApplicationThread caller, String name, int userId, boolean stable) throws RemoteException; public ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token) @@ -439,8 +435,8 @@ public interface IActivityManager extends IInterface { public boolean isInLockTaskMode() throws RemoteException; /** @hide */ - public void setActivityLabelAndIcon(IBinder token, CharSequence activityLabel, - Bitmap activityBitmap) throws RemoteException; + public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues values) + throws RemoteException; /* * Private non-Binder interfaces @@ -571,7 +567,7 @@ public interface IActivityManager extends IInterface { int MOVE_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24; int MOVE_TASK_BACKWARDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25; int GET_TASK_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26; - int REPORT_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27; + int GET_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; int PUBLISH_CONTENT_PROVIDERS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; int REF_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; @@ -738,6 +734,6 @@ public interface IActivityManager extends IInterface { int START_LOCK_TASK_BY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+214; int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215; int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216; - int SET_ACTIVITY_LABEL_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217; + int SET_RECENTS_ACTIVITY_VALUES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217; int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index f290e94..fefba8a 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -101,7 +101,6 @@ public interface IApplicationThread extends IInterface { Bundle coreSettings) throws RemoteException; void scheduleExit() throws RemoteException; void scheduleSuicide() throws RemoteException; - void requestThumbnail(IBinder token) throws RemoteException; void scheduleConfigurationChanged(Configuration config) throws RemoteException; void updateTimeZone() throws RemoteException; void clearDnsCache() throws RemoteException; @@ -159,7 +158,7 @@ public interface IApplicationThread extends IInterface { int SCHEDULE_STOP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+11; int BIND_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+12; int SCHEDULE_EXIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+13; - int REQUEST_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+14; + int SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+15; int SCHEDULE_SERVICE_ARGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+16; int UPDATE_TIME_ZONE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+17; diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index ad4027d..b917263 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -58,6 +58,8 @@ interface INotificationManager ZenModeConfig getZenModeConfig(); boolean setZenModeConfig(in ZenModeConfig config); oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions); - oneway void requestZenModeConditions(in IConditionListener callback, boolean requested); + oneway void requestZenModeConditions(in IConditionListener callback, int relevance); oneway void setZenModeCondition(in Uri conditionId); + oneway void setAutomaticZenModeConditions(in Uri[] conditionIds); + Condition[] getAutomaticZenModeConditions(); }
\ No newline at end of file diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl index e587912..ecf2c73 100644 --- a/core/java/android/app/IProcessObserver.aidl +++ b/core/java/android/app/IProcessObserver.aidl @@ -20,7 +20,7 @@ package android.app; oneway interface IProcessObserver { void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities); - void onImportanceChanged(int pid, int uid, int importance); + void onProcessStateChanged(int pid, int uid, int procState); void onProcessDied(int pid, int uid); } diff --git a/core/java/android/app/IThumbnailReceiver.aidl b/core/java/android/app/IThumbnailReceiver.aidl deleted file mode 100644 index 7943f2c..0000000 --- a/core/java/android/app/IThumbnailReceiver.aidl +++ /dev/null @@ -1,30 +0,0 @@ -/* //device/java/android/android/app/IThumbnailReceiver.aidl -** -** Copyright 2006, 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; - -import android.graphics.Bitmap; - -/** - * System private API for receiving updated thumbnails from a checkpoint. - * - * {@hide} - */ -oneway interface IThumbnailReceiver { - void newThumbnail(int id, in Bitmap thumbnail, CharSequence description); - void finished(); -} - diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 5f8ebbe..58d707c 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -651,6 +651,10 @@ public class WallpaperManager { * not "image/*" */ public Intent getCropAndSetWallpaperIntent(Uri imageUri) { + if (imageUri == null) { + throw new IllegalArgumentException("Image URI must not be null"); + } + if (!ContentResolver.SCHEME_CONTENT.equals(imageUri.getScheme())) { throw new IllegalArgumentException("Image URI must be of the " + ContentResolver.SCHEME_CONTENT + " scheme type"); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 68ab611..929bf65 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -172,6 +172,16 @@ public class DevicePolicyManager { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD"; + /** + * Flag for {@link #addForwardingIntentFilter}: the intents will forwarded to the primary user. + */ + public static int FLAG_TO_PRIMARY_USER = 0x0001; + + /** + * Flag for {@link #addForwardingIntentFilter}: the intents will be forwarded to the managed + * profile. + */ + public static int FLAG_TO_MANAGED_PROFILE = 0x0002; /** * Return true if the given administrator component is currently @@ -1778,7 +1788,7 @@ public class DevicePolicyManager { * Sets the enabled state of the profile. A profile should be enabled only once it is ready to * be used. Only the profile owner can call this. * - * @see #isPRofileOwnerApp + * @see #isProfileOwnerApp * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. */ @@ -1834,27 +1844,6 @@ public class DevicePolicyManager { /** * @hide - * @param userId the userId of a managed profile profile. - * - * @return whether or not the managed profile is enabled. - * @throws IllegalArgumentException if the userId is invalid. - */ - public boolean isProfileEnabled(int userId) throws IllegalArgumentException { - if (mService != null) { - try { - return mService.isProfileEnabled(userId); - } catch (RemoteException re) { - Log.w(TAG, "Failed to get status for owner profile."); - throw new IllegalArgumentException( - "Failed to get status for owner profile.", re); - } - } - return true; - } - - - /** - * @hide * @return the human readable name of the organisation associated with this DPM or null if * one is not set. * @throws IllegalArgumentException if the userId is invalid. @@ -1953,6 +1942,39 @@ public class DevicePolicyManager { } /** + * Called by a profile owner to forward intents sent from the managed profile to the owner, or + * from the owner to the managed profile. + * If an intent matches this intent filter, then activities belonging to the other user can + * respond to this intent. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param filter if an intent matches this IntentFilter, then it can be forwarded. + */ + public void addForwardingIntentFilter(ComponentName admin, IntentFilter filter, int flags) { + if (mService != null) { + try { + mService.addForwardingIntentFilter(admin, filter, flags); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Called by a profile owner to remove the forwarding intent filters from the current user + * and from the owner. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + */ + public void clearForwardingIntentFilters(ComponentName admin) { + if (mService != null) { + try { + mService.clearForwardingIntentFilters(admin); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** * Called by a profile or device owner to get the application restrictions for a given target * application running in the managed profile. * diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 72b3c20..e3090b6 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -109,7 +109,6 @@ interface IDevicePolicyManager { String getProfileOwner(int userHandle); String getProfileOwnerName(int userHandle); void setProfileEnabled(in ComponentName who); - boolean isProfileEnabled(int userHandle); boolean installCaCert(in byte[] certBuffer); void uninstallCaCert(in byte[] certBuffer); @@ -121,4 +120,6 @@ interface IDevicePolicyManager { Bundle getApplicationRestrictions(in ComponentName who, in String packageName); void setUserRestriction(in ComponentName who, in String key, boolean enable); + void addForwardingIntentFilter(in ComponentName admin, in IntentFilter filter, int flags); + void clearForwardingIntentFilters(in ComponentName admin); } diff --git a/core/java/android/app/task/ITaskCallback.aidl b/core/java/android/app/task/ITaskCallback.aidl new file mode 100644 index 0000000..ffa57d1 --- /dev/null +++ b/core/java/android/app/task/ITaskCallback.aidl @@ -0,0 +1,53 @@ +/** + * Copyright 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.task; + +import android.app.task.ITaskService; +import android.app.task.TaskParams; + +/** + * The server side of the TaskManager IPC protocols. The app-side implementation + * invokes on this interface to indicate completion of the (asynchronous) instructions + * issued by the server. + * + * In all cases, the 'who' parameter is the caller's service binder, used to track + * which Task Service instance is reporting. + * + * {@hide} + */ +interface ITaskCallback { + /** + * Immediate callback to the system after sending a start signal, used to quickly detect ANR. + * + * @param taskId Unique integer used to identify this task. + */ + void acknowledgeStartMessage(int taskId); + /** + * Immediate callback to the system after sending a stop signal, used to quickly detect ANR. + * + * @param taskId Unique integer used to identify this task. + */ + void acknowledgeStopMessage(int taskId); + /* + * Tell the task manager that the client is done with its execution, so that it can go on to + * the next one and stop attributing wakelock time to us etc. + * + * @param taskId Unique integer used to identify this task. + * @param reschedule Whether or not to reschedule this task. + */ + void taskFinished(int taskId, boolean reschedule); +} diff --git a/core/java/android/app/task/ITaskService.aidl b/core/java/android/app/task/ITaskService.aidl new file mode 100644 index 0000000..87b0191 --- /dev/null +++ b/core/java/android/app/task/ITaskService.aidl @@ -0,0 +1,35 @@ +/** + * Copyright 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.task; + +import android.app.task.ITaskCallback; +import android.app.task.TaskParams; + +import android.os.Bundle; + +/** + * Interface that the framework uses to communicate with application code that implements a + * TaskService. End user code does not implement this interface directly; instead, the app's + * service implementation will extend android.app.task.TaskService. + * {@hide} + */ +oneway interface ITaskService { + /** Begin execution of application's task. */ + void startTask(in TaskParams taskParams); + /** Stop execution of application's task. */ + void stopTask(in TaskParams taskParams); +} diff --git a/core/java/android/app/task/TaskParams.aidl b/core/java/android/app/task/TaskParams.aidl new file mode 100644 index 0000000..9b25855 --- /dev/null +++ b/core/java/android/app/task/TaskParams.aidl @@ -0,0 +1,19 @@ +/** + * Copyright 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.task; + +parcelable TaskParams;
\ No newline at end of file diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java new file mode 100644 index 0000000..e2eafd8 --- /dev/null +++ b/core/java/android/app/task/TaskParams.java @@ -0,0 +1,86 @@ +/* + * 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.task; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Contains the parameters used to configure/identify your task. You do not create this object + * yourself, instead it is handed in to your application by the System. + */ +public class TaskParams implements Parcelable { + + private final int taskId; + private final Bundle extras; + private final IBinder mCallback; + + /** + * @return The unique id of this task, specified at creation time. + */ + public int getTaskId() { + return taskId; + } + + /** + * @return The extras you passed in when constructing this task with + * {@link android.content.Task.Builder#setExtras(android.os.Bundle)}. This will + * never be null. If you did not set any extras this will be an empty bundle. + */ + public Bundle getExtras() { + return extras; + } + + /** + * @hide + */ + public ITaskCallback getCallback() { + return ITaskCallback.Stub.asInterface(mCallback); + } + + private TaskParams(Parcel in) { + taskId = in.readInt(); + extras = in.readBundle(); + mCallback = in.readStrongBinder(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(taskId); + dest.writeBundle(extras); + dest.writeStrongBinder(mCallback); + } + + public static final Creator<TaskParams> CREATOR = new Creator<TaskParams>() { + @Override + public TaskParams createFromParcel(Parcel in) { + return new TaskParams(in); + } + + @Override + public TaskParams[] newArray(int size) { + return new TaskParams[size]; + } + }; +} diff --git a/core/java/android/app/task/TaskService.java b/core/java/android/app/task/TaskService.java new file mode 100644 index 0000000..81333be --- /dev/null +++ b/core/java/android/app/task/TaskService.java @@ -0,0 +1,260 @@ +/* + * 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.task; + +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +/** + * <p>Entry point for the callback from the {@link android.content.TaskManager}.</p> + * <p>This is the base class that handles asynchronous requests that were previously scheduled. You + * are responsible for overriding {@link TaskService#onStartTask(TaskParams)}, which is where + * you will implement your task logic.</p> + * <p>This service executes each incoming task on a {@link android.os.Handler} running on your + * application's main thread. This means that you <b>must</b> offload your execution logic to + * another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result + * in blocking any future callbacks from the TaskManager - specifically + * {@link #onStopTask(android.app.task.TaskParams)}, which is meant to inform you that the + * scheduling requirements are no longer being met.</p> + */ +public abstract class TaskService extends Service { + private static final String TAG = "TaskService"; + + /** + * Task services must be protected with this permission: + * + * <pre class="prettyprint"> + * <service android:name="MyTaskService" + * android:permission="android.permission.BIND_TASK_SERVICE" > + * ... + * </service> + * </pre> + * + * <p>If a task service is declared in the manifest but not protected with this + * permission, that service will be ignored by the OS. + */ + public static final String PERMISSION_BIND = + "android.permission.BIND_TASK_SERVICE"; + + /** + * Identifier for a message that will result in a call to + * {@link #onStartTask(android.app.task.TaskParams)}. + */ + private final int MSG_EXECUTE_TASK = 0; + /** + * Message that will result in a call to {@link #onStopTask(android.app.task.TaskParams)}. + */ + private final int MSG_STOP_TASK = 1; + /** + * Message that the client has completed execution of this task. + */ + private final int MSG_TASK_FINISHED = 2; + + /** Lock object for {@link #mHandler}. */ + private final Object mHandlerLock = new Object(); + + /** + * Handler we post tasks to. Responsible for calling into the client logic, and handling the + * callback to the system. + */ + @GuardedBy("mHandlerLock") + TaskHandler mHandler; + + /** Binder for this service. */ + ITaskService mBinder = new ITaskService.Stub() { + @Override + public void startTask(TaskParams taskParams) { + ensureHandler(); + Message m = Message.obtain(mHandler, MSG_EXECUTE_TASK, taskParams); + m.sendToTarget(); + } + @Override + public void stopTask(TaskParams taskParams) { + ensureHandler(); + Message m = Message.obtain(mHandler, MSG_STOP_TASK, taskParams); + m.sendToTarget(); + } + }; + + /** @hide */ + void ensureHandler() { + synchronized (mHandlerLock) { + if (mHandler == null) { + mHandler = new TaskHandler(getMainLooper()); + } + } + } + + /** + * Runs on application's main thread - callbacks are meant to offboard work to some other + * (app-specified) mechanism. + * @hide + */ + class TaskHandler extends Handler { + TaskHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + final TaskParams params = (TaskParams) msg.obj; + switch (msg.what) { + case MSG_EXECUTE_TASK: + try { + TaskService.this.onStartTask(params); + } catch (Exception e) { + Log.e(TAG, "Error while executing task: " + params.getTaskId()); + throw new RuntimeException(e); + } finally { + maybeAckMessageReceived(params, MSG_EXECUTE_TASK); + } + break; + case MSG_STOP_TASK: + try { + TaskService.this.onStopTask(params); + } catch (Exception e) { + Log.e(TAG, "Application unable to handle onStopTask.", e); + throw new RuntimeException(e); + } finally { + maybeAckMessageReceived(params, MSG_STOP_TASK); + } + break; + case MSG_TASK_FINISHED: + final boolean needsReschedule = (msg.arg2 == 1); + ITaskCallback callback = params.getCallback(); + if (callback != null) { + try { + callback.taskFinished(params.getTaskId(), needsReschedule); + } catch (RemoteException e) { + Log.e(TAG, "Error reporting task finish to system: binder has gone" + + "away."); + } + } else { + Log.e(TAG, "finishTask() called for a nonexistent task id."); + } + break; + default: + Log.e(TAG, "Unrecognised message received."); + break; + } + } + + /** + * Messages come in on the application's main thread, so rather than run the risk of + * waiting for an app that may be doing something foolhardy, we ack to the system after + * processing a message. This allows us to throw up an ANR dialogue as quickly as possible. + * @param params id of the task we're acking. + * @param state Information about what message we're acking. + */ + private void maybeAckMessageReceived(TaskParams params, int state) { + final ITaskCallback callback = params.getCallback(); + final int taskId = params.getTaskId(); + if (callback != null) { + try { + if (state == MSG_EXECUTE_TASK) { + callback.acknowledgeStartMessage(taskId); + } else if (state == MSG_STOP_TASK) { + callback.acknowledgeStopMessage(taskId); + } + } catch(RemoteException e) { + Log.e(TAG, "System unreachable for starting task."); + } + } else { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, state + ": Attempting to ack a task that has already been" + + "processed."); + } + } + } + } + + /** @hide */ + public final IBinder onBind(Intent intent) { + return mBinder.asBinder(); + } + + /** + * Override this method with the callback logic for your task. Any such logic needs to be + * performed on a separate thread, as this function is executed on your application's main + * thread. + * + * @param params Parameters specifying info about this task, including the extras bundle you + * optionally provided at task-creation time. + */ + public abstract void onStartTask(TaskParams params); + + /** + * This method is called if your task should be stopped even before you've called + * {@link #taskFinished(TaskParams, boolean)}. + * + * <p>This will happen if the requirements specified at schedule time are no longer met. For + * example you may have requested WiFi with + * {@link android.content.Task.Builder#setRequiredNetworkCapabilities(int)}, yet while your + * task was executing the user toggled WiFi. Another example is if you had specified + * {@link android.content.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its + * idle maintenance window. You are solely responsible for the behaviour of your application + * upon receipt of this message; your app will likely start to misbehave if you ignore it. One + * repercussion is that the system will cease to hold a wakelock for you.</p> + * + * <p>After you've done your clean-up you are still expected to call + * {@link #taskFinished(TaskParams, boolean)} this will inform the TaskManager that all is well, and + * allow you to reschedule your task as it is probably uncompleted. Until you call + * taskFinished() you will not receive any newly scheduled tasks with the given task id as the + * TaskManager will consider the task to be in an error state.</p> + * + * @param params Parameters specifying info about this task. + * @return True to indicate to the TaskManager whether you'd like to reschedule this task based + * on the criteria provided at task creation-time. False to drop the task. Regardless of the + * value returned, your task must stop executing. + */ + public abstract boolean onStopTask(TaskParams params); + + /** + * Callback to inform the TaskManager you have completed execution. This can be called from any + * thread, as it will ultimately be run on your application's main thread. When the system + * receives this message it will release the wakelock being held. + * <p> + * You can specify post-execution behaviour to the scheduler here with <code>needsReschedule + * </code>. This will apply a back-off timer to your task based on the default, or what was + * set with {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The + * original requirements are always honoured even for a backed-off task. + * Note that a task running in idle mode will not be backed-off. Instead what will happen + * is the task will be re-added to the queue and re-executed within a future idle + * maintenance window. + * </p> + * + * @param params Parameters specifying system-provided info about this task, this was given to + * your application in {@link #onStartTask(TaskParams)}. + * @param needsReschedule True if this task is complete, false if you want the TaskManager to + * reschedule you. + */ + public final void taskFinished(TaskParams params, boolean needsReschedule) { + ensureHandler(); + Message m = Message.obtain(mHandler, MSG_TASK_FINISHED, params); + m.arg2 = needsReschedule ? 1 : 0; + m.sendToTarget(); + } +}
\ No newline at end of file diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index dd3a871..d3e9089 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -181,7 +181,8 @@ public class AppWidgetManager { * A bundle extra that hints to the AppWidgetProvider the category of host that owns this * this widget. Can have the value {@link * AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN} or {@link - * AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD}. + * AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD} or {@link + * AppWidgetProviderInfo#WIDGET_CATEGORY_RECENTS}. */ public static final String OPTION_APPWIDGET_HOST_CATEGORY = "appWidgetCategory"; diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index 4b33799..8b9c7f0 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -54,6 +54,11 @@ public class AppWidgetProviderInfo implements Parcelable { public static final int WIDGET_CATEGORY_KEYGUARD = 2; /** + * Indicates that the widget can be displayed within recents. + */ + public static final int WIDGET_CATEGORY_RECENTS = 4; + + /** * Identity of this AppWidget component. This component should be a {@link * android.content.BroadcastReceiver}, and it will be sent the AppWidget intents * {@link android.appwidget as described in the AppWidget package documentation}. diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index a396a05..7f8d0ab 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -512,6 +512,25 @@ public final class BluetoothDevice implements Parcelable { */ public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; + /** + * No preferrence of physical transport for GATT connections to remote dual-mode devices + * @hide + */ + public static final int TRANSPORT_AUTO = 0; + + /** + * Prefer BR/EDR transport for GATT connections to remote dual-mode devices + * @hide + */ + public static final int TRANSPORT_BREDR = 1; + + /** + * Prefer LE transport for GATT connections to remote dual-mode devices + * @hide + */ + public static final int TRANSPORT_LE = 2; + + /** * Lazy initialization. Guaranteed final after first object constructed, or * getService() called. @@ -1216,6 +1235,27 @@ public final class BluetoothDevice implements Parcelable { */ public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) { + return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO)); + } + + /** + * Connect to GATT Server hosted by this device. Caller acts as GATT client. + * The callback is used to deliver results to Caller, such as connection status as well + * as any further GATT client operations. + * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct + * GATT client operations. + * @param callback GATT callback handler that will receive asynchronous callbacks. + * @param autoConnect Whether to directly connect to the remote device (false) + * or to automatically connect as soon as the remote + * device becomes available (true). + * @param transport preferred transport for GATT connections to remote dual-mode devices + * {@link BluetoothDevice#TRANSPORT_AUTO} or + * {@link BluetoothDevice#TRANSPORT_BREDR} or {@link BluetoothDevice#TRANSPORT_LE} + * @throws IllegalArgumentException if callback is null + * @hide + */ + public BluetoothGatt connectGatt(Context context, boolean autoConnect, + BluetoothGattCallback callback, int transport) { // TODO(Bluetooth) check whether platform support BLE // Do the check here or in GattServer? BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); @@ -1226,10 +1266,11 @@ public final class BluetoothDevice implements Parcelable { // BLE is not supported return null; } - BluetoothGatt gatt = new BluetoothGatt(context, iGatt, this); + BluetoothGatt gatt = new BluetoothGatt(context, iGatt, this, transport); gatt.connect(autoConnect, callback); return gatt; } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } + } diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index ff3af7c..601d9ee 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -51,6 +51,7 @@ public final class BluetoothGatt implements BluetoothProfile { private int mConnState; private final Object mStateLock = new Object(); private Boolean mDeviceBusy = false; + private int mTransport; private static final int CONN_STATE_IDLE = 0; private static final int CONN_STATE_CONNECTING = 1; @@ -135,7 +136,7 @@ public final class BluetoothGatt implements BluetoothProfile { } try { mService.clientConnect(mClientIf, mDevice.getAddress(), - !mAutoConnect); // autoConnect is inverse of "isDirect" + !mAutoConnect, mTransport); // autoConnect is inverse of "isDirect" } catch (RemoteException e) { Log.e(TAG,"",e); } @@ -600,10 +601,12 @@ public final class BluetoothGatt implements BluetoothProfile { } }; - /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device) { + /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device, + int transport) { mContext = context; mService = iGatt; mDevice = device; + mTransport = transport; mServices = new ArrayList<BluetoothGattService>(); mConnState = CONN_STATE_IDLE; @@ -759,7 +762,7 @@ public final class BluetoothGatt implements BluetoothProfile { public boolean connect() { try { mService.clientConnect(mClientIf, mDevice.getAddress(), - false); // autoConnect is inverse of "isDirect" + false, mTransport); // autoConnect is inverse of "isDirect" return true; } catch (RemoteException e) { Log.e(TAG,"",e); diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java index 0c00c06..34e8605 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -50,6 +50,7 @@ public final class BluetoothGattServer implements BluetoothProfile { private Object mServerIfLock = new Object(); private int mServerIf; + private int mTransport; private List<BluetoothGattService> mServices; private static final int CALLBACK_REG_TIMEOUT = 10000; @@ -269,12 +270,13 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Create a BluetoothGattServer proxy object. */ - /*package*/ BluetoothGattServer(Context context, IBluetoothGatt iGatt) { + /*package*/ BluetoothGattServer(Context context, IBluetoothGatt iGatt, int transport) { mContext = context; mService = iGatt; mAdapter = BluetoothAdapter.getDefaultAdapter(); mCallback = null; mServerIf = 0; + mTransport = transport; mServices = new ArrayList<BluetoothGattService>(); } @@ -401,7 +403,7 @@ public final class BluetoothGattServer implements BluetoothProfile { try { mService.serverConnect(mServerIf, device.getAddress(), - autoConnect ? false : true); // autoConnect is inverse of "isDirect" + autoConnect ? false : true,mTransport); // autoConnect is inverse of "isDirect" } catch (RemoteException e) { Log.e(TAG,"",e); return false; diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java index 172f3bc..b1618cf3 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -194,6 +194,26 @@ public final class BluetoothManager { */ public BluetoothGattServer openGattServer(Context context, BluetoothGattServerCallback callback) { + + return (openGattServer (context, callback, BluetoothDevice.TRANSPORT_AUTO)); + } + + /** + * Open a GATT Server + * The callback is used to deliver results to Caller, such as connection status as well + * as the results of any other GATT server operations. + * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer + * to conduct GATT server operations. + * @param context App context + * @param callback GATT server callback handler that will receive asynchronous callbacks. + * @param transport preferred transport for GATT connections to remote dual-mode devices + * {@link BluetoothDevice#TRANSPORT_AUTO} or + * {@link BluetoothDevice#TRANSPORT_BREDR} or {@link BluetoothDevice#TRANSPORT_LE} + * @return BluetoothGattServer instance + * @hide + */ + public BluetoothGattServer openGattServer(Context context, + BluetoothGattServerCallback callback,int transport) { if (context == null || callback == null) { throw new IllegalArgumentException("null parameter: " + context + " " + callback); } @@ -208,7 +228,7 @@ public final class BluetoothManager { Log.e(TAG, "Fail to get GATT Server connection"); return null; } - BluetoothGattServer mGattServer = new BluetoothGattServer(context, iGatt); + BluetoothGattServer mGattServer = new BluetoothGattServer(context, iGatt,transport); Boolean regStatus = mGattServer.registerCallback(callback); return regStatus? mGattServer : null; } catch (RemoteException e) { diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index f532f7c..00fd7ce 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -325,6 +325,7 @@ public final class BluetoothSocket implements Closeable { } } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); + throw new IOException("unable to send RPC: " + e.getMessage()); } } diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java index 6dd551e..a0b603e 100644 --- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java +++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java @@ -53,6 +53,9 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker { private static final boolean DBG = true; private static final boolean VDBG = true; + // Event sent to the mBtdtHandler when DHCP fails so we can tear down the network. + private static final int EVENT_NETWORK_FAILED = 1; + private AtomicBoolean mTeardownRequested = new AtomicBoolean(false); private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false); private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0); @@ -315,6 +318,7 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker { } if (!success) { Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError()); + mBtdtHandler.obtainMessage(EVENT_NETWORK_FAILED).sendToTarget(); return; } mLinkProperties = dhcpResults.linkProperties; @@ -407,6 +411,10 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker { if (VDBG) Log.d(TAG, "got EVENT_NETWORK_DISCONNECTED, " + linkProperties); mBtdt.stopReverseTether(); break; + case EVENT_NETWORK_FAILED: + if (VDBG) Log.d(TAG, "got EVENT_NETWORK_FAILED"); + mBtdt.teardown(); + break; } } } diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 4b28516..ab53fb0 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -18,6 +18,8 @@ package android.bluetooth; import android.os.ParcelUuid; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.Arrays; import java.util.HashSet; import java.util.UUID; @@ -76,6 +78,12 @@ public final class BluetoothUuid { public static final ParcelUuid BASE_UUID = ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); + /** Length of bytes for 16 bit UUID */ + public static final int UUID_BYTES_16_BIT = 2; + /** Length of bytes for 32 bit UUID */ + public static final int UUID_BYTES_32_BIT = 4; + /** Length of bytes for 128 bit UUID */ + public static final int UUID_BYTES_128_BIT = 16; public static final ParcelUuid[] RESERVED_UUIDS = { AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, @@ -216,15 +224,60 @@ public final class BluetoothUuid { } /** + * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID, + * but the returned UUID is always in 128-bit format. + * Note UUID is little endian in Bluetooth. + * + * @param uuidBytes Byte representation of uuid. + * @return {@link ParcelUuid} parsed from bytes. + * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed. + */ + public static ParcelUuid parseUuidFrom(byte[] uuidBytes) { + if (uuidBytes == null) { + throw new IllegalArgumentException("uuidBytes cannot be null"); + } + int length = uuidBytes.length; + if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT && + length != UUID_BYTES_128_BIT) { + throw new IllegalArgumentException("uuidBytes length invalid - " + length); + } + + // Construct a 128 bit UUID. + if (length == UUID_BYTES_128_BIT) { + ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); + long msb = buf.getLong(8); + long lsb = buf.getLong(0); + return new ParcelUuid(new UUID(msb, lsb)); + } + + // For 16 bit and 32 bit UUID we need to convert them to 128 bit value. + // 128_bit_value = uuid * 2^96 + BASE_UUID + long shortUuid; + if (length == UUID_BYTES_16_BIT) { + shortUuid = uuidBytes[0] & 0xFF; + shortUuid += (uuidBytes[1] & 0xFF) << 8; + } else { + shortUuid = uuidBytes[0] & 0xFF ; + shortUuid += (uuidBytes[1] & 0xFF) << 8; + shortUuid += (uuidBytes[2] & 0xFF) << 16; + shortUuid += (uuidBytes[3] & 0xFF) << 24; + } + long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32); + long lsb = BASE_UUID.getUuid().getLeastSignificantBits(); + return new ParcelUuid(new UUID(msb, lsb)); + } + + /** * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid. + * * @param parcelUuid * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise. */ public static boolean isShortUuid(ParcelUuid parcelUuid) { - UUID uuid = parcelUuid.getUuid(); - if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { - return false; - } - return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L); + UUID uuid = parcelUuid.getUuid(); + if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { + return false; + } + return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L); } } diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index c6b5c3d..49b156d 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -35,7 +35,7 @@ interface IBluetoothGatt { void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback); void unregisterClient(in int clientIf); - void clientConnect(in int clientIf, in String address, in boolean isDirect); + void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport); void clientDisconnect(in int clientIf, in String address); void startAdvertising(in int appIf); void stopAdvertising(); @@ -77,7 +77,7 @@ interface IBluetoothGatt { void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallback callback); void unregisterServer(in int serverIf); - void serverConnect(in int servertIf, in String address, in boolean isDirect); + void serverConnect(in int servertIf, in String address, in boolean isDirect, in int transport); void serverDisconnect(in int serverIf, in String address); void beginServiceDeclaration(in int serverIf, in int srvcType, in int srvcInstanceId, in int minHandles, diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index de223a3..7c625bd 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2377,6 +2377,16 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.service.fingerprint.FingerprintManager} for handling management + * of fingerprints. + * + * @see #getSystemService + * @see android.app.FingerprintManager + */ + public static final String FINGERPRINT_SERVICE = "fingerprint"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.media.MediaRouter} for controlling and managing * routing of media. * diff --git a/core/java/android/content/Task.java b/core/java/android/content/Task.java new file mode 100644 index 0000000..ed5ed88 --- /dev/null +++ b/core/java/android/content/Task.java @@ -0,0 +1,400 @@ +/* + * 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.content; + +import android.app.task.TaskService; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Container of data passed to the {@link android.content.TaskManager} fully encapsulating the + * parameters required to schedule work against the calling application. These are constructed + * using the {@link Task.Builder}. + */ +public class Task implements Parcelable { + + public interface NetworkType { + public final int ANY = 0; + public final int UNMETERED = 1; + } + + /** + * Linear: retry_time(failure_time, t) = failure_time + initial_retry_delay * t, t >= 1 + * Expon: retry_time(failure_time, t) = failure_time + initial_retry_delay ^ t, t >= 1 + */ + public interface BackoffPolicy { + public final int LINEAR = 0; + public final int EXPONENTIAL = 1; + } + + /** + * Unique task id associated with this class. This is assigned to your task by the scheduler. + */ + public int getTaskId() { + return taskId; + } + + /** + * Bundle of extras which are returned to your application at execution time. + */ + public Bundle getExtras() { + return extras; + } + + /** + * Name of the service endpoint that will be called back into by the TaskManager. + */ + public String getServiceClassName() { + return serviceClassName; + } + + /** + * Whether this task needs the device to be plugged in. + */ + public boolean isRequireCharging() { + return requireCharging; + } + + /** + * Whether this task needs the device to be in an Idle maintenance window. + */ + public boolean isRequireDeviceIdle() { + return requireDeviceIdle; + } + + /** + * See {@link android.content.Task.NetworkType} for a description of this value. + */ + public int getNetworkCapabilities() { + return networkCapabilities; + } + + /** + * Set for a task that does not recur periodically, to specify a delay after which the task + * will be eligible for execution. This value is not set if the task recurs periodically. + */ + public long getMinLatencyMillis() { + return minLatencyMillis; + } + + /** + * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the task recurs + * periodically. + */ + public long getMaxExecutionDelayMillis() { + return maxExecutionDelayMillis; + } + + /** + * Track whether this task will repeat with a given period. + */ + public boolean isPeriodic() { + return isPeriodic; + } + + /** + * Set to the interval between occurrences of this task. This value is <b>not</b> set if the + * task does not recur periodically. + */ + public long getIntervalMillis() { + return intervalMillis; + } + + /** + * The amount of time the TaskManager will wait before rescheduling a failed task. This value + * will be increased depending on the backoff policy specified at task creation time. Defaults + * to 5 seconds. + */ + public long getInitialBackoffMillis() { + return initialBackoffMillis; + } + + /** + * See {@link android.content.Task.BackoffPolicy} for an explanation of the values this field + * can take. This defaults to exponential. + */ + public int getBackoffPolicy() { + return backoffPolicy; + } + + private final int taskId; + // TODO: Change this to use PersistableBundle when that lands in master. + private final Bundle extras; + private final String serviceClassName; + private final boolean requireCharging; + private final boolean requireDeviceIdle; + private final int networkCapabilities; + private final long minLatencyMillis; + private final long maxExecutionDelayMillis; + private final boolean isPeriodic; + private final long intervalMillis; + private final long initialBackoffMillis; + private final int backoffPolicy; + + private Task(Parcel in) { + taskId = in.readInt(); + extras = in.readBundle(); + serviceClassName = in.readString(); + requireCharging = in.readInt() == 1; + requireDeviceIdle = in.readInt() == 1; + networkCapabilities = in.readInt(); + minLatencyMillis = in.readLong(); + maxExecutionDelayMillis = in.readLong(); + isPeriodic = in.readInt() == 1; + intervalMillis = in.readLong(); + initialBackoffMillis = in.readLong(); + backoffPolicy = in.readInt(); + } + + private Task(Task.Builder b) { + taskId = b.mTaskId; + extras = new Bundle(b.mExtras); + serviceClassName = b.mTaskServiceClassName; + requireCharging = b.mRequiresCharging; + requireDeviceIdle = b.mRequiresDeviceIdle; + networkCapabilities = b.mNetworkCapabilities; + minLatencyMillis = b.mMinLatencyMillis; + maxExecutionDelayMillis = b.mMaxExecutionDelayMillis; + isPeriodic = b.mIsPeriodic; + intervalMillis = b.mIntervalMillis; + initialBackoffMillis = b.mInitialBackoffMillis; + backoffPolicy = b.mBackoffPolicy; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(taskId); + out.writeBundle(extras); + out.writeString(serviceClassName); + out.writeInt(requireCharging ? 1 : 0); + out.writeInt(requireDeviceIdle ? 1 : 0); + out.writeInt(networkCapabilities); + out.writeLong(minLatencyMillis); + out.writeLong(maxExecutionDelayMillis); + out.writeInt(isPeriodic ? 1 : 0); + out.writeLong(intervalMillis); + out.writeLong(initialBackoffMillis); + out.writeInt(backoffPolicy); + } + + public static final Creator<Task> CREATOR = new Creator<Task>() { + @Override + public Task createFromParcel(Parcel in) { + return new Task(in); + } + + @Override + public Task[] newArray(int size) { + return new Task[size]; + } + }; + + /** + * Builder class for constructing {@link Task} objects. + */ + public final class Builder { + private int mTaskId; + private Bundle mExtras; + private String mTaskServiceClassName; + // Requirements. + private boolean mRequiresCharging; + private boolean mRequiresDeviceIdle; + private int mNetworkCapabilities; + // One-off parameters. + private long mMinLatencyMillis; + private long mMaxExecutionDelayMillis; + // Periodic parameters. + private boolean mIsPeriodic; + private long mIntervalMillis; + // Back-off parameters. + private long mInitialBackoffMillis = 5000L; + private int mBackoffPolicy = BackoffPolicy.EXPONENTIAL; + /** Easy way to track whether the client has tried to set a back-off policy. */ + private boolean mBackoffPolicySet = false; + + /** + * @param taskId Application-provided id for this task. Subsequent calls to cancel, or + * tasks created with the same taskId, will update the pre-existing task with + * the same id. + * @param cls The endpoint that you implement that will receive the callback from the + * TaskManager. + */ + public Builder(int taskId, Class<TaskService> cls) { + mTaskServiceClassName = cls.getClass().getName(); + mTaskId = taskId; + } + + /** + * Set optional extras. This is persisted, so we only allow primitive types. + * @param extras Bundle containing extras you want the scheduler to hold on to for you. + */ + public Builder setExtras(Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Set some description of the kind of network capabilities you would like to have. This + * will be a parameter defined in {@link android.content.Task.NetworkType}. + * Not calling this function means the network is not necessary. + * Bear in mind that calling this function defines network as a strict requirement for your + * task if the network requested is not available your task will never run. See + * {@link #setOverrideDeadline(long)} to change this behaviour. + */ + public Builder setRequiredNetworkCapabilities(int networkCapabilities) { + mNetworkCapabilities = networkCapabilities; + return this; + } + + /* + * Specify that to run this task, the device needs to be plugged in. This defaults to + * false. + * @param requireCharging Whether or not the device is plugged in. + */ + public Builder setRequiresCharging(boolean requiresCharging) { + mRequiresCharging = requiresCharging; + return this; + } + + /** + * Specify that to run, the task needs the device to be in idle mode. This defaults to + * false. + * <p>Idle mode is a loose definition provided by the system, which means that the device + * is not in use, and has not been in use for some time. As such, it is a good time to + * perform resource heavy tasks. Bear in mind that battery usage will still be attributed + * to your application, and surfaced to the user in battery stats.</p> + * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance + * window. + */ + public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) { + mRequiresDeviceIdle = requiresDeviceIdle; + return this; + } + + /** + * Specify that this task should recur with the provided interval, not more than once per + * period. You have no control over when within this interval this task will be executed, + * only the guarantee that it will be executed at most once within this interval. + * A periodic task will be repeated until the phone is turned off, however it will only be + * persisted if the client app has declared the + * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule + * periodic tasks without this permission, they simply will cease to exist after the phone + * restarts. + * Setting this function on the builder with {@link #setMinimumLatency(long)} or + * {@link #setOverrideDeadline(long)} will result in an error. + * @param intervalMillis Millisecond interval for which this task will repeat. + */ + public Builder setPeriodic(long intervalMillis) { + mIsPeriodic = true; + mIntervalMillis = intervalMillis; + return this; + } + + /** + * Specify that this task should be delayed by the provided amount of time. + * Because it doesn't make sense setting this property on a periodic task, doing so will + * throw an {@link java.lang.IllegalArgumentException} when + * {@link android.content.Task.Builder#build()} is called. + * @param minLatencyMillis Milliseconds before which this task will not be considered for + * execution. + */ + public Builder setMinimumLatency(long minLatencyMillis) { + mMinLatencyMillis = minLatencyMillis; + return this; + } + + /** + * Set deadline which is the maximum scheduling latency. The task will be run by this + * deadline even if other requirements are not met. Because it doesn't make sense setting + * this property on a periodic task, doing so will throw an + * {@link java.lang.IllegalArgumentException} when + * {@link android.content.Task.Builder#build()} is called. + */ + public Builder setOverrideDeadline(long maxExecutionDelayMillis) { + mMaxExecutionDelayMillis = maxExecutionDelayMillis; + return this; + } + + /** + * Set up the back-off/retry policy. + * This defaults to some respectable values: {5 seconds, Exponential}. We cap back-off at + * 1hr. + * Note that trying to set a backoff criteria for a task with + * {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build(). + * This is because back-off typically does not make sense for these types of tasks. See + * {@link android.app.task.TaskService#taskFinished(android.app.task.TaskParams, boolean)} + * for more description of the return value for the case of a task executing while in idle + * mode. + * @param initialBackoffMillis Millisecond time interval to wait initially when task has + * failed. + * @param backoffPolicy is one of {@link BackoffPolicy} + */ + public Builder setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) { + mBackoffPolicySet = true; + mInitialBackoffMillis = initialBackoffMillis; + mBackoffPolicy = backoffPolicy; + return this; + } + + /** + * @return The task object to hand to the TaskManager. This object is immutable. + */ + public Task build() { + // Check that extras bundle only contains primitive types. + try { + for (String key : extras.keySet()) { + Object value = extras.get(key); + if (value == null) continue; + if (value instanceof Long) continue; + if (value instanceof Integer) continue; + if (value instanceof Boolean) continue; + if (value instanceof Float) continue; + if (value instanceof Double) continue; + if (value instanceof String) continue; + throw new IllegalArgumentException("Unexpected value type: " + + value.getClass().getName()); + } + } catch (IllegalArgumentException e) { + throw e; + } catch (RuntimeException exc) { + throw new IllegalArgumentException("error unparcelling Bundle", exc); + } + // Check that a deadline was not set on a periodic task. + if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) { + throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " + + "periodic task."); + } + if (mIsPeriodic && (mMinLatencyMillis != 0L)) { + throw new IllegalArgumentException("Can't call setMinimumLatency() on a " + + "periodic task"); + } + if (mBackoffPolicySet && mRequiresDeviceIdle) { + throw new IllegalArgumentException("An idle mode task will not respect any" + + " back-off policy, so calling setBackoffCriteria with" + + " setRequiresDeviceIdle is an error."); + } + return new Task(this); + } + } + +} diff --git a/core/java/android/content/TaskManager.java b/core/java/android/content/TaskManager.java new file mode 100644 index 0000000..d28d78a --- /dev/null +++ b/core/java/android/content/TaskManager.java @@ -0,0 +1,66 @@ +/* + * 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.content; + +import java.util.List; + +/** + * Class for scheduling various types of tasks with the scheduling framework on the device. + * + * Get an instance of this class through {@link Context#getSystemService(String)}. + */ +public abstract class TaskManager { + /* + * Returned from {@link #schedule(Task)} when an invalid parameter was supplied. This can occur + * if the run-time for your task is too short, or perhaps the system can't resolve the + * requisite {@link TaskService} in your package. + */ + static final int RESULT_INVALID_PARAMETERS = -1; + /** + * Returned from {@link #schedule(Task)} if this application has made too many requests for + * work over too short a time. + */ + // TODO: Determine if this is necessary. + static final int RESULT_OVER_QUOTA = -2; + + /* + * @param task The task you wish scheduled. See {@link Task#TaskBuilder} for more detail on + * the sorts of tasks you can schedule. + * @return If >0, this int corresponds to the taskId of the successfully scheduled task. + * Otherwise you have to compare the return value to the error codes defined in this class. + */ + public abstract int schedule(Task task); + + /** + * Cancel a task that is pending in the TaskManager. + * @param taskId unique identifier for this task. Obtain this value from the tasks returned by + * {@link #getAllPendingTasks()}. + * @return + */ + public abstract void cancel(int taskId); + + /** + * Cancel all tasks that have been registered with the TaskManager by this package. + */ + public abstract void cancelAll(); + + /** + * @return a list of all the tasks registered by this package that have not yet been executed. + */ + public abstract List<Task> getAllPendingTasks(); + +} diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 796b113..0acf043 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -35,4 +35,6 @@ interface ILauncherApps { ResolveInfo resolveActivity(in Intent intent, in UserHandle user); void startActivityAsUser(in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); + boolean isPackageEnabled(String packageName, in UserHandle user); + boolean isActivityEnabled(in ComponentName component, in UserHandle user); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 488e25f..03eb50f 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -111,6 +111,8 @@ interface IPackageManager { ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId); + boolean canForwardTo(in Intent intent, String resolvedType, int userIdFrom, int userIdDest); + List<ResolveInfo> queryIntentActivities(in Intent intent, String resolvedType, int flags, int userId); @@ -245,6 +247,11 @@ interface IPackageManager { void clearPackagePersistentPreferredActivities(String packageName, int userId); + void addForwardingIntentFilter(in IntentFilter filter, boolean removable, int userIdOrig, + int userIdDest); + + void clearForwardingIntentFilters(int userIdOrig); + /** * Report the set of 'Home' activity candidates, plus (if any) which of them * is the current "always use this one" setting. diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 5187181..8025b60 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -187,6 +187,39 @@ public class LauncherApps { } /** + * Checks if the package is installed and enabled for a profile. + * + * @param packageName The package to check. + * @param user The UserHandle of the profile. + * + * @return true if the package exists and is enabled. + */ + public boolean isPackageEnabledForProfile(String packageName, UserHandle user) { + try { + return mService.isPackageEnabled(packageName, user); + } catch (RemoteException re) { + return false; + } + } + + /** + * Checks if the activity exists and it enabled for a profile. + * + * @param component The activity to check. + * @param user The UserHandle of the profile. + * + * @return true if the activity exists and is enabled. + */ + public boolean isActivityEnabledForProfile(ComponentName component, UserHandle user) { + try { + return mService.isActivityEnabled(component, user); + } catch (RemoteException re) { + return false; + } + } + + + /** * Adds a listener for changes to packages in current and managed profiles. * * @param listener The listener to add. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 484a2a1..1a003ff 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3507,4 +3507,26 @@ public abstract class PackageManager { return Environment.getDataDirectory().toString() + "/user/" + userId + "/" + packageName; } + + /** + * Adds a forwarding intent filter. After calling this method all intents sent from the user + * with id userIdOrig can also be be resolved by activities in the user with id userIdDest if + * they match the specified intent filter. + * @param filter the {@link IntentFilter} the intent has to match to be forwarded + * @param removable if set to false, {@link clearForwardingIntents} will not remove this intent + * filter + * @param userIdOrig user from which the intent can be forwarded + * @param userIdDest user to which the intent can be forwarded + * @hide + */ + public abstract void addForwardingIntentFilter(IntentFilter filter, boolean removable, + int userIdOrig, int userIdDest); + + /** + * Clearing all removable {@link ForwardingIntentFilter}s that are set with the given user as + * the origin. + * @param userIdOrig user from which the intent can be forwarded + * @hide + */ + public abstract void clearForwardingIntentFilters(int userIdOrig); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 8f19f01..080b37b 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -459,7 +459,7 @@ public class PackageParser { return pi; } - private Certificate[] loadCertificates(StrictJarFile jarFile, ZipEntry je, + private Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je, byte[] readBuffer) { try { // We must read the stream for the JarEntry to retrieve @@ -469,7 +469,7 @@ public class PackageParser { // not using } is.close(); - return je != null ? jarFile.getCertificates(je) : null; + return je != null ? jarFile.getCertificateChains(je) : null; } catch (IOException e) { Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e); } catch (RuntimeException e) { @@ -632,7 +632,7 @@ public class PackageParser { try { StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath); - Certificate[] certs = null; + Certificate[][] certs = null; if ((flags&PARSE_IS_SYSTEM) != 0) { // If this package comes from the system image, then we @@ -656,8 +656,8 @@ public class PackageParser { final int N = certs.length; for (int i=0; i<N; i++) { Slog.i(TAG, " Public key: " - + certs[i].getPublicKey().getEncoded() - + " " + certs[i].getPublicKey()); + + certs[i][0].getPublicKey().getEncoded() + + " " + certs[i][0].getPublicKey()); } } } @@ -677,7 +677,7 @@ public class PackageParser { ManifestDigest.fromInputStream(jarFile.getInputStream(je)); } - final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer); + final Certificate[][] localCerts = loadCertificates(jarFile, je, readBuffer); if (DEBUG_JAR) { Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName() + ": certs=" + certs + " (" @@ -726,8 +726,7 @@ public class PackageParser { final int N = certs.length; pkg.mSignatures = new Signature[certs.length]; for (int i=0; i<N; i++) { - pkg.mSignatures[i] = new Signature( - certs[i].getEncoded()); + pkg.mSignatures[i] = new Signature(certs[i]); } } else { Slog.e(TAG, "Package " + pkg.packageName @@ -739,7 +738,7 @@ public class PackageParser { // Add the signing KeySet to the system pkg.mSigningKeys = new HashSet<PublicKey>(); for (int i=0; i < certs.length; i++) { - pkg.mSigningKeys.add(certs[i].getPublicKey()); + pkg.mSigningKeys.add(certs[i][0].getPublicKey()); } } catch (CertificateEncodingException e) { diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java index 752bf8b..f4e7dc3 100644 --- a/core/java/android/content/pm/Signature.java +++ b/core/java/android/content/pm/Signature.java @@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream; import java.lang.ref.SoftReference; import java.security.PublicKey; import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.Arrays; @@ -38,12 +39,28 @@ public class Signature implements Parcelable { private int mHashCode; private boolean mHaveHashCode; private SoftReference<String> mStringRef; + private Certificate[] mCertificateChain; /** * Create Signature from an existing raw byte array. */ public Signature(byte[] signature) { mSignature = signature.clone(); + mCertificateChain = null; + } + + /** + * Create signature from a certificate chain. Used for backward + * compatibility. + * + * @throws CertificateEncodingException + * @hide + */ + public Signature(Certificate[] certificateChain) throws CertificateEncodingException { + mSignature = certificateChain[0].getEncoded(); + if (certificateChain.length > 1) { + mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length); + } } private static final int parseHexDigit(int nibble) { @@ -156,6 +173,29 @@ public class Signature implements Parcelable { return cert.getPublicKey(); } + /** + * Used for compatibility code that needs to check the certificate chain + * during upgrades. + * + * @throws CertificateEncodingException + * @hide + */ + public Signature[] getChainSignatures() throws CertificateEncodingException { + if (mCertificateChain == null) { + return new Signature[] { this }; + } + + Signature[] chain = new Signature[1 + mCertificateChain.length]; + chain[0] = this; + + int i = 1; + for (Certificate c : mCertificateChain) { + chain[i++] = new Signature(c.getEncoded()); + } + + return chain; + } + @Override public boolean equals(Object obj) { try { diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index f53aa4c..c0383a3 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -27,8 +27,8 @@ import android.os.UserHandle; */ public class UserInfo implements Parcelable { - /** 6 bits for user type */ - public static final int FLAG_MASK_USER_TYPE = 0x0000003F; + /** 8 bits for user type */ + public static final int FLAG_MASK_USER_TYPE = 0x000000FF; /** * *************************** NOTE *************************** @@ -70,6 +70,11 @@ public class UserInfo implements Parcelable { */ public static final int FLAG_MANAGED_PROFILE = 0x00000020; + /** + * Indicates that this user is disabled. + */ + public static final int FLAG_DISABLED = 0x00000040; + public static final int NO_PROFILE_GROUP_ID = -1; @@ -117,6 +122,10 @@ public class UserInfo implements Parcelable { return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE; } + public boolean isEnabled() { + return (flags & FLAG_DISABLED) != FLAG_DISABLED; + } + /** * @return true if this user can be switched to. **/ diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 4879c23..499de17 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2308,8 +2308,8 @@ public class Resources { */ private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) { if (value.string == null) { - throw new NotFoundException( - "Resource is not a Drawable (color or path): " + value); + throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" + + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); } final String file = value.string.toString(); diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 528e119..28309d7 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -372,7 +372,6 @@ public final class CameraCharacteristics extends CameraMetadata { /** * <p>Optional. Hyperfocal distance for this lens.</p> - * <p>If the lens is fixed focus, the camera device will report 0.</p> * <p>If the lens is not fixed focus, the camera device will report this * field when {@link CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION android.lens.info.focusDistanceCalibration} is APPROXIMATE or CALIBRATED.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> @@ -455,22 +454,25 @@ public final class CameraCharacteristics extends CameraMetadata { * <p>The maximum numbers of different types of output streams * that can be configured and used simultaneously by a camera device.</p> * <p>This is a 3 element tuple that contains the max number of output simultaneous - * streams for raw sensor, processed (and uncompressed), and JPEG formats respectively. - * For example, if max raw sensor format output stream number is 1, max YUV streams + * streams for raw sensor, processed (but not stalling), and processed (and stalling) + * formats respectively. For example, assuming that JPEG is typically a processed and + * stalling stream, if max raw sensor format output stream number is 1, max YUV streams * number is 3, and max JPEG stream number is 2, then this tuple should be <code>(1, 3, 2)</code>.</p> * <p>This lists the upper bound of the number of output streams supported by * the camera device. Using more streams simultaneously may require more hardware and * CPU resources that will consume more power. The image format for a output stream can - * be any supported format provided by {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS android.scaler.availableFormats}. The formats - * defined in {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS android.scaler.availableFormats} can be catergorized into the 3 stream types - * as below:</p> + * be any supported format provided by {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations}. + * The formats defined in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations} can be catergorized + * into the 3 stream types as below:</p> * <ul> - * <li>JPEG-compressed format: BLOB.</li> - * <li>Raw formats: RAW_SENSOR and RAW_OPAQUE.</li> - * <li>processed, uncompressed formats: YCbCr_420_888, YCrCb_420_SP, YV12.</li> + * <li>Processed (but stalling): any non-RAW format with a stallDurations > 0. + * Typically JPEG format (ImageFormat#JPEG).</li> + * <li>Raw formats: ImageFormat#RAW_SENSOR and ImageFormat#RAW_OPAQUE.</li> + * <li>Processed (but not-stalling): any non-RAW format without a stall duration. + * Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li> * </ul> * - * @see CameraCharacteristics#SCALER_AVAILABLE_FORMATS + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS */ public static final Key<int[]> REQUEST_MAX_NUM_OUTPUT_STREAMS = new Key<int[]>("android.request.maxNumOutputStreams", int[].class); diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index bb290af..9d0e0e1 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -192,8 +192,9 @@ public interface CameraDevice extends AutoCloseable { * * <p>The camera device will query each Surface's size and formats upon this * call, so they must be set to a valid setting at this time (in particular: - * if the format is user-visible, it must be one of android.scaler.availableFormats; - * and the size must be one of android.scaler.available[Processed|Jpeg]Sizes).</p> + * if the format is user-visible, it must be one of + * {@link StreamConfigurationMap#getOutputFormats}; and the size must be one of + * {@link StreamConfigurationMap#getOutputSizes(int)}).</p> * * <p>When this method is called with valid Surfaces, the device will transition to the {@link * StateListener#onBusy busy state}. Once configuration is complete, the device will transition @@ -239,6 +240,9 @@ public interface CameraDevice extends AutoCloseable { * @see StateListener#onUnconfigured * @see #stopRepeating * @see #flush + * @see StreamConfigurationMap#getOutputFormats() + * @see StreamConfigurationMap#getOutputSizes(int) + * @see StreamConfigurationMap#getOutputSizes(Class) */ public void configureOutputs(List<Surface> outputs) throws CameraAccessException; diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index ba8db3a..6e38a22 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1295,6 +1295,20 @@ public abstract class CameraMetadata { public static final int CONTROL_SCENE_MODE_BARCODE = 16; // + // Enumeration values for CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE + // + + /** + * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE + */ + public static final int CONTROL_VIDEO_STABILIZATION_MODE_OFF = 0; + + /** + * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE + */ + public static final int CONTROL_VIDEO_STABILIZATION_MODE_ON = 1; + + // // Enumeration values for CaptureRequest#EDGE_MODE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index c4e342c..f161f3a 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -831,9 +831,11 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * stabilized</p> * * @see CaptureRequest#SCALER_CROP_REGION + * @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF + * @see #CONTROL_VIDEO_STABILIZATION_MODE_ON */ - public static final Key<Boolean> CONTROL_VIDEO_STABILIZATION_MODE = - new Key<Boolean>("android.control.videoStabilizationMode", boolean.class); + public static final Key<Integer> CONTROL_VIDEO_STABILIZATION_MODE = + new Key<Integer>("android.control.videoStabilizationMode", int.class); /** * <p>Operation mode for edge diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index d8981c8..1d2d0e9 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -124,6 +124,58 @@ public final class CaptureResult extends CameraMetadata { /** + * <p>The mode control selects how the image data is converted from the + * sensor's native color into linear sRGB color.</p> + * <p>When auto-white balance is enabled with {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, this + * control is overridden by the AWB routine. When AWB is disabled, the + * application controls how the color mapping is performed.</p> + * <p>We define the expected processing pipeline below. For consistency + * across devices, this is always the case with TRANSFORM_MATRIX.</p> + * <p>When either FULL or HIGH_QUALITY is used, the camera device may + * do additional processing but {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and + * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} will still be provided by the + * camera device (in the results) and be roughly correct.</p> + * <p>Switching to TRANSFORM_MATRIX and using the data provided from + * FAST or HIGH_QUALITY will yield a picture with the same white point + * as what was produced by the camera device in the earlier frame.</p> + * <p>The expected processing pipeline is as follows:</p> + * <p><img alt="White balance processing pipeline" src="../../../../images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p> + * <p>The white balance is encoded by two values, a 4-channel white-balance + * gain vector (applied in the Bayer domain), and a 3x3 color transform + * matrix (applied after demosaic).</p> + * <p>The 4-channel white-balance gains are defined as:</p> + * <pre><code>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} = [ R G_even G_odd B ] + * </code></pre> + * <p>where <code>G_even</code> is the gain for green pixels on even rows of the + * output, and <code>G_odd</code> is the gain for green pixels on the odd rows. + * These may be identical for a given camera device implementation; if + * the camera device does not support a separate gain for even/odd green + * channels, it will use the <code>G_even</code> value, and write <code>G_odd</code> equal to + * <code>G_even</code> in the output result metadata.</p> + * <p>The matrices for color transforms are defined as a 9-entry vector:</p> + * <pre><code>{@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} = [ I0 I1 I2 I3 I4 I5 I6 I7 I8 ] + * </code></pre> + * <p>which define a transform from input sensor colors, <code>P_in = [ r g b ]</code>, + * to output linear sRGB, <code>P_out = [ r' g' b' ]</code>,</p> + * <p>with colors as follows:</p> + * <pre><code>r' = I0r + I1g + I2b + * g' = I3r + I4g + I5b + * b' = I6r + I7g + I8b + * </code></pre> + * <p>Both the input and output value ranges must match. Overflow/underflow + * values are clipped to fit within the range.</p> + * + * @see CaptureRequest#COLOR_CORRECTION_GAINS + * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CaptureRequest#CONTROL_AWB_MODE + * @see #COLOR_CORRECTION_MODE_TRANSFORM_MATRIX + * @see #COLOR_CORRECTION_MODE_FAST + * @see #COLOR_CORRECTION_MODE_HIGH_QUALITY + */ + public static final Key<Integer> COLOR_CORRECTION_MODE = + new Key<Integer>("android.colorCorrection.mode", int.class); + + /** * <p>A color transform matrix to use to transform * from sensor RGB color space to output linear sRGB color space</p> * <p>This matrix is either set by the camera device when the request @@ -176,6 +228,82 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.aePrecaptureId", int.class); /** + * <p>The desired setting for the camera device's auto-exposure + * algorithm's antibanding compensation.</p> + * <p>Some kinds of lighting fixtures, such as some fluorescent + * lights, flicker at the rate of the power supply frequency + * (60Hz or 50Hz, depending on country). While this is + * typically not noticeable to a person, it can be visible to + * a camera device. If a camera sets its exposure time to the + * wrong value, the flicker may become visible in the + * viewfinder as flicker or in a final captured image, as a + * set of variable-brightness bands across the image.</p> + * <p>Therefore, the auto-exposure routines of camera devices + * include antibanding routines that ensure that the chosen + * exposure value will not cause such banding. The choice of + * exposure time depends on the rate of flicker, which the + * camera device can detect automatically, or the expected + * rate can be selected by the application using this + * control.</p> + * <p>A given camera device may not support all of the possible + * options for the antibanding mode. The + * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_ANTIBANDING_MODES android.control.aeAvailableAntibandingModes} key contains + * the available modes for a given camera device.</p> + * <p>The default mode is AUTO, which must be supported by all + * camera devices.</p> + * <p>If manual exposure control is enabled (by setting + * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} to OFF), + * then this setting has no effect, and the application must + * ensure it selects exposure times that do not cause banding + * issues. The {@link CaptureResult#STATISTICS_SCENE_FLICKER android.statistics.sceneFlicker} key can assist + * the application in this.</p> + * + * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_ANTIBANDING_MODES + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_MODE + * @see CaptureResult#STATISTICS_SCENE_FLICKER + * @see #CONTROL_AE_ANTIBANDING_MODE_OFF + * @see #CONTROL_AE_ANTIBANDING_MODE_50HZ + * @see #CONTROL_AE_ANTIBANDING_MODE_60HZ + * @see #CONTROL_AE_ANTIBANDING_MODE_AUTO + */ + public static final Key<Integer> CONTROL_AE_ANTIBANDING_MODE = + new Key<Integer>("android.control.aeAntibandingMode", int.class); + + /** + * <p>Adjustment to AE target image + * brightness</p> + * <p>For example, if EV step is 0.333, '6' will mean an + * exposure compensation of +2 EV; -3 will mean an exposure + * compensation of -1</p> + */ + public static final Key<Integer> CONTROL_AE_EXPOSURE_COMPENSATION = + new Key<Integer>("android.control.aeExposureCompensation", int.class); + + /** + * <p>Whether AE is currently locked to its latest + * calculated values.</p> + * <p>Note that even when AE is locked, the flash may be + * fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_AUTO_FLASH / ON_ALWAYS_FLASH / + * ON_AUTO_FLASH_REDEYE.</p> + * <p>If AE precapture is triggered (see {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}) + * when AE is already locked, the camera device will not change the exposure time + * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}) and sensitivity ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}) + * parameters. The flash may be fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} + * is ON_AUTO_FLASH/ON_AUTO_FLASH_REDEYE and the scene is too dark. If the + * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.</p> + * <p>See {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE lock related state transition details.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CaptureResult#CONTROL_AE_STATE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_SENSITIVITY + */ + public static final Key<Boolean> CONTROL_AE_LOCK = + new Key<Boolean>("android.control.aeLock", boolean.class); + + /** * <p>The desired mode for the camera device's * auto-exposure routine.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is @@ -237,6 +365,35 @@ public final class CaptureResult extends CameraMetadata { new Key<int[]>("android.control.aeRegions", int[].class); /** + * <p>Range over which fps can be adjusted to + * maintain exposure</p> + * <p>Only constrains AE algorithm, not manual control + * of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</p> + * + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + */ + public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE = + new Key<int[]>("android.control.aeTargetFpsRange", int[].class); + + /** + * <p>Whether the camera device will trigger a precapture + * metering sequence when it processes this request.</p> + * <p>This entry is normally set to IDLE, or is not + * included at all in the request settings. When included and + * set to START, the camera device will trigger the autoexposure + * precapture metering sequence.</p> + * <p>The effect of AE precapture trigger depends on the current + * AE mode and state; see {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE precapture + * state transition details.</p> + * + * @see CaptureResult#CONTROL_AE_STATE + * @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE + * @see #CONTROL_AE_PRECAPTURE_TRIGGER_START + */ + public static final Key<Integer> CONTROL_AE_PRECAPTURE_TRIGGER = + new Key<Integer>("android.control.aePrecaptureTrigger", int.class); + + /** * <p>Current state of AE algorithm</p> * <p>Switching between or enabling AE modes ({@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}) always * resets the AE state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode}, @@ -481,6 +638,24 @@ public final class CaptureResult extends CameraMetadata { new Key<int[]>("android.control.afRegions", int[].class); /** + * <p>Whether the camera device will trigger autofocus for this request.</p> + * <p>This entry is normally set to IDLE, or is not + * included at all in the request settings.</p> + * <p>When included and set to START, the camera device will trigger the + * autofocus algorithm. If autofocus is disabled, this trigger has no effect.</p> + * <p>When set to CANCEL, the camera device will cancel any active trigger, + * and return to its initial AF state.</p> + * <p>See {@link CaptureResult#CONTROL_AF_STATE android.control.afState} for what that means for each AF mode.</p> + * + * @see CaptureResult#CONTROL_AF_STATE + * @see #CONTROL_AF_TRIGGER_IDLE + * @see #CONTROL_AF_TRIGGER_START + * @see #CONTROL_AF_TRIGGER_CANCEL + */ + public static final Key<Integer> CONTROL_AF_TRIGGER = + new Key<Integer>("android.control.afTrigger", int.class); + + /** * <p>Current state of AF algorithm.</p> * <p>Switching between or enabling AF modes ({@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}) always * resets the AF state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode}, @@ -889,6 +1064,16 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.afTriggerId", int.class); /** + * <p>Whether AWB is currently locked to its + * latest calculated values.</p> + * <p>Note that AWB lock is only meaningful for AUTO + * mode; in other modes, AWB is already fixed to a specific + * setting.</p> + */ + public static final Key<Boolean> CONTROL_AWB_LOCK = + new Key<Boolean>("android.control.awbLock", boolean.class); + + /** * <p>Whether AWB is currently setting the color * transform fields, and what its illumination target * is.</p> @@ -948,6 +1133,30 @@ public final class CaptureResult extends CameraMetadata { new Key<int[]>("android.control.awbRegions", int[].class); /** + * <p>Information to the camera device 3A (auto-exposure, + * auto-focus, auto-white balance) routines about the purpose + * of this capture, to help the camera device to decide optimal 3A + * strategy.</p> + * <p>This control (except for MANUAL) is only effective if + * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p> + * <p>ZERO_SHUTTER_LAG must be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} + * contains ZSL. MANUAL must be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} + * contains MANUAL_SENSOR.</p> + * + * @see CaptureRequest#CONTROL_MODE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see #CONTROL_CAPTURE_INTENT_CUSTOM + * @see #CONTROL_CAPTURE_INTENT_PREVIEW + * @see #CONTROL_CAPTURE_INTENT_STILL_CAPTURE + * @see #CONTROL_CAPTURE_INTENT_VIDEO_RECORD + * @see #CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT + * @see #CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG + * @see #CONTROL_CAPTURE_INTENT_MANUAL + */ + public static final Key<Integer> CONTROL_CAPTURE_INTENT = + new Key<Integer>("android.control.captureIntent", int.class); + + /** * <p>Current state of AWB algorithm</p> * <p>Switching between or enabling AWB modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}) always * resets the AWB state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode}, @@ -1078,6 +1287,31 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.awbState", int.class); /** + * <p>A special color effect to apply.</p> + * <p>When this mode is set, a color effect will be applied + * to images produced by the camera device. The interpretation + * and implementation of these color effects is left to the + * implementor of the camera device, and should not be + * depended on to be consistent (or present) across all + * devices.</p> + * <p>A color effect will only be applied if + * {@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF.</p> + * + * @see CaptureRequest#CONTROL_MODE + * @see #CONTROL_EFFECT_MODE_OFF + * @see #CONTROL_EFFECT_MODE_MONO + * @see #CONTROL_EFFECT_MODE_NEGATIVE + * @see #CONTROL_EFFECT_MODE_SOLARIZE + * @see #CONTROL_EFFECT_MODE_SEPIA + * @see #CONTROL_EFFECT_MODE_POSTERIZE + * @see #CONTROL_EFFECT_MODE_WHITEBOARD + * @see #CONTROL_EFFECT_MODE_BLACKBOARD + * @see #CONTROL_EFFECT_MODE_AQUA + */ + public static final Key<Integer> CONTROL_EFFECT_MODE = + new Key<Integer>("android.control.effectMode", int.class); + + /** * <p>Overall mode of 3A control * routines.</p> * <p>High-level 3A control. When set to OFF, all 3A control @@ -1106,6 +1340,57 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.control.mode", int.class); /** + * <p>A camera mode optimized for conditions typical in a particular + * capture setting.</p> + * <p>This is the mode that that is active when + * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code>. Aside from FACE_PRIORITY, + * these modes will disable {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}, + * {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, and {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} while in use.</p> + * <p>The interpretation and implementation of these scene modes is left + * to the implementor of the camera device. Their behavior will not be + * consistent across all devices, and any given device may only implement + * a subset of these modes.</p> + * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AF_MODE + * @see CaptureRequest#CONTROL_AWB_MODE + * @see CaptureRequest#CONTROL_MODE + * @see #CONTROL_SCENE_MODE_DISABLED + * @see #CONTROL_SCENE_MODE_FACE_PRIORITY + * @see #CONTROL_SCENE_MODE_ACTION + * @see #CONTROL_SCENE_MODE_PORTRAIT + * @see #CONTROL_SCENE_MODE_LANDSCAPE + * @see #CONTROL_SCENE_MODE_NIGHT + * @see #CONTROL_SCENE_MODE_NIGHT_PORTRAIT + * @see #CONTROL_SCENE_MODE_THEATRE + * @see #CONTROL_SCENE_MODE_BEACH + * @see #CONTROL_SCENE_MODE_SNOW + * @see #CONTROL_SCENE_MODE_SUNSET + * @see #CONTROL_SCENE_MODE_STEADYPHOTO + * @see #CONTROL_SCENE_MODE_FIREWORKS + * @see #CONTROL_SCENE_MODE_SPORTS + * @see #CONTROL_SCENE_MODE_PARTY + * @see #CONTROL_SCENE_MODE_CANDLELIGHT + * @see #CONTROL_SCENE_MODE_BARCODE + */ + public static final Key<Integer> CONTROL_SCENE_MODE = + new Key<Integer>("android.control.sceneMode", int.class); + + /** + * <p>Whether video stabilization is + * active</p> + * <p>If enabled, video stabilization can modify the + * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream + * stabilized</p> + * + * @see CaptureRequest#SCALER_CROP_REGION + * @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF + * @see #CONTROL_VIDEO_STABILIZATION_MODE_ON + */ + public static final Key<Integer> CONTROL_VIDEO_STABILIZATION_MODE = + new Key<Integer>("android.control.videoStabilizationMode", int.class); + + /** * <p>Operation mode for edge * enhancement.</p> * <p>Edge/sharpness/detail enhancement. OFF means no @@ -1688,6 +1973,22 @@ public final class CaptureResult extends CameraMetadata { new Key<Float>("android.sensor.greenSplit", float.class); /** + * <p>A pixel <code>[R, G_even, G_odd, B]</code> that supplies the test pattern + * when {@link CaptureRequest#SENSOR_TEST_PATTERN_MODE android.sensor.testPatternMode} is SOLID_COLOR.</p> + * <p>Each color channel is treated as an unsigned 32-bit integer. + * The camera device then uses the most significant X bits + * that correspond to how many bits are in its Bayer raw sensor + * output.</p> + * <p>For example, a sensor with RAW10 Bayer output would use the + * 10 most significant bits from each color channel.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE + */ + public static final Key<int[]> SENSOR_TEST_PATTERN_DATA = + new Key<int[]>("android.sensor.testPatternData", int[].class); + + /** * <p>When enabled, the sensor sends a test pattern instead of * doing a real exposure from the camera.</p> * <p>When a test pattern is enabled, all manual sensor controls specified @@ -1936,6 +2237,20 @@ public final class CaptureResult extends CameraMetadata { new Key<int[]>("android.statistics.hotPixelMap", int[].class); /** + * <p>Whether the camera device will output the lens + * shading map in output result metadata.</p> + * <p>When set to ON, + * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in + * the output result metadata.</p> + * + * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF + * @see #STATISTICS_LENS_SHADING_MAP_MODE_ON + */ + public static final Key<Integer> STATISTICS_LENS_SHADING_MAP_MODE = + new Key<Integer>("android.statistics.lensShadingMapMode", int.class); + + /** * <p>Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> diff --git a/core/java/android/hardware/camera2/ColorSpaceTransform.java b/core/java/android/hardware/camera2/ColorSpaceTransform.java new file mode 100644 index 0000000..9912e4b --- /dev/null +++ b/core/java/android/hardware/camera2/ColorSpaceTransform.java @@ -0,0 +1,239 @@ +/* + * 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.hardware.camera2; + +import static com.android.internal.util.Preconditions.*; +import android.hardware.camera2.impl.HashCodeHelpers; + +import java.util.Arrays; + +/** + * Immutable class for describing a 3x3 matrix of {@link Rational} values in row-major order. + * + * <p>This matrix maps a transform from one color space to another. For the particular color space + * source and target, see the appropriate camera metadata documentation for the key that provides + * this value.</p> + * + * @see CameraMetadata + */ +public final class ColorSpaceTransform { + + /** The number of rows in this matrix. */ + private static final int ROWS = 3; + + /** The number of columns in this matrix. */ + private static final int COLUMNS = 3; + + /** The number of total Rational elements in this matrix. */ + private static final int COUNT = ROWS * COLUMNS; + + /** Number of int elements in a rational. */ + private static final int RATIONAL_SIZE = 2; + + /** Numerator offset inside a rational (pair). */ + private static final int OFFSET_NUMERATOR = 0; + + /** Denominator offset inside a rational (pair). */ + private static final int OFFSET_DENOMINATOR = 1; + + /** Number of int elements in this matrix. */ + private static final int COUNT_INT = ROWS * COLUMNS * RATIONAL_SIZE; + + /** + * Create a new immutable {@link ColorSpaceTransform} instance from a {@link Rational} array. + * + * <p>The elements must be stored in a row-major order.</p> + * + * @param elements An array of {@code 9} elements + * + * @throws IllegalArgumentException + * if the count of {@code elements} is not {@code 9} + * @throws NullPointerException + * if {@code elements} or any sub-element is {@code null} + */ + public ColorSpaceTransform(Rational[] elements) { + + checkNotNull(elements, "elements must not be null"); + if (elements.length != COUNT) { + throw new IllegalArgumentException("elements must be " + COUNT + " length"); + } + + mElements = new int[COUNT_INT]; + + for (int i = 0; i < elements.length; ++i) { + checkNotNull(elements, "element[" + i + "] must not be null"); + mElements[i * RATIONAL_SIZE + OFFSET_NUMERATOR] = elements[i].getNumerator(); + mElements[i * RATIONAL_SIZE + OFFSET_DENOMINATOR] = elements[i].getDenominator(); + } + } + + /** + * Create a new immutable {@link ColorSpaceTransform} instance from an {@code int} array. + * + * <p>The elements must be stored in a row-major order. Each rational is stored + * contiguously as a {@code (numerator, denominator)} pair.</p> + * + * <p>In particular:<pre>{@code + * int[] elements = new int[ + * N11, D11, N12, D12, N13, D13, + * N21, D21, N22, D22, N23, D23, + * N31, D31, N32, D32, N33, D33 + * ]; + * + * new ColorSpaceTransform(elements)}</pre> + * + * where {@code Nij} and {@code Dij} is the numerator and denominator for row {@code i} and + * column {@code j}.</p> + * + * @param elements An array of {@code 18} elements + * + * @throws IllegalArgumentException + * if the count of {@code elements} is not {@code 18} + * @throws NullPointerException + * if {@code elements} is {@code null} + */ + public ColorSpaceTransform(int[] elements) { + checkNotNull(elements, "elements must not be null"); + if (elements.length != COUNT_INT) { + throw new IllegalArgumentException("elements must be " + COUNT_INT + " length"); + } + + for (int i = 0; i < elements.length; ++i) { + checkNotNull(elements, "element " + i + " must not be null"); + } + + mElements = Arrays.copyOf(elements, elements.length); + } + + /** + * Get an element of this matrix by its row and column. + * + * <p>The rows must be within the range [0, 3), + * and the column must be within the range [0, 3).</p> + * + * @return element (non-{@code null}) + * + * @throws IllegalArgumentException if column or row was out of range + */ + public Rational getElement(int column, int row) { + if (column < 0 || column >= COLUMNS) { + throw new IllegalArgumentException("column out of range"); + } else if (row < 0 || row >= ROWS) { + throw new IllegalArgumentException("row out of range"); + } + + int numerator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_NUMERATOR]; + int denominator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_DENOMINATOR]; + + return new Rational(numerator, denominator); + } + + /** + * Copy the {@link Rational} elements in row-major order from this matrix into the destination. + * + * @param destination + * an array big enough to hold at least {@code 9} elements after the + * {@code offset} + * @param offset + * a non-negative offset into the array + * @throws NullPointerException + * If {@code destination} was {@code null} + * @throws ArrayIndexOutOfBoundsException + * If there's not enough room to write the elements at the specified destination and + * offset. + */ + public void copyElements(Rational[] destination, int offset) { + checkArgumentNonnegative(offset, "offset must not be negative"); + checkNotNull(destination, "destination must not be null"); + if (destination.length + offset < COUNT) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + + for (int i = 0, j = 0; i < COUNT; ++i, j += RATIONAL_SIZE) { + int numerator = mElements[j + OFFSET_NUMERATOR]; + int denominator = mElements[j + OFFSET_DENOMINATOR]; + + destination[i + offset] = new Rational(numerator, denominator); + } + } + + /** + * Copy the {@link Rational} elements in row-major order from this matrix into the destination. + * + * <p>Each element is stored as a contiguous rational packed as a + * {@code (numerator, denominator)} pair of ints, identical to the + * {@link ColorSpaceTransform#ColorSpaceTransform(int[]) constructor}.</p> + * + * @param destination + * an array big enough to hold at least {@code 18} elements after the + * {@code offset} + * @param offset + * a non-negative offset into the array + * @throws NullPointerException + * If {@code destination} was {@code null} + * @throws ArrayIndexOutOfBoundsException + * If there's not enough room to write the elements at the specified destination and + * offset. + * + * @see ColorSpaceTransform#ColorSpaceTransform(int[]) + */ + public void copyElements(int[] destination, int offset) { + checkArgumentNonnegative(offset, "offset must not be negative"); + checkNotNull(destination, "destination must not be null"); + if (destination.length + offset < COUNT_INT) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + + // Manual copy faster than System#arraycopy for very small loops + for (int i = 0; i < COUNT_INT; ++i) { + destination[i + offset] = mElements[i]; + } + } + + /** + * Check if this {@link ColorSpaceTransform} is equal to another {@link ColorSpaceTransform}. + * + * <p>Two color space transforms are equal if and only if all of their elements are + * {@link Object#equals equal}.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof ColorSpaceTransform) { + final ColorSpaceTransform other = (ColorSpaceTransform) obj; + return Arrays.equals(mElements, other.mElements); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mElements); + } + + private final int[] mElements; +}; diff --git a/core/java/android/hardware/camera2/LensShadingMap.java b/core/java/android/hardware/camera2/LensShadingMap.java new file mode 100644 index 0000000..2c224f6 --- /dev/null +++ b/core/java/android/hardware/camera2/LensShadingMap.java @@ -0,0 +1,243 @@ +/* + * 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.hardware.camera2; + +import static com.android.internal.util.Preconditions.*; +import static android.hardware.camera2.RggbChannelVector.*; + +import android.hardware.camera2.impl.HashCodeHelpers; + +import java.util.Arrays; + +/** + * Immutable class for describing a {@code 4 x N x M} lens shading map of floats. + * + * @see CameraCharacteristics#LENS_SHADING_MAP + */ +public final class LensShadingMap { + + /** + * The smallest gain factor in this map. + * + * <p>All values in this map will be at least this large.</p> + */ + public static final float MINIMUM_GAIN_FACTOR = 1.0f; + + /** + * Create a new immutable LensShadingMap instance. + * + * <p>The elements must be stored in a row-major order (fully packed).</p> + * + * <p>This constructor takes over the array; do not write to the array afterwards.</p> + * + * @param elements + * An array of elements whose length is + * {@code RggbChannelVector.COUNT * rows * columns} + * + * @throws IllegalArgumentException + * if the {@code elements} array length is invalid, + * if any of the subelems are not finite or less than {@value #MINIMUM_GAIN_FACTOR}, + * or if rows or columns is not positive + * @throws NullPointerException + * if {@code elements} is {@code null} + * + * @hide + */ + public LensShadingMap(final float[] elements, final int rows, final int columns) { + + mRows = checkArgumentPositive(rows, "rows must be positive"); + mColumns = checkArgumentPositive(rows, "columns must be positive"); + mElements = checkNotNull(elements, "elements must not be null"); + + if (elements.length != getGainFactorCount()) { + throw new IllegalArgumentException("elements must be " + getGainFactorCount() + + " length"); + } + + // Every element must be finite and >= 1.0f + checkArrayElementsInRange(elements, MINIMUM_GAIN_FACTOR, Float.MAX_VALUE, "elements"); + } + + /** + * Get the number of rows in this map. + */ + public int getRowCount() { + return mRows; + } + + /** + * Get the number of columns in this map. + */ + public int getColumnCount() { + return mColumns; + } + + /** + * Get the total number of gain factors in this map. + * + * <p>A single gain factor contains exactly one color channel. + * Use with {@link #copyGainFactors} to allocate a large-enough array.</p> + */ + public int getGainFactorCount() { + return mRows * mColumns * COUNT; + } + + /** + * Get a single color channel gain factor from this lens shading map by its row and column. + * + * <p>The rows must be within the range [0, {@link #getRowCount}), + * the column must be within the range [0, {@link #getColumnCount}), + * and the color channel must be within the range [0, {@value RggbChannelVector#COUNT}).</p> + * + * <p>The channel order is {@code [R, Geven, Godd, B]}, where + * {@code Geven} is the green channel for the even rows of a Bayer pattern, and + * {@code Godd} is the odd rows. + * </p> + * + * @param colorChannel color channel from {@code [R, Geven, Godd, B]} + * @param column within the range [0, {@link #getColumnCount}) + * @param row within the range [0, {@link #getRowCount}) + * + * @return a gain factor >= {@value #MINIMUM_GAIN_FACTOR} + * + * @throws IllegalArgumentException if any of the parameters was out of range + * + * @see #RED + * @see #GREEN_EVEN + * @see #GREEN_ODD + * @see #BLUE + * @see #getRowCount + * @see #getColumnCount + */ + public float getGainFactor(final int colorChannel, final int column, final int row) { + if (colorChannel < 0 || colorChannel > COUNT) { + throw new IllegalArgumentException("colorChannel out of range"); + } else if (column < 0 || column >= mColumns) { + throw new IllegalArgumentException("column out of range"); + } else if (row < 0 || row >= mRows) { + throw new IllegalArgumentException("row out of range"); + } + + return mElements[colorChannel + (row * mColumns + column) * COUNT ]; + } + + /** + * Get a gain factor vector from this lens shading map by its row and column. + * + * <p>The rows must be within the range [0, {@link #getRowCount}), + * the column must be within the range [0, {@link #getColumnCount}).</p> + * + * @param column within the range [0, {@link #getColumnCount}) + * @param row within the range [0, {@link #getRowCount}) + * + * @return an {@link RggbChannelVector} where each gain factor >= {@value #MINIMUM_GAIN_FACTOR} + * + * @throws IllegalArgumentException if any of the parameters was out of range + * + * @see #getRowCount + * @see #getColumnCount + */ + public RggbChannelVector getGainFactorVector(final int column, final int row) { + if (column < 0 || column >= mColumns) { + throw new IllegalArgumentException("column out of range"); + } else if (row < 0 || row >= mRows) { + throw new IllegalArgumentException("row out of range"); + } + + final int offset = (row * mColumns + column) * COUNT; + + final float red = + mElements[RED + offset]; + final float greenEven = + mElements[GREEN_EVEN + offset]; + final float greenOdd = + mElements[GREEN_ODD + offset]; + final float blue = + mElements[BLUE + offset]; + + return new RggbChannelVector(red, greenEven, greenOdd, blue); + } + + /** + * Copy all gain factors in row-major order from this lens shading map into the destination. + * + * <p>Each gain factor will be >= {@link #MINIMUM_GAIN_FACTOR}.</p> + * + * @param destination + * an array big enough to hold at least {@link RggbChannelVector#COUNT} + * elements after the {@code offset} + * @param offset + * a non-negative offset into the array + * @throws NullPointerException + * If {@code destination} was {@code null} + * @throws IllegalArgumentException + * If offset was negative + * @throws ArrayIndexOutOfBoundsException + * If there's not enough room to write the elements at the specified destination and + * offset. + * + * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + */ + public void copyGainFactors(final float[] destination, final int offset) { + checkArgumentNonnegative(offset, "offset must not be negative"); + checkNotNull(destination, "destination must not be null"); + if (destination.length + offset < getGainFactorCount()) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + + System.arraycopy(mElements, /*srcPos*/0, destination, offset, getGainFactorCount()); + } + + /** + * Check if this LensShadingMap is equal to another LensShadingMap. + * + * <p>Two lens shading maps are equal if and only if they have the same rows/columns, + * and all of their elements are {@link Object#equals equal}.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof LensShadingMap) { + final LensShadingMap other = (LensShadingMap) obj; + return mRows == other.mRows + && mColumns == other.mColumns + && Arrays.equals(mElements, other.mElements); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int elemsHash = HashCodeHelpers.hashCode(mElements); + return HashCodeHelpers.hashCode(mRows, mColumns, elemsHash); + } + + + private final int mRows; + private final int mColumns; + private final float[] mElements; +}; diff --git a/core/java/android/hardware/camera2/MeteringRectangle.java b/core/java/android/hardware/camera2/MeteringRectangle.java new file mode 100644 index 0000000..ff7a745 --- /dev/null +++ b/core/java/android/hardware/camera2/MeteringRectangle.java @@ -0,0 +1,224 @@ +/* + * 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.hardware.camera2; + +import android.util.Size; +import static com.android.internal.util.Preconditions.*; + +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.camera2.impl.HashCodeHelpers; + +/** + * An immutable class to represent a rectangle {@code (x,y, width, height)} with an + * additional weight component. + * + * </p>The rectangle is defined to be inclusive of the specified coordinates.</p> + * + * <p>When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel + * array, with {@code (0,0)} being the top-left pixel in the + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE active pixel array}, and + * {@code (android.sensor.info.activeArraySize.width - 1, + * android.sensor.info.activeArraySize.height - 1)} + * being the bottom-right pixel in the active pixel array. + * </p> + * + * <p>The metering weight is nonnegative.</p> + */ +public final class MeteringRectangle { + + private final int mX; + private final int mY; + private final int mWidth; + private final int mHeight; + private final int mWeight; + + /** + * Create a new metering rectangle. + * + * @param x coordinate >= 0 + * @param y coordinate >= 0 + * @param width width >= 0 + * @param height height >= 0 + * @param meteringWeight weight >= 0 + * + * @throws IllegalArgumentException if any of the parameters were non-negative + */ + public MeteringRectangle(int x, int y, int width, int height, int meteringWeight) { + mX = checkArgumentNonnegative(x, "x must be nonnegative"); + mY = checkArgumentNonnegative(y, "y must be nonnegative"); + mWidth = checkArgumentNonnegative(width, "width must be nonnegative"); + mHeight = checkArgumentNonnegative(height, "height must be nonnegative"); + mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative"); + } + + /** + * Create a new metering rectangle. + * + * @param xy a non-{@code null} {@link Point} with both x,y >= 0 + * @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0 + * @param meteringWeight weight >= 0 + * + * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws NullPointerException if any of the arguments were null + */ + public MeteringRectangle(Point xy, Size dimensions, int meteringWeight) { + checkNotNull(xy, "xy must not be null"); + checkNotNull(dimensions, "dimensions must not be null"); + + mX = checkArgumentNonnegative(xy.x, "x must be nonnegative"); + mY = checkArgumentNonnegative(xy.y, "y must be nonnegative"); + mWidth = checkArgumentNonnegative(dimensions.getWidth(), "width must be nonnegative"); + mHeight = checkArgumentNonnegative(dimensions.getHeight(), "height must be nonnegative"); + mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative"); + } + + /** + * Create a new metering rectangle. + * + * @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0 + * @param meteringWeight weight >= 0 + * + * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws NullPointerException if any of the arguments were null + */ + public MeteringRectangle(Rect rect, int meteringWeight) { + checkNotNull(rect, "rect must not be null"); + + mX = checkArgumentNonnegative(rect.left, "rect.left must be nonnegative"); + mY = checkArgumentNonnegative(rect.top, "rect.top must be nonnegative"); + mWidth = checkArgumentNonnegative(rect.width(), "rect.width must be nonnegative"); + mHeight = checkArgumentNonnegative(rect.height(), "rect.height must be nonnegative"); + mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative"); + } + + /** + * Return the X coordinate of the left side of the rectangle. + * + * @return x coordinate >= 0 + */ + public int getX() { + return mX; + } + + /** + * Return the Y coordinate of the upper side of the rectangle. + * + * @return y coordinate >= 0 + */ + public int getY() { + return mY; + } + + /** + * Return the width of the rectangle. + * + * @return width >= 0 + */ + public int getWidth() { + return mWidth; + } + + /** + * Return the height of the rectangle. + * + * @return height >= 0 + */ + public int getHeight() { + return mHeight; + } + + /** + * Return the metering weight of the rectangle. + * + * @return weight >= 0 + */ + public int getMeteringWeight() { + return mWeight; + } + + /** + * Convenience method to create the upper-left (X,Y) coordinate as a {@link Point}. + * + * @return {@code (x,y)} point with both x,y >= 0 + */ + public Point getUpperLeftPoint() { + return new Point(mX, mY); + } + + /** + * Convenience method to create the size from this metering rectangle. + * + * <p>This strips away the X,Y,weight from the rectangle.</p> + * + * @return a Size with non-negative width and height + */ + public Size getSize() { + return new Size(mWidth, mHeight); + } + + /** + * Convenience method to create a {@link Rect} from this metering rectangle. + * + * <p>This strips away the weight from the rectangle.</p> + * + * @return a {@link Rect} with non-negative x1, y1, x2, y2 + */ + public Rect getRect() { + return new Rect(mX, mY, mX + mWidth, mY + mHeight); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object other) { + if (other instanceof MeteringRectangle) { + return equals(other); + } + return false; + } + + /** + * Compare two metering rectangles to see if they are equal. + * + * Two weighted rectangles are only considered equal if each of their components + * (x, y, width, height, weight) is respectively equal. + * + * @param other Another MeteringRectangle + * + * @return {@code true} if the metering rectangles are equal, {@code false} otherwise + */ + public boolean equals(final MeteringRectangle other) { + if (other == null) { + return false; + } + + return (mX == other.mX + && mY == other.mY + && mWidth == other.mWidth + && mHeight == other.mHeight + && mWidth == other.mWidth); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mX, mY, mWidth, mHeight, mWeight); + } +} diff --git a/core/java/android/hardware/camera2/ReprocessFormatsMap.java b/core/java/android/hardware/camera2/ReprocessFormatsMap.java new file mode 100644 index 0000000..c6c59d4 --- /dev/null +++ b/core/java/android/hardware/camera2/ReprocessFormatsMap.java @@ -0,0 +1,258 @@ +/* + * 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.hardware.camera2; + +import static com.android.internal.util.Preconditions.*; + +import android.hardware.camera2.impl.HashCodeHelpers; + +import java.util.Arrays; + +/** + * Immutable class to store the input to output formats + * {@link CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP map} to be used for with + * camera image reprocessing. + * + * <p> + * The mapping of image formats that are supported by this camera device for input streams, + * to their corresponding output formats.</p> + * + * <p> + * Attempting to configure an input stream with output streams not listed as available in this map + * is not valid. + * </p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS + * + * <!-- hide this until we expose input streams through public API --> + * @hide + */ +public final class ReprocessFormatsMap { + /** + * Create a new {@link ReprocessFormatsMap} + * + * <p>This value is encoded as a variable-size array-of-arrays. + * The inner array always contains {@code [format, length, ...]} where ... has length elements. + * An inner array is followed by another inner array if the total metadata entry size hasn't + * yet been exceeded.</p> + * + * <p>Entry must not be {@code null}. Empty array is acceptable.</p> + * + * <p>The entry array ownership is passed to this instance after construction; do not + * write to it afterwards.</p> + * + * @param entry Array of ints, not yet deserialized (not-null) + * + * @throws IllegalArgumentException + * if the data was poorly formatted + * (missing output format length or too few output formats) + * @throws NullPointerException + * if entry was null + * + * @hide + */ + public ReprocessFormatsMap(final int[] entry) { + checkNotNull(entry, "entry must not be null"); + + int numInputs = 0; + int left = entry.length; + for (int i = 0; i < entry.length; ) { + final int format = entry[i]; + + left--; + i++; + + if (left < 1) { + throw new IllegalArgumentException( + String.format("Input %x had no output format length listed", format)); + } + + final int length = entry[i]; + left--; + i++; + + if (length > 0) { + if (left < length) { + throw new IllegalArgumentException( + String.format( + "Input %x had too few output formats listed (actual: %d, " + + "expected: %d)", format, left, length)); + } + + i += length; + left -= length; + } + + numInputs++; + } + + mEntry = entry; + mInputCount = numInputs; + } + + /** + * Get a list of all input image formats that can be used to reprocess an input + * stream into an output stream. + * + * <p>Use this input format to look up the available output formats with {@link #getOutputs}. + * </p> + * + * @return an array of inputs (possibly empty, but never {@code null}) + * + * @see ImageFormat + * @see #getOutputs + */ + public int[] getInputs() { + final int[] inputs = new int[mInputCount]; + + int left = mEntry.length; + for (int i = 0, j = 0; i < mEntry.length; j++) { + final int format = mEntry[i]; + + left--; + i++; + + if (left < 1) { + throw new AssertionError( + String.format("Input %x had no output format length listed", format)); + } + // TODO: check format is a valid input format + + final int length = mEntry[i]; + left--; + i++; + + if (length > 0) { + if (left < length) { + throw new AssertionError( + String.format( + "Input %x had too few output formats listed (actual: %d, " + + "expected: %d)", format, left, length)); + } + + i += length; + left -= length; + } + + // TODO: check output format is a valid output format + + inputs[j] = format; + } + + return inputs; + } + + /** + * Get the list of output formats that can be reprocessed into from the input {@code format}. + * + * <p>The input {@code format} must be one of the formats returned by {@link #getInputs}.</p> + * + * @param format an input format + * + * @return list of output image formats + * + * @see ImageFormat + * @see #getInputs + */ + public int[] getOutputs(final int format) { + + int left = mEntry.length; + for (int i = 0; i < mEntry.length; ) { + final int inputFormat = mEntry[i]; + + left--; + i++; + + if (left < 1) { + throw new AssertionError( + String.format("Input %x had no output format length listed", format)); + } + + final int length = mEntry[i]; + left--; + i++; + + if (length > 0) { + if (left < length) { + throw new AssertionError( + String.format( + "Input %x had too few output formats listed (actual: %d, " + + "expected: %d)", format, left, length)); + } + } + + if (inputFormat == format) { + int[] outputs = new int[length]; + + // Copying manually faster than System.arraycopy for small arrays + for (int k = 0; k < length; ++k) { + outputs[k] = mEntry[i + k]; + } + + return outputs; + } + + i += length; + left -= length; + + } + + throw new IllegalArgumentException( + String.format("Input format %x was not one in #getInputs", format)); + } + + /** + * Check if this {@link ReprocessFormatsMap} is equal to another + * {@link ReprocessFormatsMap}. + * + * <p>These two objects are only equal if and only if each of the respective elements is equal. + * </p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof ReprocessFormatsMap) { + final ReprocessFormatsMap other = (ReprocessFormatsMap) obj; + // Do not compare anything besides mEntry, since the rest of the values are derived + return Arrays.equals(mEntry, other.mEntry); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + // Do not hash anything besides mEntry since the rest of the values are derived + return HashCodeHelpers.hashCode(mEntry); + } + + private final int[] mEntry; + /* + * Dependent fields: values are derived from mEntry + */ + private final int mInputCount; +} diff --git a/core/java/android/hardware/camera2/RggbChannelVector.java b/core/java/android/hardware/camera2/RggbChannelVector.java new file mode 100644 index 0000000..80167c6 --- /dev/null +++ b/core/java/android/hardware/camera2/RggbChannelVector.java @@ -0,0 +1,199 @@ +/* + * 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.hardware.camera2; + +import static com.android.internal.util.Preconditions.*; + +/** + * Immutable class to store a 4-element vector of floats indexable by a bayer RAW 2x2 pixel block. + */ +public final class RggbChannelVector { + /** + * The number of color channels in this vector. + */ + public static final int COUNT = 4; + + /** Red color channel in a bayer Raw pattern. */ + public static final int RED = 0; + + /** Green color channel in a bayer Raw pattern used by the even rows. */ + public static final int GREEN_EVEN = 1; + + /** Green color channel in a bayer Raw pattern used by the odd rows. */ + public static final int GREEN_ODD = 2; + + /** Blue color channel in a bayer Raw pattern. */ + public static final int BLUE = 3; + + /** + * Create a new {@link RggbChannelVector} from an RGGB 2x2 pixel. + * + * <p>All pixel values are considered normalized within {@code [0.0f, 1.0f]} + * (i.e. {@code 1.0f} could be linearized to {@code 255} if converting to a + * non-floating point pixel representation).</p> + * + * <p>All arguments must be finite; NaN and infinity is not allowed.</p> + * + * @param red red pixel + * @param greenEven green pixel (even row) + * @param greenOdd green pixel (odd row) + * @param blue blue pixel + * + * @throws IllegalArgumentException if any of the arguments were not finite + */ + public RggbChannelVector(final float red, final float greenEven, final float greenOdd, + final float blue) { + mRed = checkArgumentFinite(red, "red"); + mGreenEven = checkArgumentFinite(greenEven, "greenEven"); + mGreenOdd = checkArgumentFinite(greenOdd, "greenOdd"); + mBlue = checkArgumentFinite(blue, "blue"); + } + + /** + * Get the red component. + * + * @return a floating point value (guaranteed to be finite) + */ + public final float getRed() { + return mRed; + } + + /** + * Get the green (even rows) component. + * + * @return a floating point value (guaranteed to be finite) + */ + public float getGreenEven() { + return mGreenEven; + } + + /** + * Get the green (odd rows) component. + * + * @return a floating point value (guaranteed to be finite) + */ + public float getGreenOdd() { + return mGreenOdd; + } + + /** + * Get the blue component. + * + * @return a floating point value (guaranteed to be finite) + */ + public float getBlue() { + return mBlue; + } + + /** + * Get the component by the color channel index. + * + * <p>{@code colorChannel} must be one of {@link #RED}, {@link #GREEN_EVEN}, {@link #GREEN_ODD}, + * {@link #BLUE}.</p> + * + * @param colorChannel greater or equal to {@code 0} and less than {@link #COUNT} + * @return a floating point value (guaranteed to be finite) + * + * @throws IllegalArgumentException if {@code colorChannel} was out of range + */ + public float getComponent(final int colorChannel) { + if (colorChannel < 0 || colorChannel >= COUNT) { + throw new IllegalArgumentException("Color channel out of range"); + } + + switch (colorChannel) { + case RED: + return mRed; + case GREEN_EVEN: + return mGreenEven; + case GREEN_ODD: + return mGreenOdd; + case BLUE: + return mBlue; + default: + throw new AssertionError("Unhandled case " + colorChannel); + } + } + + /** + * Copy the vector into the destination in the order {@code [R, Geven, Godd, B]}. + * + * @param destination + * an array big enough to hold at least {@value #COUNT} elements after the + * {@code offset} + * @param offset + * a non-negative offset into the array + * + * @throws NullPointerException + * If {@code destination} was {@code null} + * @throws ArrayIndexOutOfBoundsException + * If there's not enough room to write the elements at the specified destination and + * offset. + */ + public void copyTo(final float[] destination, final int offset) { + checkNotNull(destination, "destination must not be null"); + if (destination.length + offset < COUNT) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + + destination[offset + RED] = mRed; + destination[offset + GREEN_EVEN] = mGreenEven; + destination[offset + GREEN_ODD] = mGreenOdd; + destination[offset + BLUE] = mBlue; + } + + /** + * Check if this {@link RggbChannelVector} is equal to another {@link RggbChannelVector}. + * + * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof RggbChannelVector) { + final RggbChannelVector other = (RggbChannelVector) obj; + return mRed == other.mRed && + mGreenEven == other.mGreenEven && + mGreenOdd == other.mGreenOdd && + mBlue == other.mBlue; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Float.floatToIntBits(mRed) ^ + Float.floatToIntBits(mGreenEven) ^ + Float.floatToIntBits(mGreenOdd) ^ + Float.floatToIntBits(mBlue); + } + + private final float mRed; + private final float mGreenEven; + private final float mGreenOdd; + private final float mBlue; +} diff --git a/core/java/android/hardware/camera2/Size.java b/core/java/android/hardware/camera2/Size.java index 45aaeae..9328a003 100644 --- a/core/java/android/hardware/camera2/Size.java +++ b/core/java/android/hardware/camera2/Size.java @@ -16,32 +16,55 @@ package android.hardware.camera2; +// TODO: Delete this class, since it was moved to android.util as public API + /** - * A simple immutable class for describing the dimensions of camera image - * buffers. + * Immutable class for describing width and height dimensions in pixels. + * + * @hide */ public final class Size { /** - * Create a new immutable Size instance + * Create a new immutable Size instance. * - * @param width The width to store in the Size instance - * @param height The height to store in the Size instance + * @param width The width of the size, in pixels + * @param height The height of the size, in pixels */ - public Size(int width, int height) { + public Size(final int width, final int height) { mWidth = width; mHeight = height; } + /** + * Get the width of the size (in pixels). + * @return width + */ public final int getWidth() { return mWidth; } + /** + * Get the height of the size (in pixels). + * @return height + */ public final int getHeight() { return mHeight; } + /** + * Check if this size is equal to another size. + * <p> + * Two sizes are equal if and only if both their widths and heights are + * equal. + * </p> + * <p> + * A size object is never equal to any other type of object. + * </p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ @Override - public boolean equals(Object obj) { + public boolean equals(final Object obj) { if (obj == null) { return false; } @@ -49,27 +72,29 @@ public final class Size { return true; } if (obj instanceof Size) { - Size other = (Size) obj; + final Size other = (Size) obj; return mWidth == other.mWidth && mHeight == other.mHeight; } return false; } + /** + * Return the size represented as a string with the format {@code "WxH"} + * + * @return string representation of the size + */ @Override public String toString() { return mWidth + "x" + mHeight; } + /** + * {@inheritDoc} + */ @Override public int hashCode() { - final long INT_MASK = 0xffffffffL; - - long asLong = INT_MASK & mWidth; - asLong <<= 32; - - asLong |= (INT_MASK & mHeight); - - return ((Long)asLong).hashCode(); + // assuming most sizes are <2^16, doing a rotate will give us perfect hashing + return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); } private final int mWidth; diff --git a/core/java/android/hardware/camera2/StreamConfiguration.java b/core/java/android/hardware/camera2/StreamConfiguration.java new file mode 100644 index 0000000..c53dd7c --- /dev/null +++ b/core/java/android/hardware/camera2/StreamConfiguration.java @@ -0,0 +1,166 @@ +/* + * 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.hardware.camera2; + +import static com.android.internal.util.Preconditions.*; +import static android.hardware.camera2.StreamConfigurationMap.checkArgumentFormatInternal; + +import android.graphics.ImageFormat; +import android.hardware.camera2.impl.HashCodeHelpers; +import android.util.Size; + +/** + * Immutable class to store the available stream + * {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS configurations} to be used + * when configuring streams with {@link CameraDevice#configureOutputs}. + * <!-- TODO: link to input stream configuration --> + * + * <p>This is the authoritative list for all input/output formats (and sizes respectively + * for that format) that are supported by a camera device.</p> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS + * + * @hide + */ +public final class StreamConfiguration { + + /** + * Create a new {@link StreamConfiguration}. + * + * @param format image format + * @param width image width, in pixels (positive) + * @param height image height, in pixels (positive) + * @param input true if this is an input configuration, false for output configurations + * + * @throws IllegalArgumentException + * if width/height were not positive + * or if the format was not user-defined in ImageFormat/PixelFormat + * (IMPL_DEFINED is ok) + * + * @hide + */ + public StreamConfiguration( + final int format, final int width, final int height, final boolean input) { + mFormat = checkArgumentFormatInternal(format); + mWidth = checkArgumentPositive(width, "width must be positive"); + mHeight = checkArgumentPositive(width, "height must be positive"); + mInput = input; + } + + /** + * Get the image {@code format} in this stream configuration. + * + * @return an integer format + * + * @see ImageFormat + */ + public final int getFormat() { + return mFormat; + } + + + /** + * Return the width of the stream configuration. + * + * @return width > 0 + */ + public int getWidth() { + return mWidth; + } + + /** + * Return the height of the stream configuration. + * + * @return height > 0 + */ + public int getHeight() { + return mHeight; + } + + /** + * Convenience method to return the size of this stream configuration. + * + * @return a Size with positive width and height + */ + public Size getSize() { + return new Size(mWidth, mHeight); + } + + /** + * Determines if this configuration is usable for input streams. + * + * <p>Input and output stream configurations are not interchangeable; + * input stream configurations must be used when configuring inputs.</p> + * + * @return {@code true} if input configuration, {@code false} otherwise + */ + public boolean isInput() { + return mInput; + } + + /** + * Determines if this configuration is usable for output streams. + * + * <p>Input and output stream configurations are not interchangeable; + * out stream configurations must be used when configuring outputs.</p> + * + * @return {@code true} if output configuration, {@code false} otherwise + * + * @see CameraDevice#configureOutputs + */ + public boolean isOutput() { + return !mInput; + } + + /** + * Check if this {@link StreamConfiguration} is equal to another {@link StreamConfiguration}. + * + * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof StreamConfiguration) { + final StreamConfiguration other = (StreamConfiguration) obj; + return mFormat == other.mFormat && + mWidth == other.mWidth && + mHeight == other.mHeight && + mInput == other.mInput; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mFormat, mWidth, mHeight, mInput ? 1 : 0); + } + + private final int mFormat; + private final int mWidth; + private final int mHeight; + private final boolean mInput; +} diff --git a/core/java/android/hardware/camera2/StreamConfigurationDuration.java b/core/java/android/hardware/camera2/StreamConfigurationDuration.java new file mode 100644 index 0000000..189ae62 --- /dev/null +++ b/core/java/android/hardware/camera2/StreamConfigurationDuration.java @@ -0,0 +1,148 @@ +/* + * 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.hardware.camera2; + +import static com.android.internal.util.Preconditions.*; +import static android.hardware.camera2.StreamConfigurationMap.checkArgumentFormatInternal; + +import android.graphics.ImageFormat; +import android.hardware.camera2.impl.HashCodeHelpers; +import android.util.Size; + +/** + * Immutable class to store a time duration for any given format/size combination. + * + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS + * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS + * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS + * + * @hide + */ +public final class StreamConfigurationDuration { + + /** + * Create a new {@link StreamConfigurationDuration}. + * + * @param format image format + * @param width image width, in pixels (positive) + * @param height image height, in pixels (positive) + * @param durationNs duration in nanoseconds (non-negative) + * + * @throws IllegalArgumentException + * if width/height were not positive, or durationNs was negative + * or if the format was not user-defined in ImageFormat/PixelFormat + * (IMPL_DEFINED is OK) + * + * + * @hide + */ + public StreamConfigurationDuration( + final int format, final int width, final int height, final long durationNs) { + mFormat = checkArgumentFormatInternal(format); + mWidth = checkArgumentPositive(width, "width must be positive"); + mHeight = checkArgumentPositive(width, "height must be positive"); + mDurationNs = checkArgumentNonnegative(durationNs, "durationNs must be non-negative"); + } + + /** + * Get the image {@code format} in this stream configuration duration + * + * @return an integer format + * + * @see ImageFormat + */ + public final int getFormat() { + return mFormat; + } + + + /** + * Return the width of the stream configuration duration. + * + * @return width > 0 + */ + public int getWidth() { + return mWidth; + } + + /** + * Return the height of the stream configuration duration + * + * @return height > 0 + */ + public int getHeight() { + return mHeight; + } + + /** + * Convenience method to return the size of this stream configuration duration. + * + * @return a Size with positive width and height + */ + public Size getSize() { + return new Size(mWidth, mHeight); + } + + /** + * Get the time duration (in nanoseconds). + * + * @return long >= 0 + */ + public long getDuration() { + return mDurationNs; + } + + /** + * Check if this {@link StreamConfigurationDuration} is equal to another + * {@link StreamConfigurationDuration}. + * + * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof StreamConfigurationDuration) { + final StreamConfigurationDuration other = (StreamConfigurationDuration) obj; + return mFormat == other.mFormat && + mWidth == other.mWidth && + mHeight == other.mHeight && + mDurationNs == other.mDurationNs; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mFormat, mWidth, mHeight, + (int) mDurationNs, (int)(mDurationNs >>> Integer.SIZE)); + } + + private final int mFormat; + private final int mWidth; + private final int mHeight; + private final long mDurationNs; +} diff --git a/core/java/android/hardware/camera2/StreamConfigurationMap.java b/core/java/android/hardware/camera2/StreamConfigurationMap.java new file mode 100644 index 0000000..e24fd1b --- /dev/null +++ b/core/java/android/hardware/camera2/StreamConfigurationMap.java @@ -0,0 +1,508 @@ +/* + * 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.hardware.camera2; + +import android.graphics.ImageFormat; +import android.graphics.PixelFormat; +import android.hardware.camera2.impl.HashCodeHelpers; +import android.view.Surface; +import android.util.Size; + +import java.util.Arrays; + +import static com.android.internal.util.Preconditions.*; + +/** + * Immutable class to store the available stream + * {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS configurations} to be used + * when configuring streams with {@link CameraDevice#configureOutputs}. + * <!-- TODO: link to input stream configuration --> + * + * <p>This is the authoritative list for all <!-- input/ -->output formats (and sizes respectively + * for that format) that are supported by a camera device.</p> + * + * <p>This also contains the minimum frame durations and stall durations for each format/size + * combination that can be used to calculate effective frame rate when submitting multiple captures. + * </p> + * + * <p>An instance of this object is available from {@link CameraCharacteristics} using + * the {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS} key and the + * {@link CameraCharacteristics#get} method.</p. + * + * <pre>{@code + * CameraCharacteristics characteristics = ...; + * StreamConfigurationMap configs = characteristics.get( + * CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS); + * }</pre> + * + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS + * @see CameraDevice#configureOutputs + */ +public final class StreamConfigurationMap { + + /** + * Create a new {@link StreamConfigurationMap}. + * + * <p>The array parameters ownership is passed to this object after creation; do not + * write to them after this constructor is invoked.</p> + * + * @param configurations a non-{@code null} array of {@link StreamConfiguration} + * @param durations a non-{@code null} array of {@link StreamConfigurationDuration} + * + * @throws NullPointerException if any of the arguments or subelements were {@code null} + * + * @hide + */ + public StreamConfigurationMap( + StreamConfiguration[] configurations, + StreamConfigurationDuration[] durations) { + // TODO: format check against ImageFormat/PixelFormat ? + + mConfigurations = checkArrayElementsNotNull(configurations, "configurations"); + mDurations = checkArrayElementsNotNull(durations, "durations"); + + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get the image {@code format} output formats in this stream configuration. + * + * <p>All image formats returned by this function will be defined in either {@link ImageFormat} + * or in {@link PixelFormat} (and there is no possibility of collision).</p> + * + * <p>Formats listed in this array are guaranteed to return true if queried with + * {@link #isOutputSupportedFor(int).</p> + * + * @return an array of integer format + * + * @see ImageFormat + * @see PixelFormat + */ + public final int[] getOutputFormats() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get the image {@code format} input formats in this stream configuration. + * + * <p>All image formats returned by this function will be defined in either {@link ImageFormat} + * or in {@link PixelFormat} (and there is no possibility of collision).</p> + * + * @return an array of integer format + * + * @see ImageFormat + * @see PixelFormat + * + * @hide + */ + public final int[] getInputFormats() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get the supported input sizes for this input format. + * + * <p>The format must have come from {@link #getInputFormats}; otherwise + * {@code null} is returned.</p> + * + * @param format a format from {@link #getInputFormats} + * @return a non-empty array of sizes, or {@code null} if the format was not available. + * + * @hide + */ + public Size[] getInputSizes(final int format) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Determine whether or not output streams can be + * {@link CameraDevice#configureOutputs configured} with a particular user-defined format. + * + * <p>This method determines that the output {@code format} is supported by the camera device; + * each output {@code surface} target may or may not itself support that {@code format}. + * Refer to the class which provides the surface for additional documentation.</p> + * + * <p>Formats for which this returns {@code true} are guaranteed to exist in the result + * returned by {@link #getOutputSizes}.</p> + * + * @param format an image format from either {@link ImageFormat} or {@link PixelFormat} + * @return + * {@code true} iff using a {@code surface} with this {@code format} will be + * supported with {@link CameraDevice#configureOutputs} + * + * @throws IllegalArgumentException + * if the image format was not a defined named constant + * from either {@link ImageFormat} or {@link PixelFormat} + * + * @see ImageFormat + * @see PixelFormat + * @see CameraDevice#configureOutputs + */ + public boolean isOutputSupportedFor(int format) { + checkArgumentFormat(format); + + final int[] formats = getOutputFormats(); + for (int i = 0; i < formats.length; ++i) { + if (format == formats[i]) { + return true; + } + } + + return false; + } + + /** + * Determine whether or not output streams can be configured with a particular class + * as a consumer. + * + * <p>The following list is generally usable for outputs: + * <ul> + * <li>{@link android.media.ImageReader} - + * Recommended for image processing or streaming to external resources (such as a file or + * network) + * <li>{@link android.media.MediaRecorder} - + * Recommended for recording video (simple to use) + * <li>{@link android.media.MediaCodec} - + * Recommended for recording video (more complicated to use, with more flexibility) + * <li>{@link android.renderscript.Allocation} - + * Recommended for image processing with {@link android.renderscript RenderScript} + * <li>{@link android.view.SurfaceHolder} - + * Recommended for low-power camera preview with {@link android.view.SurfaceView} + * <li>{@link android.graphics.SurfaceTexture} - + * Recommended for OpenGL-accelerated preview processing or compositing with + * {@link android.view.TextureView} + * </ul> + * </p> + * + * <p>Generally speaking this means that creating a {@link Surface} from that class <i>may</i> + * provide a producer endpoint that is suitable to be used with + * {@link CameraDevice#configureOutputs}.</p> + * + * <p>Since not all of the above classes support output of all format and size combinations, + * the particular combination should be queried with {@link #isOutputSupportedFor(Surface)}.</p> + * + * @param klass a non-{@code null} {@link Class} object reference + * @return {@code true} if this class is supported as an output, {@code false} otherwise + * + * @throws NullPointerException if {@code klass} was {@code null} + * + * @see CameraDevice#configureOutputs + * @see #isOutputSupportedFor(Surface) + */ + public static <T> boolean isOutputSupportedFor(final Class<T> klass) { + checkNotNull(klass, "klass must not be null"); + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Determine whether or not the {@code surface} in its current state is suitable to be + * {@link CameraDevice#configureOutputs configured} as an output. + * + * <p>Not all surfaces are usable with the {@link CameraDevice}, and not all configurations + * of that {@code surface} are compatible. Some classes that provide the {@code surface} are + * compatible with the {@link CameraDevice} in general + * (see {@link #isOutputSupportedFor(Class)}, but it is the caller's responsibility to put the + * {@code surface} into a state that will be compatible with the {@link CameraDevice}.</p> + * + * <p>Reasons for a {@code surface} being specifically incompatible might be: + * <ul> + * <li>Using a format that's not listed by {@link #getOutputFormats} + * <li>Using a format/size combination that's not listed by {@link #getOutputSizes} + * <li>The {@code surface} itself is not in a state where it can service a new producer.</p> + * </li> + * </ul> + * + * This is not an exhaustive list; see the particular class's documentation for further + * possible reasons of incompatibility.</p> + * + * @param surface a non-{@code null} {@link Surface} object reference + * @return {@code true} if this is supported, {@code false} otherwise + * + * @throws NullPointerException if {@code surface} was {@code null} + * + * @see CameraDevice#configureOutputs + * @see #isOutputSupportedFor(Class) + */ + public boolean isOutputSupportedFor(final Surface surface) { + checkNotNull(surface, "surface must not be null"); + + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get a list of sizes compatible with {@code klass} to use as an output. + * + * <p>Since some of the supported classes may support additional formats beyond + * an opaque/implementation-defined (under-the-hood) format; this function only returns + * sizes for the implementation-defined format.</p> + * + * <p>Some classes such as {@link android.media.ImageReader} may only support user-defined + * formats; in particular {@link #isOutputSupportedFor(Class)} will return {@code true} for + * that class and this method will return an empty array (but not {@code null}).</p> + * + * <p>If a well-defined format such as {@code NV21} is required, use + * {@link #getOutputSizes(int)} instead.</p> + * + * <p>The {@code klass} should be a supported output, that querying + * {@code #isOutputSupportedFor(Class)} should return {@code true}.</p> + * + * @param klass + * a non-{@code null} {@link Class} object reference + * @return + * an array of supported sizes for implementation-defined formats, + * or {@code null} iff the {@code klass} is not a supported output + * + * @throws NullPointerException if {@code klass} was {@code null} + * + * @see #isOutputSupportedFor(Class) + */ + public <T> Size[] getOutputSizes(final Class<T> klass) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get a list of sizes compatible with the requested image {@code format}. + * + * <p>The {@code format} should be a supported format (one of the formats returned by + * {@link #getOutputFormats}).</p> + * + * @param format an image format from {@link ImageFormat} or {@link PixelFormat} + * @return + * an array of supported sizes, + * or {@code null} if the {@code format} is not a supported output + * + * @see ImageFormat + * @see PixelFormat + * @see #getOutputFormats + */ + public Size[] getOutputSizes(final int format) { + try { + checkArgumentFormatSupported(format, /*output*/true); + } catch (IllegalArgumentException e) { + return null; + } + + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get the minimum {@link CaptureRequest#SENSOR_FRAME_DURATION frame duration} + * for the format/size combination (in nanoseconds). + * + * <p>{@code format} should be one of the ones returned by {@link #getOutputFormats()}.</p> + * <p>{@code size} should be one of the ones returned by + * {@link #getOutputSizes(int)}.</p> + * + * @param format an image format from {@link ImageFormat} or {@link PixelFormat} + * @param size an output-compatible size + * @return a minimum frame duration {@code >=} 0 in nanoseconds + * + * @throws IllegalArgumentException if {@code format} or {@code size} was not supported + * @throws NullPointerException if {@code size} was {@code null} + * + * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see ImageFormat + * @see PixelFormat + */ + public long getOutputMinFrameDuration(final int format, final Size size) { + checkArgumentFormatSupported(format, /*output*/true); + + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get the minimum {@link CaptureRequest#SENSOR_FRAME_DURATION frame duration} + * for the class/size combination (in nanoseconds). + * + * <p>This assumes a the {@code klass} is set up to use an implementation-defined format. + * For user-defined formats, use {@link #getOutputMinFrameDuration(int, Size)}.</p> + * + * <p>{@code klass} should be one of the ones which is supported by + * {@link #isOutputSupportedFor(Class)}.</p> + * + * <p>{@code size} should be one of the ones returned by + * {@link #getOutputSizes(int)}.</p> + * + * @param klass + * a class which is supported by {@link #isOutputSupportedFor(Class)} and has a + * non-empty array returned by {@link #getOutputSizes(Class)} + * @param size an output-compatible size + * @return a minimum frame duration {@code >=} 0 in nanoseconds + * + * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported + * @throws NullPointerException if {@code size} or {@code klass} was {@code null} + * + * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see ImageFormat + * @see PixelFormat + */ + public <T> long getOutputMinFrameDuration(final Class<T> klass, final Size size) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get the {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS stall duration} + * for the format/size combination (in nanoseconds). + * + * <p>{@code format} should be one of the ones returned by {@link #getOutputFormats()}.</p> + * <p>{@code size} should be one of the ones returned by + * {@link #getOutputSizes(int)}.</p> + * + * @param format an image format from {@link ImageFormat} or {@link PixelFormat} + * @param size an output-compatible size + * @return a stall duration {@code >=} 0 in nanoseconds + * + * @throws IllegalArgumentException if {@code format} or {@code size} was not supported + * @throws NullPointerException if {@code size} was {@code null} + * + * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS + * @see ImageFormat + * @see PixelFormat + */ + public long getOutputStallDuration(final int format, final Size size) { + checkArgumentFormatSupported(format, /*output*/true); + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Get the {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS stall duration} + * for the class/size combination (in nanoseconds). + * + * <p>This assumes a the {@code klass} is set up to use an implementation-defined format. + * For user-defined formats, use {@link #getOutputMinFrameDuration(int, Size)}.</p> + * + * <p>{@code klass} should be one of the ones with a non-empty array returned by + * {@link #getOutputSizes(Class)}.</p> + * + * <p>{@code size} should be one of the ones returned by + * {@link #getOutputSizes(Class)}.</p> + * + * @param klass + * a class which is supported by {@link #isOutputSupportedFor(Class)} and has a + * non-empty array returned by {@link #getOutputSizes(Class)} + * @param size an output-compatible size + * @return a minimum frame duration {@code >=} 0 in nanoseconds + * + * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported + * @throws NullPointerException if {@code size} or {@code klass} was {@code null} + * + * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see ImageFormat + * @see PixelFormat + */ + public <T> long getOutputStallDuration(final Class<T> klass, final Size size) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Check if this {@link StreamConfigurationMap} is equal to another + * {@link StreamConfigurationMap}. + * + * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof StreamConfigurationMap) { + final StreamConfigurationMap other = (StreamConfigurationMap) obj; + // TODO: do we care about order? + return Arrays.equals(mConfigurations, other.mConfigurations) && + Arrays.equals(mDurations, other.mDurations); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + // TODO: do we care about order? + return HashCodeHelpers.hashCode(mConfigurations) ^ HashCodeHelpers.hashCode(mDurations); + } + + // Check that the argument is supported by #getOutputFormats or #getInputFormats + private int checkArgumentFormatSupported(int format, boolean output) { + checkArgumentFormat(format); + + int[] formats = output ? getOutputFormats() : getInputFormats(); + for (int i = 0; i < formats.length; ++i) { + if (format == formats[i]) { + return format; + } + } + + throw new IllegalArgumentException(String.format( + "format %x is not supported by this stream configuration map", format)); + } + + /** + * Ensures that the format is either user-defined or implementation defined. + * + * <p>Any invalid/undefined formats will raise an exception.</p> + * + * @param format image format + * @return the format + * + * @throws IllegalArgumentException if the format was invalid + */ + static int checkArgumentFormatInternal(int format) { + if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) { + return format; + } + + return checkArgumentFormat(format); + } + + /** + * Ensures that the format is user-defined in either ImageFormat or PixelFormat. + * + * <p>Any invalid/undefined formats will raise an exception, including implementation-defined. + * </p> + * + * <p>Note that {@code @hide} and deprecated formats will not pass this check.</p> + * + * @param format image format + * @return the format + * + * @throws IllegalArgumentException if the format was not user-defined + */ + static int checkArgumentFormat(int format) { + if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) { + throw new IllegalArgumentException(String.format( + "format %x was not defined in either ImageFormat or PixelFormat", format)); + } + + return format; + } + + private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22; + + private final StreamConfiguration[] mConfigurations; + private final StreamConfigurationDuration[] mDurations; + +} diff --git a/core/java/android/hardware/camera2/TonemapCurve.java b/core/java/android/hardware/camera2/TonemapCurve.java new file mode 100644 index 0000000..ee20d68 --- /dev/null +++ b/core/java/android/hardware/camera2/TonemapCurve.java @@ -0,0 +1,290 @@ +/* + * 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.hardware.camera2; + +import static com.android.internal.util.Preconditions.*; + +import android.graphics.PointF; +import android.hardware.camera2.impl.HashCodeHelpers; + +import java.util.Arrays; + +/** + * Immutable class for describing a {@code 2 x M x 3} tonemap curve of floats. + * + * <p>This defines red, green, and blue curves that the {@link CameraDevice} will + * use as the tonemapping/contrast/gamma curve when {@link CaptureRequest#TONEMAP_MODE} is + * set to {@link CameraMetadata#TONEMAP_MODE_CONTRAST_CURVE}.</p> + * + * <p>The total number of points {@code (Pin, Pout)} for each color channel can be no more than + * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS}.</p> + * + * <p>The coordinate system for each point is within the inclusive range + * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p> + * + * @see CaptureRequest#TONEMAP_CURVE_BLUE + * @see CaptureRequest#TONEMAP_CURVE_GREEN + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CameraMetadata#TONEMAP_MODE_CONTRAST_CURVE + * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS + */ +public final class TonemapCurve { + /** + * Lower bound tonemap value corresponding to pure black for a single color channel. + */ + public static final float LEVEL_BLACK = 0.0f; + + /** + * Upper bound tonemap value corresponding to a pure white for a single color channel. + */ + public static final float LEVEL_WHITE = 1.0f; + + /** + * Number of elements in a {@code (Pin, Pout)} point; + */ + public static final int POINT_SIZE = 2; + + /** + * Index of the red color channel curve. + */ + public static final int CHANNEL_RED = 0; + /** + * Index of the green color channel curve. + */ + public static final int CHANNEL_GREEN = 1; + /** + * Index of the blue color channel curve. + */ + public static final int CHANNEL_BLUE = 2; + + /** + * Create a new immutable TonemapCurve instance. + * + * <p>Values are stored as a contiguous {@code (Pin, Pout}) point.</p> + * + * <p>All parameters may have independent length but should have at most + * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS} * {@value #POINT_SIZE} elements.</p> + * + * <p>All sub-elements must be in the inclusive range of + * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p> + * + * <p>This constructor copies the array contents and does not retain ownership of the array.</p> + * + * @param elements An array of elements whose length is {@code CHANNEL_COUNT * rows * columns} + * + * @throws IllegalArgumentException + * if the {@code elements} array length is invalid, + * if any of the subelems are not finite + * @throws NullPointerException + * if any of the parameters is {@code null} + * + * @hide + */ + public TonemapCurve(float[] red, float[] green, float[] blue) { + // TODO: maxCurvePoints check? + + checkNotNull(red, "red must not be null"); + checkNotNull(green, "green must not be null"); + checkNotNull(blue, "blue must not be null"); + + checkArgumentArrayLengthDivisibleBy(red, POINT_SIZE, "red"); + checkArgumentArrayLengthDivisibleBy(green, POINT_SIZE, "green"); + checkArgumentArrayLengthDivisibleBy(blue, POINT_SIZE, "blue"); + + checkArrayElementsInRange(red, LEVEL_BLACK, LEVEL_WHITE, "red"); + checkArrayElementsInRange(green, LEVEL_BLACK, LEVEL_WHITE, "green"); + checkArrayElementsInRange(blue, LEVEL_BLACK, LEVEL_WHITE, "blue"); + + mRed = Arrays.copyOf(red, red.length); + mGreen = Arrays.copyOf(green, green.length); + mBlue = Arrays.copyOf(blue, blue.length); + } + + private static void checkArgumentArrayLengthDivisibleBy(float[] array, + int divisible, String arrayName) { + if (array.length % divisible != 0) { + throw new IllegalArgumentException(arrayName + " size must be divisible by " + + divisible); + } + } + + private static int checkArgumentColorChannel(int colorChannel) { + switch (colorChannel) { + case CHANNEL_RED: + case CHANNEL_GREEN: + case CHANNEL_BLUE: + break; + default: + throw new IllegalArgumentException("colorChannel out of range"); + } + + return colorChannel; + } + + /** + * Get the number of points stored in this tonemap curve for the specified color channel. + * + * @param colorChannel one of {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, {@link #CHANNEL_BLUE} + * @return number of points stored in this tonemap for that color's curve (>= 0) + * + * @throws IllegalArgumentException if {@code colorChannel} was out of range + */ + public int getPointCount(int colorChannel) { + checkArgumentColorChannel(colorChannel); + + return getCurve(colorChannel).length / POINT_SIZE; + } + + /** + * Get the point for a color channel at a specified index. + * + * <p>The index must be at least 0 but no greater than {@link #getPointCount(int)} for + * that {@code colorChannel}.</p> + * + * <p>All returned coordinates in the point are between the range of + * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p> + * + * @param colorChannel {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, or {@link #CHANNEL_BLUE} + * @param index at least 0 but no greater than {@code getPointCount(colorChannel)} + * @return the {@code (Pin, Pout)} pair mapping the tone for that index + * + * @throws IllegalArgumentException if {@code colorChannel} or {@code index} was out of range + * + * @see #LEVEL_BLACK + * @see #LEVEL_WHITE + */ + public PointF getPoint(int colorChannel, int index) { + checkArgumentColorChannel(colorChannel); + if (index < 0 || index >= getPointCount(colorChannel)) { + throw new IllegalArgumentException("index out of range"); + } + + final float[] curve = getCurve(colorChannel); + + final float pIn = curve[index * POINT_SIZE + OFFSET_POINT_IN]; + final float pOut = curve[index * POINT_SIZE + OFFSET_POINT_OUT]; + + return new PointF(pIn, pOut); + } + + /** + * Copy the color curve for a single color channel from this tonemap curve into the destination. + * + * <p> + * <!--The output is encoded the same as in the constructor --> + * Values are stored as packed {@code (Pin, Pout}) points, and there are a total of + * {@link #getPointCount} points for that respective channel.</p> + * + * <p>All returned coordinates are between the range of + * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p> + * + * @param destination + * an array big enough to hold at least {@link #getPointCount} {@code *} + * {@link #POINT_SIZE} elements after the {@code offset} + * @param offset + * a non-negative offset into the array + * @throws NullPointerException + * If {@code destination} was {@code null} + * @throws IllegalArgumentException + * If offset was negative + * @throws ArrayIndexOutOfBoundsException + * If there's not enough room to write the elements at the specified destination and + * offset. + * + * @see CaptureRequest#TONEMAP_CURVE_BLUE + * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE_GREEN + * @see #LEVEL_BLACK + * @see #LEVEL_WHITE + */ + public void copyColorCurve(int colorChannel, float[] destination, + int offset) { + checkArgumentNonnegative(offset, "offset must not be negative"); + checkNotNull(destination, "destination must not be null"); + + if (destination.length + offset < getPointCount(colorChannel) * POINT_SIZE) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + + float[] curve = getCurve(colorChannel); + System.arraycopy(curve, /*srcPos*/0, destination, offset, curve.length); + } + + /** + * Check if this TonemapCurve is equal to another TonemapCurve. + * + * <p>Two matrices are equal if and only if all of their elements are + * {@link Object#equals equal}.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof TonemapCurve) { + final TonemapCurve other = (TonemapCurve) obj; + return Arrays.equals(mRed, other.mRed) && + Arrays.equals(mGreen, other.mGreen) && + Arrays.equals(mBlue, other.mBlue); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + if (mHashCalculated) { + // Avoid re-calculating hash. Data is immutable so this is both legal and faster. + return mHashCode; + } + + mHashCode = HashCodeHelpers.hashCode(mRed, mGreen, mBlue); + mHashCalculated = true; + + return mHashCode; + } + + private float[] getCurve(int colorChannel) { + switch (colorChannel) { + case CHANNEL_RED: + return mRed; + case CHANNEL_GREEN: + return mGreen; + case CHANNEL_BLUE: + return mBlue; + default: + throw new AssertionError("colorChannel out of range"); + } + } + + private final static int OFFSET_POINT_IN = 0; + private final static int OFFSET_POINT_OUT = 1; + + private final float[] mRed; + private final float[] mGreen; + private final float[] mBlue; + + private int mHashCode; + private boolean mHashCalculated = false; +}; diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index 40a7905..988f8f9 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -647,7 +647,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { * should have arrived. The following line checks whether this holds. */ if (frameNumber != mCompletedFrameNumber + 1) { - throw new AssertionError(String.format( + Log.e(TAG, String.format( "result frame number %d comes out of order, should be %d + 1", frameNumber, mCompletedFrameNumber)); } diff --git a/core/java/android/hardware/camera2/impl/HashCodeHelpers.java b/core/java/android/hardware/camera2/impl/HashCodeHelpers.java new file mode 100644 index 0000000..2d63827 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/HashCodeHelpers.java @@ -0,0 +1,143 @@ +/* + * 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.hardware.camera2.impl; + +/** + * Provide hashing functions using the Modified Bernstein hash + */ +public final class HashCodeHelpers { + + /** + * Hash every element uniformly using the Modified Bernstein hash. + * + * <p>Useful to implement a {@link Object#hashCode} for uniformly distributed data.</p> + * + * @param array a non-{@code null} array of integers + * + * @return the numeric hash code + */ + public static int hashCode(int[] array) { + if (array == null) { + return 0; + } + + /* + * Note that we use 31 here instead of 33 since it's preferred in Effective Java + * and used elsewhere in the runtime (e.g. Arrays#hashCode) + * + * That being said 33 and 31 are nearly identical in terms of their usefulness + * according to http://svn.apache.org/repos/asf/apr/apr/trunk/tables/apr_hash.c + */ + int h = 1; + for (int x : array) { + // Strength reduction; in case the compiler has illusions about divisions being faster + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + } + + return h; + } + + /** + * Hash every element uniformly using the Modified Bernstein hash. + * + * <p>Useful to implement a {@link Object#hashCode} for uniformly distributed data.</p> + * + * @param array a non-{@code null} array of floats + * + * @return the numeric hash code + */ + public static int hashCode(float[] array) { + if (array == null) { + return 0; + } + + int h = 1; + for (float f : array) { + int x = Float.floatToIntBits(f); + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + } + + return h; + } + + /** + * Hash every element uniformly using the Modified Bernstein hash. + * + * <p>Useful to implement a {@link Object#hashCode} for uniformly distributed data.</p> + * + * @param array a non-{@code null} array of objects + * + * @return the numeric hash code + */ + public static <T> int hashCode(T[] array) { + if (array == null) { + return 0; + } + + int h = 1; + for (T o : array) { + int x = (o == null) ? 0 : o.hashCode(); + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + } + + return h; + } + + public static <T> int hashCode(T a) { + return (a == null) ? 0 : a.hashCode(); + } + + public static <T> int hashCode(T a, T b) { + int h = hashCode(a); + + int x = (b == null) ? 0 : b.hashCode(); + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + + return h; + } + + public static <T> int hashCode(T a, T b, T c) { + int h = hashCode(a, b); + + int x = (a == null) ? 0 : a.hashCode(); + h = ((h << 5) - h) ^ x; // (h * 31) XOR x + + return h; + } + + public static int hashCode(int x) { + return hashCode(new int[] { x } ); + } + + public static int hashCode(int x, int y) { + return hashCode(new int[] { x, y } ); + } + + public static int hashCode(int x, int y, int z) { + return hashCode(new int[] { x, y, z } ); + } + + public static int hashCode(int x, int y, int z, int w) { + return hashCode(new int[] { x, y, z, w } ); + } + + public static int hashCode(int x, int y, int z, int w, int t) { + return hashCode(new int[] { x, y, z, w, t } ); + } + + +} diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java index 8578a32..7213c78 100644 --- a/core/java/android/hardware/hdmi/HdmiCec.java +++ b/core/java/android/hardware/hdmi/HdmiCec.java @@ -85,7 +85,7 @@ public final class HdmiCec { public static final int ADDR_RESERVED_2 = 13; /** Logical address for TV other than the one assigned with {@link #ADDR_TV} */ - public static final int ADDR_FREE_USE = 14; + public static final int ADDR_SPECIFIC_USE = 14; /** Logical address for devices to which address cannot be allocated */ public static final int ADDR_UNREGISTERED = 15; @@ -160,6 +160,8 @@ public final class HdmiCec { public static final int MESSAGE_SET_EXTERNAL_TIMER = 0xA2; public static final int MESSAGE_ABORT = 0xFF; + public static final int UNKNOWN_VENDOR_ID = 0xFFFFFF; + public static final int POWER_STATUS_UNKNOWN = -1; public static final int POWER_STATUS_ON = 0; public static final int POWER_STATUS_STANDBY = 1; @@ -179,6 +181,7 @@ public final class HdmiCec { DEVICE_RECORDER, // ADDR_RECORDER_3 DEVICE_TUNER, // ADDR_TUNER_4 DEVICE_PLAYBACK, // ADDR_PLAYBACK_3 + DEVICE_TV, // ADDR_SPECIFIC_USE }; private static final String[] DEFAULT_NAMES = { @@ -194,6 +197,7 @@ public final class HdmiCec { "Recorder_3", "Tuner_4", "Playback_3", + "Secondary_TV", }; private HdmiCec() { } // Prevents instantiation. @@ -221,9 +225,7 @@ public final class HdmiCec { * @return true if the given address is valid */ public static boolean isValidAddress(int address) { - // TODO: We leave out the address 'free use(14)' for now. Check this later - // again to make sure it is a valid address for communication. - return (ADDR_TV <= address && address <= ADDR_PLAYBACK_3); + return (ADDR_TV <= address && address <= ADDR_SPECIFIC_USE); } /** diff --git a/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java new file mode 100644 index 0000000..9698445 --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java @@ -0,0 +1,168 @@ +/* + * 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.hardware.hdmi; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class to encapsulate device information for HDMI-CEC. This container + * include basic information such as logical address, physical address and + * device type, and additional information like vendor id and osd name. + */ +public final class HdmiCecDeviceInfo implements Parcelable { + // Logical address, phsical address, device type, vendor id and display name + // are immutable value. + private final int mLogicalAddress; + private final int mPhysicalAddress; + private final int mDeviceType; + private final int mVendorId; + private final String mDisplayName; + + + /** + * A helper class to deserialize {@link HdmiCecDeviceInfo} for a parcel. + */ + public static final Parcelable.Creator<HdmiCecDeviceInfo> CREATOR = + new Parcelable.Creator<HdmiCecDeviceInfo>() { + @Override + public HdmiCecDeviceInfo createFromParcel(Parcel source) { + int logicalAddress = source.readInt(); + int physicalAddress = source.readInt(); + int deviceType = source.readInt(); + int vendorId = source.readInt(); + String displayName = source.readString(); + return new HdmiCecDeviceInfo(logicalAddress, physicalAddress, deviceType, + vendorId, displayName); + } + + @Override + public HdmiCecDeviceInfo[] newArray(int size) { + return new HdmiCecDeviceInfo[size]; + } + }; + + /** + * Constructor. + * + * @param logicalAddress logical address of HDMI-Cec device. + * For more details, refer {@link HdmiCec} + * @param physicalAddress physical address of HDMI-Cec device + * @param deviceType type of device. For more details, refer {@link HdmiCec} + * @param vendorId vendor id of device. It's used for vendor specific command + * @param displayName name of device + * @hide + */ + public HdmiCecDeviceInfo(int logicalAddress, int physicalAddress, int deviceType, + int vendorId, String displayName) { + mLogicalAddress = logicalAddress; + mPhysicalAddress = physicalAddress; + mDeviceType = deviceType; + mDisplayName = displayName; + mVendorId = vendorId; + } + + /** + * Return the logical address of the device. It can have 0-15 values. + * For more details, refer constants between {@link HdmiCec#ADDR_TV} + * and {@link HdmiCec#ADDR_UNREGISTERED}. + */ + public int getLogicalAddress() { + return mLogicalAddress; + } + + /** + * Return the physical address of the device. + */ + public int getPhysicalAddress() { + return mPhysicalAddress; + } + + /** + * Return type of the device. For more details, refer constants between + * {@link HdmiCec#DEVICE_TV} and {@link HdmiCec#DEVICE_INACTIVE}. + */ + public int getDeviceType() { + return mDeviceType; + } + + /** + * Return display (OSD) name of the device. + */ + public String getDisplayName() { + return mDisplayName; + } + + /** + * Return vendor id of the device. Vendor id is used to distinguish devices + * built by other manufactures. This is required for vendor-specific command + * on CEC standard. + */ + public int getVendorId() { + return mVendorId; + } + + /** + * Describe the kinds of special objects contained in this Parcelable's + * marshalled representation. + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Serialize this object into a {@link Parcel}. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mLogicalAddress); + dest.writeInt(mPhysicalAddress); + dest.writeInt(mDeviceType); + dest.writeInt(mVendorId); + dest.writeString(mDisplayName); + } + + @Override + public String toString() { + StringBuffer s = new StringBuffer(); + s.append("logical_address: ").append(mLogicalAddress).append(", "); + s.append("physical_address: ").append(mPhysicalAddress).append(", "); + s.append("device_type: ").append(mDeviceType).append(", "); + s.append("vendor_id: ").append(mVendorId).append(", "); + s.append("display_name: ").append(mDisplayName); + return s.toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof HdmiCecDeviceInfo)) { + return false; + } + + HdmiCecDeviceInfo other = (HdmiCecDeviceInfo) obj; + return mLogicalAddress == other.mLogicalAddress + && mPhysicalAddress == other.mPhysicalAddress + && mDeviceType == other.mDeviceType + && mVendorId == other.mVendorId + && mDisplayName.equals(other.mDisplayName); + } +} diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java index be94d97..ddaf870 100644 --- a/core/java/android/hardware/hdmi/HdmiCecMessage.java +++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java @@ -19,6 +19,8 @@ package android.hardware.hdmi; import android.os.Parcel; import android.os.Parcelable; +import libcore.util.EmptyArray; + import java.util.Arrays; /** @@ -28,6 +30,8 @@ import java.util.Arrays; */ public final class HdmiCecMessage implements Parcelable { + public static final byte[] EMPTY_PARAM = EmptyArray.BYTE; + private static final int MAX_MESSAGE_LENGTH = 16; private final int mSource; diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 505ef9c..f6438b4 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -666,7 +666,8 @@ public class InputMethodService extends AbstractInputMethodService { mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); - mWindow = new SoftInputWindow(this, mTheme, mDispatcherState); + mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, + false); if (mHardwareAccelerated) { mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); } diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index df1afee..a9bace1 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -30,11 +30,20 @@ import android.view.WindowManager; * method window. It will be displayed along the edge of the screen, moving * the application user interface away from it so that the focused item is * always visible. + * @hide */ -class SoftInputWindow extends Dialog { +public class SoftInputWindow extends Dialog { + final String mName; + final Callback mCallback; + final KeyEvent.Callback mKeyEventCallback; final KeyEvent.DispatcherState mDispatcherState; + final boolean mTakesFocus; private final Rect mBounds = new Rect(); - + + public interface Callback { + public void onBackPressed(); + } + public void setToken(IBinder token) { WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.token = token; @@ -53,10 +62,15 @@ class SoftInputWindow extends Dialog { * using styles. This theme is applied on top of the current theme in * <var>context</var>. If 0, the default dialog theme will be used. */ - public SoftInputWindow(Context context, int theme, - KeyEvent.DispatcherState dispatcherState) { + public SoftInputWindow(Context context, String name, int theme, Callback callback, + KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState, + boolean takesFocus) { super(context, theme); + mName = name; + mCallback = callback; + mKeyEventCallback = keyEventCallback; mDispatcherState = dispatcherState; + mTakesFocus = takesFocus; initDockWindow(); } @@ -148,11 +162,47 @@ class SoftInputWindow extends Dialog { } } + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mKeyEventCallback != null && mKeyEventCallback.onKeyDown(keyCode, event)) { + return true; + } + return super.onKeyDown(keyCode, event); + } + + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (mKeyEventCallback != null && mKeyEventCallback.onKeyLongPress(keyCode, event)) { + return true; + } + return super.onKeyLongPress(keyCode, event); + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (mKeyEventCallback != null && mKeyEventCallback.onKeyUp(keyCode, event)) { + return true; + } + return super.onKeyUp(keyCode, event); + } + + public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { + if (mKeyEventCallback != null && mKeyEventCallback.onKeyMultiple(keyCode, count, event)) { + return true; + } + return super.onKeyMultiple(keyCode, count, event); + } + + public void onBackPressed() { + if (mCallback != null) { + mCallback.onBackPressed(); + } else { + super.onBackPressed(); + } + } + private void initDockWindow() { WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD; - lp.setTitle("InputMethod"); + lp.setTitle(mName); lp.gravity = Gravity.BOTTOM; lp.width = -1; @@ -161,11 +211,19 @@ class SoftInputWindow extends Dialog { //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; getWindow().setAttributes(lp); - getWindow().setFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + + int windowSetFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + int windowModFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | - WindowManager.LayoutParams.FLAG_DIM_BEHIND); + WindowManager.LayoutParams.FLAG_DIM_BEHIND; + + if (!mTakesFocus) { + windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + } else { + windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + } + + getWindow().setFlags(windowSetFlags, windowModFlags); } } diff --git a/core/java/android/net/INetworkScoreCache.aidl b/core/java/android/net/INetworkScoreCache.aidl new file mode 100644 index 0000000..35601ce --- /dev/null +++ b/core/java/android/net/INetworkScoreCache.aidl @@ -0,0 +1,43 @@ +/** + * 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.net; + +import android.net.ScoredNetwork; + +/** + * A service which stores a subset of scored networks from the active network scorer. + * + * <p>To be implemented by network subsystems (e.g. Wi-Fi). NetworkScoreService will propagate + * scores down to each subsystem depending on the network type. Implementations may register for + * a given network type by calling NetworkScoreManager.registerNetworkSubsystem. + * + * <p>A proper implementation should throw SecurityException whenever the caller is not privileged. + * It may request scores by calling NetworkScoreManager#requestScores(NetworkKey[]); a call to + * updateScores may follow but may not depending on the active scorer's implementation, and in + * general this method may be called at any time. + * + * <p>Implementations should also override dump() so that "adb shell dumpsys network_score" includes + * the current scores for each network for debugging purposes. + * @hide + */ +interface INetworkScoreCache +{ + void updateScores(in List<ScoredNetwork> networks); + + void clearScores(); +} + diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl index a72d9a0..626bd2a 100644 --- a/core/java/android/net/INetworkScoreService.aidl +++ b/core/java/android/net/INetworkScoreService.aidl @@ -16,6 +16,7 @@ package android.net; +import android.net.INetworkScoreCache; import android.net.ScoredNetwork; /** @@ -34,8 +35,7 @@ interface INetworkScoreService /** * Clear all scores. * @return whether the clear was successful. - * @throws SecurityException if the caller is neither the current active scorer nor the scorer - * manager. + * @throws SecurityException if the caller is neither the current active scorer nor the system. */ boolean clearScores(); @@ -43,7 +43,19 @@ interface INetworkScoreService * Set the active scorer and clear existing scores. * @param packageName the package name of the new scorer to use. * @return true if the operation succeeded, or false if the new package is not a valid scorer. - * @throws SecurityException if the caller is not the scorer manager. + * @throws SecurityException if the caller is not the system. */ boolean setActiveScorer(in String packageName); + + /** + * Register a network subsystem for scoring. + * + * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}. + * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores. + * @throws SecurityException if the caller is not the system. + * @throws IllegalArgumentException if a score cache is already registed for this type. + * @hide + */ + void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache); + } diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index a470e88..30b61c5 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -54,7 +54,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class MobileDataStateTracker extends BaseNetworkStateTracker { private static final String TAG = "MobileDataStateTracker"; - private static final boolean DBG = true; + private static final boolean DBG = false; private static final boolean VDBG = false; private PhoneConstants.DataState mMobileDataState; diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 6dd56d9..352512e 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; +import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -101,7 +102,7 @@ public class NetworkScoreManager { * determine the current scorer and offer the user the ability to select a different scorer via * the {@link #ACTION_CHANGE_ACTIVE} intent. * @return the full package name of the current active scorer, or null if there is no active - * scorer. + * scorer. */ public String getActiveScorerPackage() { return NetworkScorerAppManager.getActiveScorer(mContext); @@ -151,8 +152,8 @@ public class NetworkScoreManager { * * @return true if the operation succeeded, or false if the new package is not a valid scorer. * @throws SecurityException if the caller does not hold the - * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission indicating that - * it can manage scorer applications. + * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission indicating + * that it can manage scorer applications. * @hide */ public boolean setActiveScorer(String packageName) throws SecurityException { @@ -162,4 +163,44 @@ public class NetworkScoreManager { return false; } } + + /** + * Request scoring for networks. + * + * <p>Note that this is just a helper method to assemble the broadcast, and will run in the + * calling process. + * + * @return true if the broadcast was sent, or false if there is no active scorer. + * @throws SecurityException if the caller does not hold the + * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission. + * @hide + */ + public boolean requestScores(NetworkKey[] networks) throws SecurityException { + String activeScorer = getActiveScorerPackage(); + if (activeScorer == null) { + return false; + } + Intent intent = new Intent(ACTION_SCORE_NETWORKS); + intent.setPackage(activeScorer); + intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks); + mContext.sendBroadcast(intent); + return true; + } + + /** + * Register a network score cache. + * + * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}. + * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores. + * @throws SecurityException if the caller does not hold the + * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission. + * @throws IllegalArgumentException if a score cache is already registered for this type. + * @hide + */ + public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { + try { + mService.registerNetworkScoreCache(networkType, scoreCache); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index 033332c..bea8d1c 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -274,48 +274,6 @@ public final class Proxy { return PROXY_VALID; } - static class AndroidProxySelectorRoutePlanner - extends org.apache.http.impl.conn.ProxySelectorRoutePlanner { - - private Context mContext; - - public AndroidProxySelectorRoutePlanner(SchemeRegistry schreg, ProxySelector prosel, - Context context) { - super(schreg, prosel); - mContext = context; - } - - @Override - protected java.net.Proxy chooseProxy(List<java.net.Proxy> proxies, HttpHost target, - HttpRequest request, HttpContext context) { - return getProxy(mContext, target.getHostName()); - } - - @Override - protected HttpHost determineProxy(HttpHost target, HttpRequest request, - HttpContext context) { - return getPreferredHttpHost(mContext, target.getHostName()); - } - - @Override - public HttpRoute determineRoute(HttpHost target, HttpRequest request, - HttpContext context) { - HttpHost proxy = getPreferredHttpHost(mContext, target.getHostName()); - if (proxy == null) { - return new HttpRoute(target); - } else { - return new HttpRoute(target, null, proxy, false); - } - } - } - - /** @hide */ - public static final HttpRoutePlanner getAndroidProxySelectorRoutePlanner(Context context) { - AndroidProxySelectorRoutePlanner ret = new AndroidProxySelectorRoutePlanner( - new SchemeRegistry(), ProxySelector.getDefault(), context); - return ret; - } - /** @hide */ public static final void setHttpProxySystemProperty(ProxyProperties p) { String host = null; diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java index 33e81c2..dd744d3 100644 --- a/core/java/android/net/RssiCurve.java +++ b/core/java/android/net/RssiCurve.java @@ -98,6 +98,27 @@ public class RssiCurve implements Parcelable { } /** + * Lookup the score for a given RSSI value. + * + * @param rssi The RSSI to lookup. If the RSSI falls below the start of the curve, the score at + * the start of the curve will be returned. If it falls after the end of the curve, the + * score at the end of the curve will be returned. + * @return the score for the given RSSI. + */ + public byte lookupScore(int rssi) { + int index = (rssi - start) / bucketWidth; + + // Snap the index to the closest bucket if it falls outside the curve. + if (index < 0) { + index = 0; + } else if (index > rssiBuckets.length - 1) { + index = rssiBuckets.length - 1; + } + + return rssiBuckets[index]; + } + + /** * Determine if two RSSI curves are defined in the same way. * * <p>Note that two curves can be equivalent but defined differently, e.g. if one bucket in one diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index f05ddde..e78ce33 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -173,6 +173,10 @@ public abstract class BatteryStats implements Parcelable { private static final String BLUETOOTH_STATE_COUNT_DATA = "bsc"; private static final String POWER_USE_SUMMARY_DATA = "pws"; private static final String POWER_USE_ITEM_DATA = "pwi"; + private static final String DISCHARGE_STEP_DATA = "dsd"; + private static final String CHARGE_STEP_DATA = "csd"; + private static final String DISCHARGE_TIME_REMAIN_DATA = "dtr"; + private static final String CHARGE_TIME_REMAIN_DATA = "ctr"; private final StringBuilder mFormatBuilder = new StringBuilder(32); private final Formatter mFormatter = new Formatter(mFormatBuilder); @@ -907,6 +911,8 @@ public abstract class BatteryStats implements Parcelable { */ public abstract int getScreenOnCount(int which); + public abstract long getInteractiveTime(long elapsedRealtimeUs, int which); + public static final int SCREEN_BRIGHTNESS_DARK = 0; public static final int SCREEN_BRIGHTNESS_DIM = 1; public static final int SCREEN_BRIGHTNESS_MEDIUM = 2; @@ -932,8 +938,6 @@ public abstract class BatteryStats implements Parcelable { public abstract long getScreenBrightnessTime(int brightnessBin, long elapsedRealtimeUs, int which); - public abstract int getInputEventCount(int which); - /** * Returns the time in microseconds that the phone has been on while the device was * running on battery. @@ -1340,6 +1344,18 @@ public abstract class BatteryStats implements Parcelable { public abstract long computeBatteryTimeRemaining(long curTime); /** + * Return the historical number of discharge steps we currently have. + */ + public abstract int getNumDischargeStepDurations(); + + /** + * Return the array of discharge step durations; the number of valid + * items in it is returned by {@link #getNumDischargeStepDurations()}. + * These values are in milliseconds. + */ + public abstract long[] getDischargeStepDurationsArray(); + + /** * Compute an approximation for how much time (in microseconds) remains until the battery * is fully charged. Returns -1 if no time can be computed: either there is not * enough current data to make a decision, or the battery is currently @@ -1349,6 +1365,18 @@ public abstract class BatteryStats implements Parcelable { */ public abstract long computeChargeTimeRemaining(long curTime); + /** + * Return the historical number of charge steps we currently have. + */ + public abstract int getNumChargeStepDurations(); + + /** + * Return the array of charge step durations; the number of valid + * items in it is returned by {@link #getNumChargeStepDurations()}. + * These values are in milliseconds. + */ + public abstract long[] getChargeStepDurationsArray(); + public abstract Map<String, ? extends LongCounter> getWakeupReasonStats(); public abstract Map<String, ? extends Timer> getKernelWakelockStats(); @@ -1543,6 +1571,7 @@ public abstract class BatteryStats implements Parcelable { final long totalRealtime = computeRealtime(rawRealtime, which); final long totalUptime = computeUptime(rawUptime, which); final long screenOnTime = getScreenOnTime(rawRealtime, which); + final long interactiveTime = getInteractiveTime(rawRealtime, which); final long phoneOnTime = getPhoneOnTime(rawRealtime, which); final long wifiOnTime = getWifiOnTime(rawRealtime, which); final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); @@ -1611,8 +1640,8 @@ public abstract class BatteryStats implements Parcelable { wifiRunningTime / 1000, bluetoothOnTime / 1000, mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes, fullWakeLockTimeTotal, partialWakeLockTimeTotal, - getInputEventCount(which), getMobileRadioActiveTime(rawRealtime, which), - getMobileRadioActiveAdjustedTime(which)); + 0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which), + getMobileRadioActiveAdjustedTime(which), interactiveTime / 1000); // Dump screen brightness stats Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS]; @@ -1985,6 +2014,7 @@ public abstract class BatteryStats implements Parcelable { sb.append("realtime, "); formatTimeMs(sb, totalUptime / 1000); sb.append("uptime"); + pw.println(sb.toString()); if (batteryTimeRemaining >= 0) { sb.setLength(0); sb.append(prefix); @@ -2003,16 +2033,25 @@ public abstract class BatteryStats implements Parcelable { pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString()); final long screenOnTime = getScreenOnTime(rawRealtime, which); + final long interactiveTime = getInteractiveTime(rawRealtime, which); final long phoneOnTime = getPhoneOnTime(rawRealtime, which); final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); final long wifiOnTime = getWifiOnTime(rawRealtime, which); final long bluetoothOnTime = getBluetoothOnTime(rawRealtime, which); sb.setLength(0); sb.append(prefix); + sb.append(" Interactive: "); formatTimeMs(sb, interactiveTime / 1000); + sb.append("("); sb.append(formatRatioLocked(interactiveTime, whichBatteryRealtime)); + sb.append(")"); + pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); sb.append(" Screen on: "); formatTimeMs(sb, screenOnTime / 1000); sb.append("("); sb.append(formatRatioLocked(screenOnTime, whichBatteryRealtime)); sb.append(") "); sb.append(getScreenOnCount(which)); - sb.append("x, Input events: "); sb.append(getInputEventCount(which)); + sb.append("x, Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000); + sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime)); + sb.append(")"); pw.println(sb.toString()); if (phoneOnTime != 0) { sb.setLength(0); @@ -2871,7 +2910,7 @@ public abstract class BatteryStats implements Parcelable { } } if (!didWake && wakelockTag != null) { - pw.print(longNames ? "wake_lock=" : "w="); + pw.print(longNames ? " wake_lock=" : ",w="); if (longNames) { UserHandle.formatUid(pw, wakelockTag.uid); pw.print(":\""); @@ -3050,7 +3089,7 @@ public abstract class BatteryStats implements Parcelable { HISTORY_STATE2_DESCRIPTIONS, !checkin); if (rec.wakeReasonTag != null) { if (checkin) { - pw.print(",Wr="); + pw.print(",wr="); pw.print(rec.wakeReasonTag.poolIdx); } else { pw.print(" wake_reason="); @@ -3120,6 +3159,28 @@ public abstract class BatteryStats implements Parcelable { pw.print(suffix); } + private static boolean dumpDurationSteps(PrintWriter pw, String header, long[] steps, + int count, boolean checkin) { + if (count <= 0) { + return false; + } + if (!checkin) { + pw.println(header); + } + String[] lineArgs = new String[1]; + for (int i=0; i<count; i++) { + if (checkin) { + lineArgs[0] = Long.toString(steps[i]); + dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs); + } else { + pw.print(" #"); pw.print(i); pw.print(": "); + TimeUtils.formatDuration(steps[i], pw); + pw.println(); + } + } + return true; + } + public static final int DUMP_UNPLUGGED_ONLY = 1<<0; public static final int DUMP_CHARGED_ONLY = 1<<1; public static final int DUMP_HISTORY_ONLY = 1<<2; @@ -3239,7 +3300,27 @@ public abstract class BatteryStats implements Parcelable { } } if (didPid) { - pw.println(""); + pw.println(); + } + if (dumpDurationSteps(pw, "Discharge step durations:", getDischargeStepDurationsArray(), + getNumDischargeStepDurations(), false)) { + long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime()); + if (timeRemaining >= 0) { + pw.print(" Estimated discharge time remaining: "); + TimeUtils.formatDuration(timeRemaining / 1000, pw); + pw.println(); + } + pw.println(); + } + if (dumpDurationSteps(pw, "Charge step durations:", getChargeStepDurationsArray(), + getNumChargeStepDurations(), false)) { + long timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime()); + if (timeRemaining >= 0) { + pw.print(" Estimated charge time remaining: "); + TimeUtils.formatDuration(timeRemaining / 1000, pw); + pw.println(); + } + pw.println(); } } @@ -3248,7 +3329,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(" System starts: " + getStartCount() + ", currently on battery: " + getIsOnBattery()); dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid); - pw.println(""); + pw.println(); } if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) { pw.println("Statistics since last unplugged:"); @@ -3352,6 +3433,25 @@ public abstract class BatteryStats implements Parcelable { } } } + if (!filtering) { + dumpDurationSteps(pw, DISCHARGE_STEP_DATA, getDischargeStepDurationsArray(), + getNumDischargeStepDurations(), true); + String[] lineArgs = new String[1]; + long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime()); + if (timeRemaining >= 0) { + lineArgs[0] = Long.toString(timeRemaining); + dumpLine(pw, 0 /* uid */, "i" /* category */, DISCHARGE_TIME_REMAIN_DATA, + (Object[])lineArgs); + } + dumpDurationSteps(pw, CHARGE_STEP_DATA, getChargeStepDurationsArray(), + getNumChargeStepDurations(), true); + timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime()); + if (timeRemaining >= 0) { + lineArgs[0] = Long.toString(timeRemaining); + dumpLine(pw, 0 /* uid */, "i" /* category */, CHARGE_TIME_REMAIN_DATA, + (Object[])lineArgs); + } + } if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) { dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1); } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 0336dd6..1ca6b90 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -81,12 +81,38 @@ public class Build { public static final String SERIAL = getString("ro.serialno"); /** - * A list of ABIs (in priority) order supported by this device. + * An ordered list of ABIs supported by this device. The most preferred ABI is the first + * element in the list. + * + * See {@link #SUPPORTED_32_BIT_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}. * * @hide */ public static final String[] SUPPORTED_ABIS = getString("ro.product.cpu.abilist").split(","); + /** + * An ordered list of <b>32 bit</b> ABIs supported by this device. The most preferred ABI + * is the first element in the list. + * + * See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}. + * + * @hide + */ + public static final String[] SUPPORTED_32_BIT_ABIS = getString("ro.product.cpu.abilist32") + .split(","); + + /** + * An ordered list of <b>64 bit</b> ABIs supported by this device. The most preferred ABI + * is the first element in the list. + * + * See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_32_BIT_ABIS}. + * + * @hide + */ + public static final String[] SUPPORTED_64_BIT_ABIS = getString("ro.product.cpu.abilist64") + .split(","); + + /** Various version strings. */ public static class VERSION { /** diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index c3f7370..899a958 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -29,6 +29,7 @@ import android.graphics.Bitmap; interface IUserManager { UserInfo createUser(in String name, int flags); UserInfo createProfileForUser(in String name, int flags, int userHandle); + void setUserEnabled(int userHandle); boolean removeUser(int userHandle); void setUserName(int userHandle, String name); void setUserIcon(int userHandle, in Bitmap icon); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index b4ed68c..1b3aa0a 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -707,16 +707,6 @@ public class Process { return primaryZygoteState; } - // TODO: Get rid of this. This is a temporary workaround until all the - // compilation related pieces for the dual zygote stack are ready. - // b/3647418. - if (System.getenv("ANDROID_SOCKET_" + SECONDARY_ZYGOTE_SOCKET) == null) { - Log.e(LOG_TAG, "Forcing app to primary zygote, secondary unavailable (ABI= " + abi + ")"); - // Should be : - // throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi); - return primaryZygoteState; - } - // The primary zygote didn't match. Try the secondary. if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { secondaryZygoteState = ZygoteState.connect(SECONDARY_ZYGOTE_SOCKET, diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 1fe9337..84639eb 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -91,7 +91,6 @@ public class UserManager { * @see #setUserRestrictions(Bundle) * @see #getUserRestrictions() */ - public static final String DISALLOW_SHARE_LOCATION = "no_share_location"; /** @@ -145,6 +144,96 @@ public class UserManager { */ public static final String DISALLOW_REMOVE_USER = "no_remove_user"; + /** + * Key for user restrictions. Specifies if a user is disallowed from enabling or + * accessing debugging features. The default value is <code>false</code>. + * <p/> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features"; + + /** + * Key for user restrictions. Specifies if a user is disallowed from configuring VPN. + * The default value is <code>false</code>. + * <p/> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_VPN = "no_config_vpn"; + + /** + * Key for user restrictions. Specifies if a user is disallowed from configuring Tethering + * & portable hotspots. The default value is <code>false</code>. + * <p/> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_TETHERING = "no_config_tethering"; + + /** + * Key for user restrictions. Specifies if a user is disallowed from factory resetting + * from Settings. + * The default value is <code>false</code>. + * <p> + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_FACTORY_RESET = "no_factory_reset"; + + /** + * Key for user restrictions. Specifies if a user is disallowed from adding new users and + * profiles. The default value is <code>false</code>. + * <p> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_ADD_USER = "no_add_user"; + + /** + * Key for user restrictions. Specifies if a user is disallowed from disabling application + * verification. The default value is <code>false</code>. + * <p> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps"; + + /** + * Key for user restrictions. Specifies if a user is disallowed from configuring cell + * broadcasts. The default value is <code>false</code>. + * <p> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts"; + + /** + * Key for user restrictions. Specifies if a user is disallowed from configuring mobile + * networks. The default value is <code>false</code>. + * <p> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks"; + + /** + * Key for user restrictions. Specifies if a user is disallowed from configuring + * applications in Settings. The default value is <code>false</code>. + * <p> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_APPS = "no_config_apps"; + /** @hide */ public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3; /** @hide */ @@ -437,6 +526,22 @@ public class UserManager { } /** + * Sets the user as enabled, if such an user exists. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * Note that the default is true, it's only that managed profiles might not be enabled. + * + * @param userHandle the id of the profile to enable + * @hide + */ + public void setUserEnabled(int userHandle) { + try { + mService.setUserEnabled(userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Could not enable the profile", e); + } + } + + /** * Return the number of users currently created on the device. */ public int getUserCount() { @@ -488,8 +593,7 @@ public class UserManager { ArrayList<UserHandle> profiles = new ArrayList<UserHandle>(); List<UserInfo> users = new ArrayList<UserInfo>(); try { - // TODO: Switch enabledOnly to true once client apps are updated - users = mService.getProfiles(UserHandle.myUserId(), false /* enabledOnly */); + users = mService.getProfiles(UserHandle.myUserId(), true /* enabledOnly */); } catch (RemoteException re) { Log.w(TAG, "Could not get user list", re); return null; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ab06230..d5a3bcb 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3480,6 +3480,12 @@ public final class Settings { "lock_screen_appwidget_ids"; /** + * List of enrolled fingerprint identifiers (comma-delimited). + * @hide + */ + public static final String USER_FINGERPRINT_IDS = "user_fingerprint_ids"; + + /** * Id of the appwidget shown on the lock screen when appwidgets are disabled. * @hide */ @@ -4395,6 +4401,13 @@ public final class Settings { public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; /** + * (Experimental). If nonzero, WebView uses data reduction proxy to save network + * bandwidth. Otherwise, WebView does not use data reduction proxy. + * @hide + */ + public static final String WEBVIEW_DATA_REDUCTION_PROXY = "webview_data_reduction_proxy"; + + /** * The {@link ComponentName} string of the service to be used as the voice recognition * service. * diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java index 233e0ca..62252be 100644 --- a/core/java/android/provider/TvContract.java +++ b/core/java/android/provider/TvContract.java @@ -16,9 +16,13 @@ package android.provider; +import android.content.ComponentName; +import android.content.ContentResolver; import android.content.ContentUris; import android.net.Uri; +import java.util.List; + /** * <p> * The contract between the TV provider and applications. Contains definitions for the supported @@ -42,6 +46,35 @@ public final class TvContract { /** The authority for the TV provider. */ public static final String AUTHORITY = "com.android.tv"; + private static final String PATH_CHANNEL = "channel"; + private static final String PATH_PROGRAM = "program"; + private static final String PATH_INPUT = "input"; + + /** + * An optional query, update or delete URI parameter that allows the caller to specify start + * time (in milliseconds since the epoch) to filter programs. + * + * @hide + */ + public static final String PARAM_START_TIME = "start_time"; + + /** + * An optional query, update or delete URI parameter that allows the caller to specify end time + * (in milliseconds since the epoch) to filter programs. + * + * @hide + */ + public static final String PARAM_END_TIME = "end_time"; + + /** + * A query, update or delete URI parameter that allows the caller to operate on all or + * browsable-only channels. If set to "true", the rows that contain non-browsable channels are + * not affected. + * + * @hide + */ + public static final String PARAM_BROWSABLE_ONLY = "browable_only"; + /** * Builds a URI that points to a specific channel. * @@ -52,6 +85,32 @@ public final class TvContract { } /** + * Builds a URI that points to all browsable channels from a given TV input. + * + * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements + * the given TV input. + */ + public static final Uri buildChannelsUriForInput(ComponentName name) { + return buildChannelsUriForInput(name, true); + } + + /** + * Builds a URI that points to all or browsable-only channels from a given TV input. + * + * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements + * the given TV input. + * @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set + * to {@code false} the URI points to all channels regardless of whether they are + * browsable or not. + */ + public static final Uri buildChannelsUriForInput(ComponentName name, boolean browsableOnly) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY) + .appendPath(PATH_INPUT).appendPath(name.getPackageName()) + .appendPath(name.getClassName()).appendPath(PATH_CHANNEL) + .appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build(); + } + + /** * Builds a URI that points to a specific program. * * @param programId The ID of the program to point to. @@ -61,6 +120,37 @@ public final class TvContract { } /** + * Builds a URI that points to all programs on a given channel. + * + * @param channelUri The URI of the channel to return programs for. + */ + public static final Uri buildProgramsUriForChannel(Uri channelUri) { + if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) { + throw new IllegalArgumentException("Not a channel: " + channelUri); + } + String channelId = String.valueOf(ContentUris.parseId(channelUri)); + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY) + .appendPath(PATH_CHANNEL).appendPath(channelId).appendPath(PATH_PROGRAM).build(); + } + + /** + * Builds a URI that points to programs on a specific channel whose schedules overlap with the + * given time frame. + * + * @param channelUri The URI of the channel to return programs for. + * @param startTime The start time used to filter programs. The returned programs should have + * {@link Programs#END_TIME_UTC_MILLIS} that is greater than this time. + * @param endTime The end time used to filter programs. The returned programs should have + * {@link Programs#START_TIME_UTC_MILLIS} that is less than this time. + */ + public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime, + long endTime) { + Uri uri = buildProgramsUriForChannel(channelUri); + return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime)) + .appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build(); + } + + /** * Builds a URI that points to a specific program the user watched. * * @param watchedProgramId The ID of the watched program to point to. @@ -70,6 +160,61 @@ public final class TvContract { return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId); } + /** + * Extracts the {@link Channels#PACKAGE_NAME} from a given URI. + * + * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or + * {@link #buildChannelsUriForInput(ComponentName, boolean)}. + * @hide + */ + public static final String getPackageName(Uri channelsUri) { + final List<String> paths = channelsUri.getPathSegments(); + if (paths.size() < 4) { + throw new IllegalArgumentException("Not channels: " + channelsUri); + } + if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) { + throw new IllegalArgumentException("Not channels: " + channelsUri); + } + return paths.get(1); + } + + /** + * Extracts the {@link Channels#SERVICE_NAME} from a given URI. + * + * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or + * {@link #buildChannelsUriForInput(ComponentName, boolean)}. + * @hide + */ + public static final String getServiceName(Uri channelsUri) { + final List<String> paths = channelsUri.getPathSegments(); + if (paths.size() < 4) { + throw new IllegalArgumentException("Not channels: " + channelsUri); + } + if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) { + throw new IllegalArgumentException("Not channels: " + channelsUri); + } + return paths.get(2); + } + + /** + * Extracts the {@link Channels#_ID} from a given URI. + * + * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or + * {@link #buildProgramsUriForChannel(Uri, long, long)}. + * @hide + */ + public static final String getChannelId(Uri programsUri) { + final List<String> paths = programsUri.getPathSegments(); + if (paths.size() < 3) { + throw new IllegalArgumentException("Not programs: " + programsUri); + } + if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) { + throw new IllegalArgumentException("Not programs: " + programsUri); + } + return paths.get(1); + } + + private TvContract() {} /** @@ -93,7 +238,8 @@ public final class TvContract { public static final class Channels implements BaseTvColumns { /** The content:// style URI for this table. */ - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/channel"); + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + + PATH_CHANNEL); /** The MIME type of a directory of TV channels. */ public static final String CONTENT_TYPE = @@ -276,7 +422,8 @@ public final class TvContract { public static final class Programs implements BaseTvColumns { /** The content:// style URI for this table. */ - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/program"); + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + + PATH_PROGRAM); /** The MIME type of a directory of TV programs. */ public static final String CONTENT_TYPE = diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 2303d65..b02a79d 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -300,10 +300,6 @@ public class DreamService extends Service implements Window.Callback { public void onDetachedFromWindow() { } - @Override - public void onWindowDismissed() { - } - /** {@inheritDoc} */ @Override public void onPanelClosed(int featureId, Menu menu) { diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java new file mode 100644 index 0000000..0d14c59 --- /dev/null +++ b/core/java/android/service/fingerprint/FingerprintManager.java @@ -0,0 +1,200 @@ +/** + * 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.service.fingerprint; + +import android.app.ActivityManagerNative; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +/** + * A class that coordinates access to the fingerprint hardware. + */ + +public class FingerprintManager { + private static final String TAG = "FingerprintManager"; + protected static final boolean DEBUG = true; + private static final String FINGERPRINT_SERVICE_PACKAGE = "com.android.service.fingerprint"; + private static final String FINGERPRINT_SERVICE_CLASS = + "com.android.service.fingerprint.FingerprintService"; + private static final int MSG_ENROLL_RESULT = 100; + private static final int MSG_SCANNED = 101; + private static final int MSG_ERROR = 102; + private static final int MSG_REMOVED = 103; + + public static final int FINGERPRINT_ERROR_NO_RECEIVER = -10; + public static final int FINGERPRINT_ERROR = -1; // One of the error messages below. + + // Progress messages. + public static final int FINGERPRINT_SCANNED = 1; + public static final int FINGERPRINT_TEMPLATE_ENROLLING = 2; + public static final int FINGERPRINT_TEMPLATE_REMOVED = 4; + + // Error messages. Must agree with fingerprint HAL definitions. + public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; + public static final int FINGERPRINT_ERROR_BAD_CAPTURE = 2; + public static final int FINGERPRINT_ERROR_TIMEOUT = 3; + public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + + private IFingerprintService mService; + private FingerprintManagerReceiver mClientReceiver; + + private Handler mHandler = new Handler() { + public void handleMessage(android.os.Message msg) { + if (mClientReceiver != null) { + switch(msg.what) { + case MSG_ENROLL_RESULT: + mClientReceiver.onEnrollResult(msg.arg1, msg.arg2); + break; + case MSG_SCANNED: + mClientReceiver.onScanned(msg.arg1, msg.arg2); + break; + case MSG_ERROR: + mClientReceiver.onError(msg.arg1); + break; + case MSG_REMOVED: + mClientReceiver.onRemoved(msg.arg1); + } + } + } + }; + + public FingerprintManager(Context context) { + // Connect to service... + Intent intent = new Intent(); + intent.setClassName(FINGERPRINT_SERVICE_PACKAGE, FINGERPRINT_SERVICE_CLASS); + if (!context.bindServiceAsUser(intent, mFingerprintConnection, + Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF)) { + if (DEBUG) Log.v(TAG, "Can't bind to " + FINGERPRINT_SERVICE_CLASS); + } + } + + private final ServiceConnection mFingerprintConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) Log.v(TAG, "Connected to FingerprintService"); + mService = IFingerprintService.Stub.asInterface(service); + try { + mService.startListening(mServiceReceiver, getCurrentUserId()); + } catch (RemoteException e) { + if (DEBUG) Log.v(TAG, "Failed to set callback", e); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Log.v(TAG, "Disconnected from FingerprintService"); + mService = null; + } + }; + + private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { + + public void onEnrollResult(int fingerprintId, int remaining) { + mHandler.obtainMessage(MSG_ENROLL_RESULT, fingerprintId, remaining).sendToTarget(); + } + + public void onScanned(int fingerprintId, int confidence) { + mHandler.obtainMessage(MSG_SCANNED, fingerprintId, confidence) + .sendToTarget();; + } + + public void onError(int error) { + mHandler.obtainMessage(MSG_ERROR, error, 0).sendToTarget(); + } + + public void onRemoved(int fingerprintId) { + mHandler.obtainMessage(MSG_REMOVED, fingerprintId, 0).sendToTarget(); + } + }; + + /** + * Start the enrollment process. Timeout dictates how long to wait for the user to + * enroll a fingerprint. + * + * @param timeout + */ + public void enroll(long timeout) { + if (mServiceReceiver == null) { + throw new IllegalStateException("enroll: Call registerCallback() first"); + } + if (mService != null) try { + mService.enroll(timeout, getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception while enrolling: ", e); + } + } + + /** + * Remove the given fingerprintId from the system. FingerprintId of 0 has special meaning + * which is to delete all fingerprint data for the current user. Use with caution. + * @param fingerprintId + */ + public void remove(int fingerprintId) { + if (mService != null) try { + mService.remove(fingerprintId, getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e); + } + } + + /** + * Starts listening for fingerprint events. When a finger is scanned or recognized, the + * client will be notified via the callback. + */ + public void startListening(FingerprintManagerReceiver receiver) { + mClientReceiver = receiver; + if (mService != null) { + try { + mService.startListening(mServiceReceiver, getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception in startListening(): ", e); + } + } + } + + private int getCurrentUserId() { + try { + return ActivityManagerNative.getDefault().getCurrentUser().id; + } catch (RemoteException e) { + Log.w(TAG, "Failed to get current user id\n"); + return UserHandle.USER_NULL; + } + } + + /** + * Stops the client from listening to fingerprint events. + */ + public void stopListening() { + mClientReceiver = null; + if (mService != null) { + try { + mService.stopListening(getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception in stopListening(): ", e); + } + } else { + Log.w(TAG, "stopListening(): Service not connected!"); + } + } +}
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java new file mode 100644 index 0000000..34f1655 --- /dev/null +++ b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java @@ -0,0 +1,59 @@ +package android.service.fingerprint; +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class FingerprintManagerReceiver { + /** + * Fingerprint enrollment progress update. Enrollment is considered complete if + * remaining hits 0 without {@link #onError(int)} being called. + * + * @param fingerprintId the fingerprint we're currently enrolling + * @param remaining the number of samples required to complete enrollment. It's up to + * the hardware to define what each step in enrollment means. Some hardware + * requires multiple samples of the same part of the finger. Others require sampling of + * different parts of the finger. The enrollment flow can use remaining to + * mean "step x" of the process or "just need another sample." + */ + public void onEnrollResult(int fingerprintId, int remaining) { } + + /** + * Fingerprint scan detected. Most clients will use this function to detect a fingerprint + * + * @param fingerprintId is the finger the hardware has detected. + * @param confidence from 0 (no confidence) to 65535 (high confidence). Fingerprint 0 has + * special meaning - the finger wasn't recognized. + */ + public void onScanned(int fingerprintId, int confidence) { } + + /** + * An error was detected during scan or enrollment. One of + * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}, + * {@link FingerprintManager#FINGERPRINT_ERROR_BAD_CAPTURE} or + * {@link FingerprintManager#FINGERPRINT_ERROR_TIMEOUT} + * {@link FingerprintManager#FINGERPRINT_ERROR_NO_SPACE} + * + * @param error one of the above error codes + */ + public void onError(int error) { } + + /** + * The given fingerprint template was successfully removed by the driver. + * See {@link FingerprintManager#remove(int)} + * + * @param fingerprintId id of template to remove. + */ + public void onRemoved(int fingerprintId) { } +}
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintService.java b/core/java/android/service/fingerprint/FingerprintService.java new file mode 100644 index 0000000..c7fa7cd --- /dev/null +++ b/core/java/android/service/fingerprint/FingerprintService.java @@ -0,0 +1,219 @@ +/** + * 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.service.fingerprint; + +import android.app.Service; +import android.content.ContentResolver; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.Slog; + +import java.io.PrintWriter; +import java.util.HashMap; + +/** + * A service to manage multiple clients that want to access the fingerprint HAL API. + * The service is responsible for maintaining a list of clients and dispatching all + * fingerprint -related events. + * + * @hide + */ +public class FingerprintService extends Service { + private final String TAG = FingerprintService.class.getSimpleName() + + "[" + getClass().getSimpleName() + "]"; + private static final boolean DEBUG = true; + HashMap<IFingerprintServiceReceiver, ClientData> mClients = + new HashMap<IFingerprintServiceReceiver, ClientData>(); + + private static final int MSG_NOTIFY = 10; + + Handler mHandler = new Handler() { + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case MSG_NOTIFY: + handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj); + break; + + default: + Slog.w(TAG, "Unknown message:" + msg.what); + } + } + }; + + private static final int STATE_IDLE = 0; + private static final int STATE_LISTENING = 1; + private static final int STATE_ENROLLING = 2; + private static final int STATE_DELETING = 3; + private static final long MS_PER_SEC = 1000; + + private static final class ClientData { + public IFingerprintServiceReceiver receiver; + int state; + int userId; + } + + @Override + public final IBinder onBind(Intent intent) { + if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent); + return new FingerprintServiceWrapper(); + } + + // JNI methods to communicate from FingerprintManagerService to HAL + native int nativeEnroll(int timeout); + native int nativeRemove(int fingerprintId); + + // JNI methods for communicating from HAL to clients + void notify(int msg, int arg1, int arg2) { + mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget(); + } + + void handleNotify(int msg, int arg1, int arg2) { + for (int i = 0; i < mClients.size(); i++) { + ClientData clientData = mClients.get(i); + switch (msg) { + case FingerprintManager.FINGERPRINT_ERROR: { + if (clientData.state != STATE_IDLE) { + // FINGERPRINT_ERROR_HW_UNAVAILABLE + // FINGERPRINT_ERROR_BAD_CAPTURE + // FINGERPRINT_ERROR_TIMEOUT + // FINGERPRINT_ERROR_NO_SPACE + final int error = arg1; + clientData.state = STATE_IDLE; + if (clientData.receiver != null) { + try { + clientData.receiver.onError(error); + } catch (RemoteException e) { + Slog.e(TAG, "can't send message to client. Did it die?", e); + } + } + } + } + break; + case FingerprintManager.FINGERPRINT_SCANNED: { + final int fingerId = arg1; + final int confidence = arg2; + if (clientData.state == STATE_LISTENING && clientData.receiver != null) { + try { + clientData.receiver.onScanned(fingerId, confidence); + } catch (RemoteException e) { + Slog.e(TAG, "can't send message to client. Did it die?", e); + } + } + break; + } + case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: { + if (clientData.state == STATE_ENROLLING) { + final int fingerId = arg1; + final int remaining = arg2; + if (remaining == 0) { + FingerprintUtils.addFingerprintIdForUser(fingerId, + getContentResolver(), clientData.userId); + clientData.state = STATE_IDLE; // Nothing left to do + } + if (clientData.receiver != null) { + try { + clientData.receiver.onEnrollResult(fingerId, remaining); + } catch (RemoteException e) { + Slog.e(TAG, "can't send message to client. Did it die?", e); + } + } + } + break; + } + case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: { + int fingerId = arg1; + if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL"); + if (clientData.state == STATE_DELETING) { + FingerprintUtils.removeFingerprintIdForUser(fingerId, getContentResolver(), + clientData.userId); + if (clientData.receiver != null) { + try { + clientData.receiver.onRemoved(fingerId); + } catch (RemoteException e) { + Slog.e(TAG, "can't send message to client. Did it die?", e); + } + } + } + } + break; + } + } + } + + int enroll(IFingerprintServiceReceiver receiver, long timeout, int userId) { + ClientData clientData = mClients.get(receiver); + if (clientData != null) { + if (clientData.userId != userId) throw new IllegalStateException("Bad user"); + clientData.state = STATE_ENROLLING; + return nativeEnroll((int) (timeout / MS_PER_SEC)); + } + return -1; + } + + int remove(IFingerprintServiceReceiver receiver, int fingerId, int userId) { + ClientData clientData = mClients.get(receiver); + if (clientData != null) { + if (clientData.userId != userId) throw new IllegalStateException("Bad user"); + clientData.state = STATE_DELETING; + // The fingerprint id will be removed when we get confirmation from the HAL + return nativeRemove(fingerId); + } + return -1; + } + + void startListening(IFingerprintServiceReceiver receiver, int userId) { + ClientData clientData = new ClientData(); + clientData.state = STATE_LISTENING; + clientData.receiver = receiver; + clientData.userId = userId; + mClients.put(receiver, clientData); + } + + void stopListening(IFingerprintServiceReceiver receiver, int userId) { + ClientData clientData = mClients.get(receiver); + if (clientData != null) { + clientData.state = STATE_IDLE; + clientData.userId = -1; + clientData.receiver = null; + } + mClients.remove(receiver); + } + + private final class FingerprintServiceWrapper extends IFingerprintService.Stub { + IFingerprintServiceReceiver mReceiver; + public int enroll(long timeout, int userId) { + return mReceiver != null ? FingerprintService.this.enroll(mReceiver, timeout, userId) + : FingerprintManager.FINGERPRINT_ERROR_NO_RECEIVER; + } + + public int remove(int fingerprintId, int userId) { + return FingerprintService.this.remove(mReceiver, fingerprintId, userId); + } + + public void startListening(IFingerprintServiceReceiver receiver, int userId) { + mReceiver = receiver; + FingerprintService.this.startListening(receiver, userId); + } + + public void stopListening(int userId) { + FingerprintService.this.stopListening(mReceiver, userId); + } + } +} diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/service/fingerprint/FingerprintUtils.java new file mode 100644 index 0000000..81a2aac --- /dev/null +++ b/core/java/android/service/fingerprint/FingerprintUtils.java @@ -0,0 +1,85 @@ +/** + * 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.service.fingerprint; + +import android.content.ContentResolver; +import android.provider.Settings; +import android.util.Log; + +import java.util.Arrays; + +class FingerprintUtils { + private static final boolean DEBUG = true; + private static final String TAG = "FingerprintUtils"; + + public static int[] getFingerprintIdsForUser(ContentResolver res, int userId) { + String fingerIdsRaw = Settings.Secure.getStringForUser(res, + Settings.Secure.USER_FINGERPRINT_IDS, userId); + + String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", "); + int result[] = new int[fingerStringIds.length]; + for (int i = 0; i < result.length; i++) { + try { + result[i] = Integer.decode(fingerStringIds[i]); + } catch (NumberFormatException e) { + if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]); + } + } + return result; + } + + public static void addFingerprintIdForUser(int fingerId, ContentResolver res, int userId) { + int[] fingerIds = getFingerprintIdsForUser(res, userId); + + // FingerId 0 has special meaning. + if (fingerId == 0) return; + + // Don't allow dups + for (int i = 0; i < fingerIds.length; i++) { + if (fingerIds[i] == fingerId) return; + } + int[] newList = Arrays.copyOf(fingerIds, fingerIds.length + 1); + newList[fingerIds.length] = fingerId; + Settings.Secure.putStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS, + Arrays.toString(newList), userId); + } + + public static boolean removeFingerprintIdForUser(int fingerId, ContentResolver res, int userId) + { + // FingerId 0 has special meaning. The HAL layer is supposed to remove each finger one + // at a time and invoke notify() for each fingerId. If we get called with 0 here, it means + // something bad has happened. + if (fingerId == 0) throw new IllegalStateException("Bad fingerId"); + + int[] fingerIds = getFingerprintIdsForUser(res, userId); + int[] resultIds = Arrays.copyOf(fingerIds, fingerIds.length); + int resultCount = 0; + for (int i = 0; i < fingerIds.length; i++) { + if (fingerId != fingerIds[i]) { + resultIds[resultCount++] = fingerIds[i]; + } + } + if (resultCount > 0) { + Settings.Secure.putStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS, + Arrays.toString(Arrays.copyOf(resultIds, resultCount)), userId); + return true; + } + return false; + } + +}; + diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl new file mode 100644 index 0000000..e92c20c --- /dev/null +++ b/core/java/android/service/fingerprint/IFingerprintService.aidl @@ -0,0 +1,38 @@ +/* + * 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.service.fingerprint; + +import android.os.Bundle; +import android.service.fingerprint.IFingerprintServiceReceiver; + +/** + * Communication channel from client to the fingerprint service. + * @hide + */ +interface IFingerprintService { + // Returns 0 if successfully started, -1 otherwise + int enroll(long timeout, int userId); + + // Returns 0 if fingerprintId's template can be removed, -1 otherwise + int remove(int fingerprintId, int userId); + + // Start listening for fingerprint events. This has the side effect of starting + // the hardware if not already started. + oneway void startListening(IFingerprintServiceReceiver receiver, int userId); + + // Stops listening for fingerprints + oneway void stopListening(int userId); +} diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl new file mode 100644 index 0000000..4826b59 --- /dev/null +++ b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl @@ -0,0 +1,30 @@ +/* + * 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.service.fingerprint; + +import android.os.Bundle; +import android.os.UserHandle; + +/** + * Communication channel from the FingerprintService back to FingerprintManager. + * @hide + */ +oneway interface IFingerprintServiceReceiver { + void onEnrollResult(int fingerprintId, int remaining); + void onScanned(int fingerprintId, int confidence); + void onError(int error); + void onRemoved(int fingerprintId); +} diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index 71e3166..aa724f0 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -41,16 +41,25 @@ public class Condition implements Parcelable { public static final int FLAG_RELEVANT_ALWAYS = 1 << 1; public final Uri id; - public String caption; - public int state; - public int flags; - - public Condition(Uri id, String caption, int state, int flags) { + public final String summary; + public final String line1; + public final String line2; + public final int icon; + public final int state; + public final int flags; + + public Condition(Uri id, String summary, String line1, String line2, int icon, + int state, int flags) { if (id == null) throw new IllegalArgumentException("id is required"); - if (caption == null) throw new IllegalArgumentException("caption is required"); + if (summary == null) throw new IllegalArgumentException("summary is required"); + if (line1 == null) throw new IllegalArgumentException("line1 is required"); + if (line2 == null) throw new IllegalArgumentException("line2 is required"); if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); this.id = id; - this.caption = caption; + this.summary = summary; + this.line1 = line1; + this.line2 = line2; + this.icon = icon; this.state = state; this.flags = flags; } @@ -58,6 +67,9 @@ public class Condition implements Parcelable { private Condition(Parcel source) { this((Uri)source.readParcelable(Condition.class.getClassLoader()), source.readString(), + source.readString(), + source.readString(), + source.readInt(), source.readInt(), source.readInt()); } @@ -69,16 +81,22 @@ public class Condition implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(id, 0); - dest.writeString(caption); + dest.writeString(summary); + dest.writeString(line1); + dest.writeString(line2); + dest.writeInt(icon); dest.writeInt(state); - dest.writeInt(flags); + dest.writeInt(this.flags); } @Override public String toString() { return new StringBuilder(Condition.class.getSimpleName()).append('[') .append("id=").append(id) - .append(",caption=").append(caption) + .append(",summary=").append(summary) + .append(",line1=").append(line1) + .append(",line2=").append(line2) + .append(",icon=").append(icon) .append(",state=").append(stateToString(state)) .append(",flags=").append(flags) .append(']').toString(); @@ -92,20 +110,31 @@ public class Condition implements Parcelable { throw new IllegalArgumentException("state is invalid: " + state); } + public static String relevanceToString(int flags) { + final boolean now = (flags & FLAG_RELEVANT_NOW) != 0; + final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0; + if (!now && !always) return "NONE"; + if (now && always) return "NOW, ALWAYS"; + return now ? "NOW" : "ALWAYS"; + } + @Override public boolean equals(Object o) { if (!(o instanceof Condition)) return false; if (o == this) return true; final Condition other = (Condition) o; return Objects.equals(other.id, id) - && Objects.equals(other.caption, caption) + && Objects.equals(other.summary, summary) + && Objects.equals(other.line1, line1) + && Objects.equals(other.line2, line2) + && other.icon == icon && other.state == state && other.flags == flags; } @Override public int hashCode() { - return Objects.hash(id, caption, state, flags); + return Objects.hash(id, summary, line1, line2, icon, state, flags); } @Override diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java index d6ef8f5..326412f 100644 --- a/core/java/android/service/notification/ConditionProviderService.java +++ b/core/java/android/service/notification/ConditionProviderService.java @@ -22,7 +22,9 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Handler; import android.os.IBinder; +import android.os.Message; import android.os.ServiceManager; import android.util.Log; @@ -46,6 +48,8 @@ public abstract class ConditionProviderService extends Service { private final String TAG = ConditionProviderService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; + private final H mHandler = new H(); + private Provider mProvider; private INotificationManager mNoMan; @@ -100,41 +104,57 @@ public abstract class ConditionProviderService extends Service { } private final class Provider extends IConditionProvider.Stub { - private final ConditionProviderService mService = ConditionProviderService.this; - @Override public void onConnected() { - try { - mService.onConnected(); - } catch (Throwable t) { - Log.w(TAG, "Error running onConnected", t); - } + mHandler.obtainMessage(H.ON_CONNECTED).sendToTarget(); } @Override public void onRequestConditions(int relevance) { - try { - mService.onRequestConditions(relevance); - } catch (Throwable t) { - Log.w(TAG, "Error running onRequestConditions", t); - } + mHandler.obtainMessage(H.ON_REQUEST_CONDITIONS, relevance, 0).sendToTarget(); } @Override public void onSubscribe(Uri conditionId) { - try { - mService.onSubscribe(conditionId); - } catch (Throwable t) { - Log.w(TAG, "Error running onSubscribe", t); - } + mHandler.obtainMessage(H.ON_SUBSCRIBE, conditionId).sendToTarget(); } @Override public void onUnsubscribe(Uri conditionId) { + mHandler.obtainMessage(H.ON_UNSUBSCRIBE, conditionId).sendToTarget(); + } + } + + private final class H extends Handler { + private static final int ON_CONNECTED = 1; + private static final int ON_REQUEST_CONDITIONS = 2; + private static final int ON_SUBSCRIBE = 3; + private static final int ON_UNSUBSCRIBE = 4; + + @Override + public void handleMessage(Message msg) { + String name = null; try { - mService.onUnsubscribe(conditionId); + switch(msg.what) { + case ON_CONNECTED: + name = "onConnected"; + onConnected(); + break; + case ON_REQUEST_CONDITIONS: + name = "onRequestConditions"; + onRequestConditions(msg.arg1); + break; + case ON_SUBSCRIBE: + name = "onSubscribe"; + onSubscribe((Uri)msg.obj); + break; + case ON_UNSUBSCRIBE: + name = "onUnsubscribe"; + onUnsubscribe((Uri)msg.obj); + break; + } } catch (Throwable t) { - Log.w(TAG, "Error running onUnsubscribe", t); + Log.w(TAG, "Error running " + name, t); } } } diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 72720d1..e7cdc4e 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -87,7 +87,7 @@ public class StatusBarNotification implements Parcelable { } private String key() { - return pkg + '|' + id + '|' + tag + '|' + uid; + return user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid; } public void writeToParcel(Parcel out, int flags) { diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 925ddcf..846e292 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -16,6 +16,8 @@ package android.service.notification; +import android.content.ComponentName; +import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -25,6 +27,8 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; /** @@ -51,6 +55,10 @@ public class ZenModeConfig implements Parcelable { private static final String SLEEP_ATT_END_HR = "endHour"; private static final String SLEEP_ATT_END_MIN = "endMin"; + private static final String CONDITION_TAG = "condition"; + private static final String CONDITION_ATT_COMPONENT = "component"; + private static final String CONDITION_ATT_ID = "id"; + public boolean allowCalls; public boolean allowMessages; @@ -59,6 +67,8 @@ public class ZenModeConfig implements Parcelable { public int sleepStartMinute; public int sleepEndHour; public int sleepEndMinute; + public ComponentName[] conditionComponents; + public Uri[] conditionIds; public ZenModeConfig() { } @@ -72,6 +82,16 @@ public class ZenModeConfig implements Parcelable { sleepStartMinute = source.readInt(); sleepEndHour = source.readInt(); sleepEndMinute = source.readInt(); + int len = source.readInt(); + if (len > 0) { + conditionComponents = new ComponentName[len]; + source.readTypedArray(conditionComponents, ComponentName.CREATOR); + } + len = source.readInt(); + if (len > 0) { + conditionIds = new Uri[len]; + source.readTypedArray(conditionIds, Uri.CREATOR); + } } @Override @@ -88,6 +108,18 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(sleepStartMinute); dest.writeInt(sleepEndHour); dest.writeInt(sleepEndMinute); + if (conditionComponents != null && conditionComponents.length > 0) { + dest.writeInt(conditionComponents.length); + dest.writeTypedArray(conditionComponents, 0); + } else { + dest.writeInt(0); + } + if (conditionIds != null && conditionIds.length > 0) { + dest.writeInt(conditionIds.length); + dest.writeTypedArray(conditionIds, 0); + } else { + dest.writeInt(0); + } } @Override @@ -98,6 +130,10 @@ public class ZenModeConfig implements Parcelable { .append(",sleepMode=").append(sleepMode) .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute) .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute) + .append(",conditionComponents=") + .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents)) + .append(",conditionIds=") + .append(conditionIds == null ? null : TextUtils.join(",", conditionIds)) .append(']').toString(); } @@ -112,13 +148,16 @@ public class ZenModeConfig implements Parcelable { && other.sleepStartHour == sleepStartHour && other.sleepStartMinute == sleepStartMinute && other.sleepEndHour == sleepEndHour - && other.sleepEndMinute == sleepEndMinute; + && other.sleepEndMinute == sleepEndMinute + && Objects.deepEquals(other.conditionComponents, conditionComponents) + && Objects.deepEquals(other.conditionIds, conditionIds); } @Override public int hashCode() { return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour, - sleepStartMinute, sleepEndHour, sleepEndMinute); + sleepStartMinute, sleepEndHour, sleepEndMinute, + Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds)); } public boolean isValid() { @@ -136,9 +175,18 @@ public class ZenModeConfig implements Parcelable { if (!ZEN_TAG.equals(tag)) return null; final ZenModeConfig rt = new ZenModeConfig(); final int version = Integer.parseInt(parser.getAttributeValue(null, ZEN_ATT_VERSION)); + final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>(); + final ArrayList<Uri> conditionIds = new ArrayList<Uri>(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { tag = parser.getName(); - if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) return rt; + if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) { + if (!conditionComponents.isEmpty()) { + rt.conditionComponents = conditionComponents + .toArray(new ComponentName[conditionComponents.size()]); + rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]); + } + return rt; + } if (type == XmlPullParser.START_TAG) { if (ALLOW_TAG.equals(tag)) { rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); @@ -155,10 +203,18 @@ public class ZenModeConfig implements Parcelable { rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0; rt.sleepEndHour = isValidHour(endHour) ? endHour : 0; rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0; + } else if (CONDITION_TAG.equals(tag)) { + final ComponentName component = + safeComponentName(parser, CONDITION_ATT_COMPONENT); + final Uri conditionId = safeUri(parser, CONDITION_ATT_ID); + if (component != null && conditionId != null) { + conditionComponents.add(component); + conditionIds.add(conditionId); + } } } } - return rt; + throw new IllegalStateException("Failed to reach END_DOCUMENT"); } public void writeXml(XmlSerializer out) throws IOException { @@ -180,6 +236,16 @@ public class ZenModeConfig implements Parcelable { out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute)); out.endTag(null, SLEEP_TAG); + if (conditionComponents != null && conditionIds != null + && conditionComponents.length == conditionIds.length) { + for (int i = 0; i < conditionComponents.length; i++) { + out.startTag(null, CONDITION_TAG); + out.attribute(null, CONDITION_ATT_COMPONENT, + conditionComponents[i].flattenToString()); + out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString()); + out.endTag(null, CONDITION_TAG); + } + } out.endTag(null, ZEN_TAG); } @@ -203,6 +269,18 @@ public class ZenModeConfig implements Parcelable { return Integer.valueOf(val); } + private static ComponentName safeComponentName(XmlPullParser parser, String att) { + final String val = parser.getAttributeValue(null, att); + if (TextUtils.isEmpty(val)) return null; + return ComponentName.unflattenFromString(val); + } + + private static Uri safeUri(XmlPullParser parser, String att) { + final String val = parser.getAttributeValue(null, att); + if (TextUtils.isEmpty(val)) return null; + return Uri.parse(val); + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index 7dbf66b..9f9c312 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -16,13 +16,14 @@ package android.service.voice; -import android.os.Bundle; - -import com.android.internal.app.IVoiceInteractorCallback; -import com.android.internal.app.IVoiceInteractorRequest; +import android.content.Intent; /** * @hide */ -interface IVoiceInteractionSession { +oneway interface IVoiceInteractionSession { + void taskStarted(in Intent intent, int taskId); + void taskFinished(in Intent intent, int taskId); + void closeSystemDialogs(); + void destroy(); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index d005890..e15489b 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -27,6 +27,19 @@ import android.os.RemoteException; import android.os.ServiceManager; import com.android.internal.app.IVoiceInteractionManagerService; +/** + * Top-level service of the current global voice interactor, which is providing + * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc. + * The current VoiceInteractionService that has been selected by the user is kept + * always running by the system, to allow it to do things like listen for hotwords + * in the background to instigate voice interactions. + * + * <p>Because this service is always running, it should be kept as lightweight as + * possible. Heavy-weight operations (including showing UI) should be implemented + * in the associated {@link android.service.voice.VoiceInteractionSessionService} when + * an actual voice interaction is taking place, and that service should run in a + * separate process from this one. + */ public class VoiceInteractionService extends Service { /** * The {@link Intent} that must be declared as handled by the service. @@ -51,11 +64,9 @@ public class VoiceInteractionService extends Service { IVoiceInteractionManagerService mSystemService; - public void startVoiceActivity(Intent intent, Bundle sessionArgs) { + public void startSession(Bundle args) { try { - mSystemService.startVoiceActivity(intent, - intent.resolveType(getContentResolver()), - mInterface, sessionArgs); + mSystemService.startSession(mInterface, args); } catch (RemoteException e) { } } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 963b6b4..a83544d 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -16,7 +16,14 @@ package android.service.voice; +import android.app.Dialog; +import android.app.Instrumentation; import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Region; +import android.inputmethodservice.SoftInputWindow; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -25,16 +32,53 @@ import android.os.Message; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.widget.FrameLayout; +import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractorCallback; import com.android.internal.app.IVoiceInteractorRequest; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; -public abstract class VoiceInteractionSession { +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +public abstract class VoiceInteractionSession implements KeyEvent.Callback { static final String TAG = "VoiceInteractionSession"; static final boolean DEBUG = true; + final Context mContext; + final HandlerCaller mHandlerCaller; + + final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); + + IVoiceInteractionManagerService mSystemService; + IBinder mToken; + + int mTheme = 0; + LayoutInflater mInflater; + TypedArray mThemeAttrs; + View mRootView; + FrameLayout mContentFrame; + SoftInputWindow mWindow; + + boolean mInitialized; + boolean mWindowAdded; + boolean mWindowVisible; + boolean mWindowWasVisible; + boolean mInShowWindow; + + final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); + + final Insets mTmpInsets = new Insets(); + final int[] mTmpLocation = new int[2]; + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { @Override public IVoiceInteractorRequest startConfirmation(String callingPackage, @@ -71,6 +115,27 @@ public abstract class VoiceInteractionSession { }; final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { + @Override + public void taskStarted(Intent intent, int taskId) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED, + taskId, intent)); + } + + @Override + public void taskFinished(Intent intent, int taskId) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_FINISHED, + taskId, intent)); + } + + @Override + public void closeSystemDialogs() { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CLOSE_SYSTEM_DIALOGS)); + } + + @Override + public void destroy() { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY)); + } }; public static class Request { @@ -129,38 +194,128 @@ public abstract class VoiceInteractionSession { static final int MSG_SUPPORTS_COMMANDS = 3; static final int MSG_CANCEL = 4; - final Context mContext; - final HandlerCaller mHandlerCaller; - final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() { + static final int MSG_TASK_STARTED = 100; + static final int MSG_TASK_FINISHED = 101; + static final int MSG_CLOSE_SYSTEM_DIALOGS = 102; + static final int MSG_DESTROY = 103; + + class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback { @Override public void executeMessage(Message msg) { - SomeArgs args = (SomeArgs)msg.obj; + SomeArgs args; switch (msg.what) { case MSG_START_CONFIRMATION: + args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface + " prompt=" + args.arg3 + " extras=" + args.arg4); onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3, (Bundle)args.arg4); break; case MSG_START_COMMAND: + args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface + " command=" + args.arg3 + " extras=" + args.arg4); onCommand((Caller) args.arg1, (Request) args.arg2, (String) args.arg3, (Bundle) args.arg4); break; case MSG_SUPPORTS_COMMANDS: + args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2); args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2); break; case MSG_CANCEL: + args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface); onCancel((Request)args.arg1); break; + case MSG_TASK_STARTED: + if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj + + " taskId=" + msg.arg1); + onTaskStarted((Intent) msg.obj, msg.arg1); + break; + case MSG_TASK_FINISHED: + if (DEBUG) Log.d(TAG, "onTaskFinished: intent=" + msg.obj + + " taskId=" + msg.arg1); + onTaskFinished((Intent) msg.obj, msg.arg1); + break; + case MSG_CLOSE_SYSTEM_DIALOGS: + if (DEBUG) Log.d(TAG, "onCloseSystemDialogs"); + onCloseSystemDialogs(); + break; + case MSG_DESTROY: + if (DEBUG) Log.d(TAG, "doDestroy"); + doDestroy(); + break; } } - }; - final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); + @Override + public void onBackPressed() { + VoiceInteractionSession.this.onBackPressed(); + } + } + + final MyCallbacks mCallbacks = new MyCallbacks(); + + /** + * Information about where interesting parts of the input method UI appear. + */ + public static final class Insets { + /** + * This is the top part of the UI that is the main content. It is + * used to determine the basic space needed, to resize/pan the + * application behind. It is assumed that this inset does not + * change very much, since any change will cause a full resize/pan + * of the application behind. This value is relative to the top edge + * of the input method window. + */ + public int contentTopInsets; + + /** + * This is the region of the UI that is touchable. It is used when + * {@link #touchableInsets} is set to {@link #TOUCHABLE_INSETS_REGION}. + * The region should be specified relative to the origin of the window frame. + */ + public final Region touchableRegion = new Region(); + + /** + * Option for {@link #touchableInsets}: the entire window frame + * can be touched. + */ + public static final int TOUCHABLE_INSETS_FRAME + = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; + + /** + * Option for {@link #touchableInsets}: the area inside of + * the content insets can be touched. + */ + public static final int TOUCHABLE_INSETS_CONTENT + = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; + + /** + * Option for {@link #touchableInsets}: the region specified by + * {@link #touchableRegion} can be touched. + */ + public static final int TOUCHABLE_INSETS_REGION + = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; + + /** + * Determine which area of the window is touchable by the user. May + * be one of: {@link #TOUCHABLE_INSETS_FRAME}, + * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_REGION}. + */ + public int touchableInsets; + } + + final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = + new ViewTreeObserver.OnComputeInternalInsetsListener() { + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + onComputeInsets(mTmpInsets); + info.contentInsets.top = info.visibleInsets.top = mTmpInsets.contentTopInsets; + info.touchableRegion.set(mTmpInsets.touchableRegion); + info.setTouchableInsets(mTmpInsets.touchableInsets); + } + }; public VoiceInteractionSession(Context context) { this(context, new Handler()); @@ -169,7 +324,7 @@ public abstract class VoiceInteractionSession { public VoiceInteractionSession(Context context, Handler handler) { mContext = context; mHandlerCaller = new HandlerCaller(context, handler.getLooper(), - mHandlerCallerCallback, true); + mCallbacks, true); } Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) { @@ -188,6 +343,192 @@ public abstract class VoiceInteractionSession { } } + void doCreate(IVoiceInteractionManagerService service, IBinder token, Bundle args) { + mSystemService = service; + mToken = token; + onCreate(args); + } + + void doDestroy() { + if (mInitialized) { + mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( + mInsetsComputer); + if (mWindowAdded) { + mWindow.dismiss(); + mWindowAdded = false; + } + mInitialized = false; + } + } + + void initViews() { + mInitialized = true; + + mThemeAttrs = mContext.obtainStyledAttributes(android.R.styleable.VoiceInteractionSession); + mRootView = mInflater.inflate( + com.android.internal.R.layout.voice_interaction_session, null); + mRootView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + mWindow.setContentView(mRootView); + mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); + + mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content); + } + + public void showWindow() { + if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded + + " mWindowVisible=" + mWindowVisible); + + if (mInShowWindow) { + Log.w(TAG, "Re-entrance in to showWindow"); + return; + } + + try { + mInShowWindow = true; + if (!mWindowVisible) { + mWindowVisible = true; + if (!mWindowAdded) { + mWindowAdded = true; + View v = onCreateContentView(); + if (v != null) { + setContentView(v); + } + } + mWindow.show(); + } + } finally { + mWindowWasVisible = true; + mInShowWindow = false; + } + } + + public void hideWindow() { + if (mWindowVisible) { + mWindow.hide(); + mWindowVisible = false; + } + } + + /** + * You can call this to customize the theme used by your IME's window. + * This must be set before {@link #onCreate}, so you + * will typically call it in your constructor with the resource ID + * of your custom theme. + */ + public void setTheme(int theme) { + if (mWindow != null) { + throw new IllegalStateException("Must be called before onCreate()"); + } + mTheme = theme; + } + + public void startVoiceActivity(Intent intent) { + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } + try { + int res = mSystemService.startVoiceActivity(mToken, intent, + intent.resolveType(mContext.getContentResolver())); + Instrumentation.checkStartActivityResult(res, intent); + } catch (RemoteException e) { + } + } + + public LayoutInflater getLayoutInflater() { + return mInflater; + } + + public Dialog getWindow() { + return mWindow; + } + + public void finish() { + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } + hideWindow(); + try { + mSystemService.finish(mToken); + } catch (RemoteException e) { + } + } + + public void onCreate(Bundle args) { + mTheme = mTheme != 0 ? mTheme + : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession; + mInflater = (LayoutInflater)mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme, + mCallbacks, this, mDispatcherState, true); + mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + initViews(); + mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); + mWindow.setToken(mToken); + } + + public void onDestroy() { + } + + public View onCreateContentView() { + return null; + } + + public void setContentView(View view) { + mContentFrame.removeAllViews(); + mContentFrame.addView(view, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + } + + public boolean onKeyDown(int keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { + return false; + } + + public void onBackPressed() { + finish(); + } + + public void onCloseSystemDialogs() { + finish(); + } + + /** + * Compute the interesting insets into your UI. The default implementation + * uses the entire window frame as the insets. The default touchable + * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}. + * + * @param outInsets Fill in with the current UI insets. + */ + public void onComputeInsets(Insets outInsets) { + int[] loc = mTmpLocation; + View decor = getWindow().getWindow().getDecorView(); + decor.getLocationInWindow(loc); + outInsets.contentTopInsets = loc[1]; + outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME; + outInsets.touchableRegion.setEmpty(); + } + + public void onTaskStarted(Intent intent, int taskId) { + } + + public void onTaskFinished(Intent intent, int taskId) { + finish(); + } + public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands); public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras); public abstract void onCommand(Caller caller, Request request, String command, Bundle extras); diff --git a/core/java/android/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java index 40e5bba..e793849 100644 --- a/core/java/android/service/voice/VoiceInteractionSessionService.java +++ b/core/java/android/service/voice/VoiceInteractionSessionService.java @@ -29,11 +29,15 @@ import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +/** + * An active voice interaction session, initiated by a {@link VoiceInteractionService}. + */ public abstract class VoiceInteractionSessionService extends Service { static final int MSG_NEW_SESSION = 1; IVoiceInteractionManagerService mSystemService; + VoiceInteractionSession mSession; IVoiceInteractionSessionService mInterface = new IVoiceInteractionSessionService.Stub() { public void newSession(IBinder token, Bundle args) { @@ -73,9 +77,14 @@ public abstract class VoiceInteractionSessionService extends Service { } void doNewSession(IBinder token, Bundle args) { - VoiceInteractionSession session = onNewSession(args); + if (mSession != null) { + mSession.doDestroy(); + mSession = null; + } + mSession = onNewSession(args); try { - mSystemService.deliverNewSession(token, session.mSession, session.mInteractor); + mSystemService.deliverNewSession(token, mSession.mSession, mSession.mInteractor); + mSession.doCreate(mSystemService, token, args); } catch (RemoteException e) { } } diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java index 186cb49..92bb0ac 100644 --- a/core/java/android/speech/tts/BlockingAudioTrack.java +++ b/core/java/android/speech/tts/BlockingAudioTrack.java @@ -83,7 +83,7 @@ class BlockingAudioTrack { mVolume = volume; mPan = pan; - mBytesPerFrame = getBytesPerFrame(mAudioFormat) * mChannelCount; + mBytesPerFrame = AudioFormat.getBytesPerSample(mAudioFormat) * mChannelCount; mIsShortUtterance = false; mAudioBufferSize = 0; mBytesWritten = 0; @@ -229,17 +229,6 @@ class BlockingAudioTrack { return audioTrack; } - private static int getBytesPerFrame(int audioFormat) { - if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) { - return 1; - } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) { - return 2; - } - - return -1; - } - - private void blockUntilDone(AudioTrack audioTrack) { if (mBytesWritten <= 0) { return; diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java index 717aeb6..d84f7f0 100644 --- a/core/java/android/speech/tts/FileSynthesisCallback.java +++ b/core/java/android/speech/tts/FileSynthesisCallback.java @@ -278,8 +278,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { private ByteBuffer makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount, int dataLength) { - // TODO: is AudioFormat.ENCODING_DEFAULT always the same as ENCODING_PCM_16BIT? - int sampleSizeInBytes = (audioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2); + int sampleSizeInBytes = AudioFormat.getBytesPerSample(audioFormat); int byteRate = sampleRateInHz * sampleSizeInBytes * channelCount; short blockAlign = (short) (sampleSizeInBytes * channelCount); short bitsPerSample = (short) (sampleSizeInBytes * 8); diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java index 9c98b98..b0cbcd2 100644 --- a/core/java/android/text/format/Formatter.java +++ b/core/java/android/text/format/Formatter.java @@ -106,4 +106,69 @@ public final class Formatter { public static String formatIpAddress(int ipv4Address) { return NetworkUtils.intToInetAddress(ipv4Address).getHostAddress(); } + + private static final int SECONDS_PER_MINUTE = 60; + private static final int SECONDS_PER_HOUR = 60 * 60; + private static final int SECONDS_PER_DAY = 24 * 60 * 60; + + /** + * Returns elapsed time for the given millis, in the following format: + * 1 day 5 hrs; will include at most two units, can go down to seconds precision. + * @param context the application context + * @param millis the elapsed time in milli seconds + * @return the formatted elapsed time + * @hide + */ + public static String formatShortElapsedTime(Context context, long millis) { + long secondsLong = millis / 1000; + + int days = 0, hours = 0, minutes = 0; + if (secondsLong >= SECONDS_PER_DAY) { + days = (int)(secondsLong / SECONDS_PER_DAY); + secondsLong -= days * SECONDS_PER_DAY; + } + if (secondsLong >= SECONDS_PER_HOUR) { + hours = (int)(secondsLong / SECONDS_PER_HOUR); + secondsLong -= hours * SECONDS_PER_HOUR; + } + if (secondsLong >= SECONDS_PER_MINUTE) { + minutes = (int)(secondsLong / SECONDS_PER_MINUTE); + secondsLong -= minutes * SECONDS_PER_MINUTE; + } + int seconds = (int)secondsLong; + + if (days >= 2) { + days += (hours+12)/24; + return context.getString(com.android.internal.R.string.durationDays, days); + } else if (days > 0) { + if (hours == 1) { + return context.getString(com.android.internal.R.string.durationDayHour, days, hours); + } + return context.getString(com.android.internal.R.string.durationDayHours, days, hours); + } else if (hours >= 2) { + hours += (minutes+30)/60; + return context.getString(com.android.internal.R.string.durationHours, hours); + } else if (hours > 0) { + if (minutes == 1) { + return context.getString(com.android.internal.R.string.durationHourMinute, hours, + minutes); + } + return context.getString(com.android.internal.R.string.durationHourMinutes, hours, + minutes); + } else if (minutes >= 2) { + minutes += (seconds+30)/60; + return context.getString(com.android.internal.R.string.durationMinutes, minutes); + } else if (minutes > 0) { + if (seconds == 1) { + return context.getString(com.android.internal.R.string.durationMinuteSecond, minutes, + seconds); + } + return context.getString(com.android.internal.R.string.durationMinuteSeconds, minutes, + seconds); + } else if (seconds == 1) { + return context.getString(com.android.internal.R.string.durationSecond, seconds); + } else { + return context.getString(com.android.internal.R.string.durationSeconds, seconds); + } + } } diff --git a/core/java/android/transition/ChangeClipBounds.java b/core/java/android/transition/ChangeClipBounds.java new file mode 100644 index 0000000..a61b29d --- /dev/null +++ b/core/java/android/transition/ChangeClipBounds.java @@ -0,0 +1,96 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.RectEvaluator; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; + +/** + * ChangeClipBounds captures the {@link android.view.View#getClipBounds()} before and after the + * scene change and animates those changes during the transition. + */ +public class ChangeClipBounds extends Transition { + + private static final String TAG = "ChangeTransform"; + + private static final String PROPNAME_CLIP = "android:clipBounds:clip"; + private static final String PROPNAME_BOUNDS = "android:clipBounds:bounds"; + + private static final String[] sTransitionProperties = { + PROPNAME_CLIP, + }; + + @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + + private void captureValues(TransitionValues values) { + View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + Rect clip = view.getClipBounds(); + values.values.put(PROPNAME_CLIP, clip); + if (clip == null) { + Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); + values.values.put(PROPNAME_BOUNDS, bounds); + } + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null + || !startValues.values.containsKey(PROPNAME_CLIP) + || !endValues.values.containsKey(PROPNAME_CLIP)) { + return null; + } + Rect start = (Rect) startValues.values.get(PROPNAME_CLIP); + Rect end = (Rect) endValues.values.get(PROPNAME_CLIP); + if (start == null && end == null) { + return null; // No animation required since there is no clip. + } + + if (start == null) { + start = (Rect) startValues.values.get(PROPNAME_BOUNDS); + } else if (end == null) { + end = (Rect) endValues.values.get(PROPNAME_BOUNDS); + } + if (start.equals(end)) { + return null; + } + + endValues.view.setClipBounds(start); + RectEvaluator evaluator = new RectEvaluator(new Rect()); + return ObjectAnimator.ofObject(endValues.view, "clipBounds", evaluator, start, end); + } +} diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java new file mode 100644 index 0000000..85cb2c7 --- /dev/null +++ b/core/java/android/transition/ChangeTransform.java @@ -0,0 +1,163 @@ +/* + * 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.transition; + +import android.animation.Animator; +import android.animation.FloatArrayEvaluator; +import android.animation.ObjectAnimator; +import android.util.FloatProperty; +import android.util.Property; +import android.view.View; +import android.view.ViewGroup; + +/** + * This Transition captures scale and rotation for Views before and after the + * scene change and animates those changes during the transition. + * + * <p>ChangeTransform does not work when the pivot changes between scenes, so either the + * pivot must be set to prevent automatic pivot adjustment or the View's size must be unchanged.</p> + */ +public class ChangeTransform extends Transition { + + private static final String TAG = "ChangeTransform"; + + private static final String PROPNAME_SCALE_X = "android:changeTransform:scaleX"; + private static final String PROPNAME_SCALE_Y = "android:changeTransform:scaleY"; + private static final String PROPNAME_ROTATION_X = "android:changeTransform:rotationX"; + private static final String PROPNAME_ROTATION_Y = "android:changeTransform:rotationY"; + private static final String PROPNAME_ROTATION_Z = "android:changeTransform:rotationZ"; + private static final String PROPNAME_PIVOT_X = "android:changeTransform:pivotX"; + private static final String PROPNAME_PIVOT_Y = "android:changeTransform:pivotY"; + + private static final String[] sTransitionProperties = { + PROPNAME_SCALE_X, + PROPNAME_SCALE_Y, + PROPNAME_ROTATION_X, + PROPNAME_ROTATION_Y, + PROPNAME_ROTATION_Z, + }; + + private static final FloatProperty<View>[] sChangedProperties = new FloatProperty[] { + (FloatProperty) View.SCALE_X, + (FloatProperty) View.SCALE_Y, + (FloatProperty) View.ROTATION_X, + (FloatProperty) View.ROTATION_Y, + (FloatProperty) View.ROTATION, + }; + + private static Property<View, float[]> TRANSFORMS = new Property<View, float[]>(float[].class, + "transforms") { + @Override + public float[] get(View object) { + return null; + } + + @Override + public void set(View view, float[] values) { + for (int i = 0; i < values.length; i++) { + float value = values[i]; + if (value != Float.NaN) { + sChangedProperties[i].setValue(view, value); + } + } + } + }; + + @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + + private void captureValues(TransitionValues values) { + View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + values.values.put(PROPNAME_SCALE_X, view.getScaleX()); + values.values.put(PROPNAME_SCALE_Y, view.getScaleY()); + values.values.put(PROPNAME_PIVOT_X, view.getPivotX()); + values.values.put(PROPNAME_PIVOT_Y, view.getPivotY()); + values.values.put(PROPNAME_ROTATION_X, view.getRotationX()); + values.values.put(PROPNAME_ROTATION_Y, view.getRotationY()); + values.values.put(PROPNAME_ROTATION_Z, view.getRotation()); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null + || !startValues.values.containsKey(PROPNAME_SCALE_X) + || !endValues.values.containsKey(PROPNAME_SCALE_X) + || !isPivotSame(startValues, endValues) + || !isChanged(startValues, endValues)) { + return null; + } + + float[] start = createValues(startValues); + float[] end = createValues(endValues); + for (int i = 0; i < start.length; i++) { + if (start[i] == end[i]) { + start[i] = Float.NaN; + end[i] = Float.NaN; + } else { + sChangedProperties[i].setValue(endValues.view, start[i]); + } + } + FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[start.length]); + return ObjectAnimator.ofObject(endValues.view, TRANSFORMS, evaluator, start, end); + } + + private static float[] createValues(TransitionValues transitionValues) { + float[] values = new float[sChangedProperties.length]; + for (int i = 0; i < values.length; i++) { + values[i] = (Float) transitionValues.values.get(sTransitionProperties[i]); + } + return values; + } + + private static boolean isPivotSame(TransitionValues startValues, TransitionValues endValues) { + float startPivotX = (Float) startValues.values.get(PROPNAME_PIVOT_X); + float startPivotY = (Float) startValues.values.get(PROPNAME_PIVOT_Y); + float endPivotX = (Float) endValues.values.get(PROPNAME_PIVOT_X); + float endPivotY = (Float) endValues.values.get(PROPNAME_PIVOT_Y); + + // We don't support pivot changes, because they could be automatically set + // and we can't end the state in an automatic state. + return startPivotX == endPivotX && startPivotY == endPivotY; + } + + private static boolean isChanged(TransitionValues startValues, TransitionValues endValues) { + for (int i = 0; i < sChangedProperties.length; i++) { + Object start = startValues.values.get(sTransitionProperties[i]); + Object end = endValues.values.get(sTransitionProperties[i]); + if (!start.equals(end)) { + return true; + } + } + return false; + } +} diff --git a/core/java/android/transition/CircularPropagation.java b/core/java/android/transition/CircularPropagation.java index 18a3d22..51beb51 100644 --- a/core/java/android/transition/CircularPropagation.java +++ b/core/java/android/transition/CircularPropagation.java @@ -34,7 +34,7 @@ import android.view.ViewGroup; public class CircularPropagation extends VisibilityPropagation { private static final String TAG = "CircularPropagation"; - private float mPropagationSpeed = 4.0f; + private float mPropagationSpeed = 3.0f; /** * Sets the speed at which transition propagation happens, relative to the duration of the @@ -91,8 +91,12 @@ public class CircularPropagation extends VisibilityPropagation { float maxDistance = distance(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight()); float distanceFraction = distance/maxDistance; - return Math.round(transition.getDuration() * directionMultiplier / mPropagationSpeed - * distanceFraction); + long duration = transition.getDuration(); + if (duration < 0) { + duration = 300; + } + + return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction); } private static float distance(float x1, float y1, float x2, float y2) { diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java index d68e971..183cdd2 100644 --- a/core/java/android/transition/MoveImage.java +++ b/core/java/android/transition/MoveImage.java @@ -125,7 +125,7 @@ public class MoveImage extends Transition { Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX); Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX); - if (!startMatrix.equals(endMatrix)) { + if (startMatrix != null && !startMatrix.equals(endMatrix)) { changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.MATRIX_PROPERTY, new MatrixEvaluator(), startMatrix, endMatrix)); } @@ -170,13 +170,20 @@ public class MoveImage extends Transition { drawable = drawable.getConstantState().newDrawable(); final MatrixClippedDrawable matrixClippedDrawable = new MatrixClippedDrawable(drawable); + final ImageView overlayImage = new ImageView(imageView.getContext()); + final ViewGroupOverlay overlay = sceneRoot.getOverlay(); + overlay.add(overlayImage); + overlayImage.setLeft(0); + overlayImage.setTop(0); + overlayImage.setRight(sceneRoot.getWidth()); + overlayImage.setBottom(sceneRoot.getBottom()); + overlayImage.setScaleType(ImageView.ScaleType.MATRIX); + overlayImage.setImageDrawable(matrixClippedDrawable); matrixClippedDrawable.setMatrix(startMatrix); matrixClippedDrawable.setBounds(startBounds); matrixClippedDrawable.setClipRect(startClip); imageView.setVisibility(View.INVISIBLE); - final ViewGroupOverlay overlay = sceneRoot.getOverlay(); - overlay.add(matrixClippedDrawable); ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(matrixClippedDrawable, changes.toArray(new PropertyValuesHolder[changes.size()])); @@ -184,19 +191,24 @@ public class MoveImage extends Transition { @Override public void onAnimationEnd(Animator animation) { imageView.setVisibility(View.VISIBLE); - overlay.remove(matrixClippedDrawable); + overlay.remove(overlayImage); } @Override public void onAnimationPause(Animator animation) { imageView.setVisibility(View.VISIBLE); - overlay.remove(matrixClippedDrawable); + overlayImage.setVisibility(View.INVISIBLE); } @Override public void onAnimationResume(Animator animation) { imageView.setVisibility(View.INVISIBLE); - overlay.add(matrixClippedDrawable); + overlayImage.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animation) { + onAnimationEnd(animation); } }; @@ -218,7 +230,9 @@ public class MoveImage extends Transition { private static void expandClip(Rect bounds, Matrix matrix, Rect clip, Rect otherClip) { RectF boundsF = new RectF(bounds); - matrix.mapRect(boundsF); + if (matrix != null) { + matrix.mapRect(boundsF); + } clip.left = expandMinDimension(boundsF.left, clip.left, otherClip.left); clip.top = expandMinDimension(boundsF.top, clip.top, otherClip.top); clip.right = expandMaxDimension(boundsF.right, clip.right, otherClip.right); @@ -244,10 +258,20 @@ public class MoveImage extends Transition { int drawableWidth = drawable.getIntrinsicWidth(); int drawableHeight = drawable.getIntrinsicHeight(); ImageView.ScaleType scaleType = imageView.getScaleType(); - if (drawableWidth <= 0 || drawableHeight <= 0 || scaleType == ImageView.ScaleType.FIT_XY) { - return null; + Matrix matrix; + if (drawableWidth <= 0 || drawableHeight <= 0) { + matrix = null; + } else if (scaleType == ImageView.ScaleType.FIT_XY) { + matrix = new Matrix(); + float scaleX = imageView.getWidth(); + scaleX /= drawableWidth; + float scaleY = imageView.getHeight(); + scaleY /= drawableHeight; + matrix.setScale(scaleX, scaleY); + } else { + matrix = new Matrix(imageView.getImageMatrix()); } - return new Matrix(imageView.getImageMatrix()); + return matrix; } private Rect findClip(ImageView imageView) { diff --git a/core/java/android/transition/SidePropagation.java b/core/java/android/transition/SidePropagation.java index c331945..5d38ac8 100644 --- a/core/java/android/transition/SidePropagation.java +++ b/core/java/android/transition/SidePropagation.java @@ -52,7 +52,7 @@ public class SidePropagation extends VisibilityPropagation { */ public static final int BOTTOM = Slide.BOTTOM; - private float mPropagationSpeed = 4.0f; + private float mPropagationSpeed = 3.0f; private int mSide = BOTTOM; /** @@ -129,8 +129,12 @@ public class SidePropagation extends VisibilityPropagation { float maxDistance = getMaxDistance(sceneRoot); float distanceFraction = distance/maxDistance; - return Math.round(transition.getDuration() * directionMultiplier / mPropagationSpeed - * distanceFraction); + long duration = transition.getDuration(); + if (duration < 0) { + duration = 300; + } + + return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction); } private int distance(int viewX, int viewY, int epicenterX, int epicenterY, diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index c67d6fa..2549fde 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -66,13 +66,12 @@ import java.util.List; * * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds} * - * <p>{@link android.transition.Explode} transition:</p> + * <p>This TransitionSet contains {@link android.transition.Explode} for visibility, + * {@link android.transition.ChangeBounds}, {@link android.transition.ChangeTransform}, + * and {@link android.transition.ChangeClipBounds} for non-<code>ImageView</code>s and + * {@link android.transition.MoveImage} for <code>ImageView</code>s:</p> * - * {@sample development/samples/ApiDemos/res/transition/explode.xml Explode} - * - * <p>{@link android.transition.MoveImage} transition:</p> - * - * {@sample development/samples/ApiDemos/res/transition/move_image.xml MoveImage} + * {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform} * * <p>Note that attributes for the transition are not required, just as they are * optional when declared in code; Transitions created from XML resources will use @@ -89,7 +88,8 @@ import java.util.List; * transition uses a fadingMode of {@link Fade#OUT} instead of the default * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each - * of which lists a specific <code>targetId</code> which this transition acts upon. + * of which lists a specific <code>targetId</code>, <code>targetClass</code>, + * <code>excludeId</code>, or <code>excludeClass</code>, which this transition acts upon. * Use of targets is optional, but can be used to either limit the time spent checking * attributes on unchanging views, or limiting the types of animations run on specific views. * In this case, we know that only the <code>grayscaleContainer</code> will be @@ -116,6 +116,7 @@ public abstract class Transition implements Cloneable { ArrayList<Integer> mTargetIdExcludes = null; ArrayList<View> mTargetExcludes = null; ArrayList<Class> mTargetTypeExcludes = null; + ArrayList<Class> mTargetTypes = null; ArrayList<Integer> mTargetIdChildExcludes = null; ArrayList<View> mTargetChildExcludes = null; ArrayList<Class> mTargetTypeChildExcludes = null; @@ -569,19 +570,15 @@ public abstract class Transition implements Cloneable { } } } - if (mTargetIds.size() == 0 && mTargets.size() == 0) { + if (mTargetIds.size() == 0 && mTargets.size() == 0 && mTargetTypes == null) { return true; } - if (mTargetIds.size() > 0) { - for (int i = 0; i < mTargetIds.size(); ++i) { - if (mTargetIds.get(i) == targetId) { - return true; - } - } + if (mTargetIds.contains((int) targetId) || mTargets.contains(target)) { + return true; } - if (target != null && mTargets.size() > 0) { - for (int i = 0; i < mTargets.size(); ++i) { - if (mTargets.get(i) == target) { + if (mTargetTypes != null) { + for (int i = 0; i < mTargetTypes.size(); ++i) { + if (mTargetTypes.get(i).isInstance(target)) { return true; } } @@ -727,6 +724,36 @@ public abstract class Transition implements Cloneable { } /** + * Adds the Class of a target view that this Transition is interested in + * animating. By default, there are no targetTypes, and a Transition will + * listen for changes on every view in the hierarchy below the sceneRoot + * of the Scene being transitioned into. Setting targetTypes constrains + * the Transition to only listen for, and act on, views with these classes. + * Views with different classes will be ignored. + * + * <p>Note that any View that can be cast to targetType will be included, so + * if targetType is <code>View.class</code>, all Views will be included.</p> + * + * @see #addTarget(int) + * @see #addTarget(android.view.View) + * @see #excludeTarget(Class, boolean) + * @see #excludeChildren(Class, boolean) + * + * @param targetType The type to include when running this transition. + * @return The Transition to which the target class was added. + * Returning the same object makes it easier to chain calls during + * construction, such as + * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code> + */ + public Transition addTarget(Class targetType) { + if (mTargetTypes == null) { + mTargetTypes = new ArrayList<Class>(); + } + mTargetTypes.add(targetType); + return this; + } + + /** * Removes the given targetId from the list of ids that this Transition * is interested in animating. * @@ -1116,9 +1143,6 @@ public abstract class Transition implements Cloneable { if (view == null) { return; } - if (!isValidTarget(view, view.getId())) { - return; - } boolean isListViewItem = false; if (view.getParent() instanceof ListView) { isListViewItem = true; diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 14ecc15..a5e960a 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -153,6 +153,12 @@ public class TransitionInflater { } else if ("moveImage".equals(name)) { transition = new MoveImage(); newTransition = true; + } else if ("changeTransform".equals(name)) { + transition = new ChangeTransform(); + newTransition = true; + } else if ("changeClipBounds".equals(name)) { + transition = new ChangeClipBounds(); + newTransition = true; } else if ("autoTransition".equals(name)) { transition = new AutoTransition(); newTransition = true; @@ -210,7 +216,6 @@ public class TransitionInflater { int type; int depth = parser.getDepth(); - ArrayList<Integer> targetIds = new ArrayList<Integer>(); while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { @@ -225,18 +230,31 @@ public class TransitionInflater { int id = a.getResourceId( com.android.internal.R.styleable.TransitionTarget_targetId, -1); if (id >= 0) { - targetIds.add(id); + transition.addTarget(id); + } else if ((id = a.getResourceId( + com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) { + transition.excludeTarget(id, true); + } else { + String className = a.getString( + com.android.internal.R.styleable.TransitionTarget_excludeClass); + try { + if (className != null) { + Class clazz = Class.forName(className); + transition.excludeTarget(clazz, true); + } else if ((className = a.getString( + com.android.internal.R.styleable.TransitionTarget_targetClass)) + != null) { + Class clazz = Class.forName(className); + transition.addTarget(clazz); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not create " + className, e); + } } } else { throw new RuntimeException("Unknown scene name: " + parser.getName()); } } - int numTargets = targetIds.size(); - if (numTargets > 0) { - for (int i = 0; i < numTargets; ++i) { - transition.addTarget(targetIds.get(i)); - } - } } private Transition loadTransition(Transition transition, AttributeSet attrs) diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java index 636e3b4..70e7f95 100644 --- a/core/java/android/tv/TvInputService.java +++ b/core/java/android/tv/TvInputService.java @@ -150,6 +150,7 @@ public abstract class TvInputService extends Service { private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); private final WindowManager mWindowManager; private WindowManager.LayoutParams mWindowParams; + private Surface mSurface; private View mOverlayView; private boolean mOverlayViewEnabled; private IBinder mWindowToken; @@ -346,6 +347,10 @@ public abstract class TvInputService extends Service { */ void release() { onRelease(); + if (mSurface != null) { + mSurface.release(); + mSurface = null; + } removeOverlayView(true); } @@ -354,6 +359,10 @@ public abstract class TvInputService extends Service { */ void setSurface(Surface surface) { onSetSurface(surface); + if (mSurface != null) { + mSurface.release(); + } + mSurface = surface; // TODO: Handle failure. } diff --git a/core/java/android/util/Range.java b/core/java/android/util/Range.java new file mode 100644 index 0000000..9a4bd4b --- /dev/null +++ b/core/java/android/util/Range.java @@ -0,0 +1,144 @@ +/* + * 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.util; + +import static com.android.internal.util.Preconditions.*; + +import android.hardware.camera2.impl.HashCodeHelpers; + +/** + * Immutable class for describing the range of two numeric values. + * <p> + * A range (or "interval") defines the inclusive boundaries around a contiguous span of + * values of some {@link Comparable} type; for example, + * "integers from 1 to 100 inclusive." + * </p> + * <p> + * All ranges are bounded, and the left side of the range is always {@code >=} + * the right side of the range. + * </p> + * + * <p>Although the implementation itself is immutable, there is no restriction that objects + * stored must also be immutable. If mutable objects are stored here, then the range + * effectively becomes mutable. </p> + */ +public final class Range<T extends Comparable<? super T>> { + /** + * Create a new immutable range. + * + * <p> + * The endpoints are {@code [lower, upper]}; that + * is the range is bounded. {@code lower} must be {@link Comparable#compareTo lesser or equal} + * to {@code upper}. + * </p> + * + * @param lower The lower endpoint (inclusive) + * @param upper The upper endpoint (inclusive) + * + * @throws NullPointerException if {@code lower} or {@code upper} is {@code null} + */ + public Range(final T lower, final T upper) { + mLower = checkNotNull(lower, "lower must not be null"); + mUpper = checkNotNull(upper, "upper must not be null"); + + if (lower.compareTo(upper) > 0) { + throw new IllegalArgumentException("lower must be less than or equal to upper"); + } + } + + /** + * Create a new immutable range, with the argument types inferred. + * + * <p> + * The endpoints are {@code [lower, upper]}; that + * is the range is bounded. {@code lower} must be {@link Comparable#compareTo lesser or equal} + * to {@code upper}. + * </p> + * + * @param lower The lower endpoint (inclusive) + * @param upper The upper endpoint (inclusive) + * + * @throws NullPointerException if {@code lower} or {@code upper} is {@code null} + */ + public static <T extends Comparable<? super T>> Range<T> create(final T lower, final T upper) { + return new Range<T>(lower, upper); + } + + /** + * Get the lower endpoint. + * + * @return a non-{@code null} {@code T} reference + */ + public T getLower() { + return mLower; + } + + /** + * Get the upper endpoint. + * + * @return a non-{@code null} {@code T} reference + */ + public T getUpper() { + return mUpper; + } + + /** + * Compare two ranges for equality. + * + * <p>A range is considered equal if and only if both the lower and upper endpoints + * are also equal.</p> + * + * @return {@code true} if the ranges are equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof Range) { + @SuppressWarnings("rawtypes") + final + Range other = (Range) obj; + return mLower.equals(other.mLower) && mUpper.equals(other.mUpper); + } + return false; + } + + /** + * Return the range as a string representation {@code "[lower, upper]"}. + * + * @return string representation of the range + */ + @Override + public String toString() { + return String.format("[%s, %s]", mLower, mUpper); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mLower, mUpper); + } + + private final T mLower; + private final T mUpper; +}; diff --git a/core/java/android/util/Size.java b/core/java/android/util/Size.java new file mode 100644 index 0000000..ba1a35f --- /dev/null +++ b/core/java/android/util/Size.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013 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.util; + +/** + * Immutable class for describing width and height dimensions in pixels. + */ +public final class Size { + /** + * Create a new immutable Size instance. + * + * @param width The width of the size, in pixels + * @param height The height of the size, in pixels + */ + public Size(int width, int height) { + mWidth = width; + mHeight = height; + } + + /** + * Get the width of the size (in pixels). + * @return width + */ + public int getWidth() { + return mWidth; + } + + /** + * Get the height of the size (in pixels). + * @return height + */ + public int getHeight() { + return mHeight; + } + + /** + * Check if this size is equal to another size. + * <p> + * Two sizes are equal if and only if both their widths and heights are + * equal. + * </p> + * <p> + * A size object is never equal to any other type of object. + * </p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof Size) { + Size other = (Size) obj; + return mWidth == other.mWidth && mHeight == other.mHeight; + } + return false; + } + + /** + * Return the size represented as a string with the format {@code "WxH"} + * + * @return string representation of the size + */ + @Override + public String toString() { + return mWidth + "x" + mHeight; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + // assuming most sizes are <2^16, doing a rotate will give us perfect hashing + return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); + } + + private final int mWidth; + private final int mHeight; +}; diff --git a/core/java/android/util/SizeF.java b/core/java/android/util/SizeF.java new file mode 100644 index 0000000..0a8b4ed --- /dev/null +++ b/core/java/android/util/SizeF.java @@ -0,0 +1,108 @@ +/* + * 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.util; + +import static com.android.internal.util.Preconditions.*; + +/** + * Immutable class for describing width and height dimensions in some arbitrary + * unit. + * <p> + * Width and height are finite values stored as a floating point representation. + * </p> + */ +public final class SizeF { + /** + * Create a new immutable SizeF instance. + * + * <p>Both the {@code width} and the {@code height} must be a finite number. + * In particular, {@code NaN} and positive/negative infinity are illegal values.</p> + * + * @param width The width of the size + * @param height The height of the size + * + * @throws IllegalArgumentException + * if either {@code width} or {@code height} was not finite. + */ + public SizeF(final float width, final float height) { + mWidth = checkArgumentFinite(width, "width"); + mHeight = checkArgumentFinite(height, "height"); + } + + /** + * Get the width of the size (as an arbitrary unit). + * @return width + */ + public float getWidth() { + return mWidth; + } + + /** + * Get the height of the size (as an arbitrary unit). + * @return height + */ + public float getHeight() { + return mHeight; + } + + /** + * Check if this size is equal to another size. + * + * <p>Two sizes are equal if and only if both their widths and heights are the same.</p> + * + * <p>For this purpose, the width/height float values are considered to be the same if and only + * if the method {@link Float#floatToIntBits(float)} returns the identical {@code int} value + * when applied to each.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof SizeF) { + final SizeF other = (SizeF) obj; + return mWidth == other.mWidth && mHeight == other.mHeight; + } + return false; + } + + /** + * Return the size represented as a string with the format {@code "WxH"} + * + * @return string representation of the size + */ + @Override + public String toString() { + return mWidth + "x" + mHeight; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Float.floatToIntBits(mWidth) ^ Float.floatToIntBits(mHeight); + } + + private final float mWidth; + private final float mHeight; +}; diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index f1523ae..0a76075 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -259,6 +259,14 @@ public final class Choreographer { return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay; } + /** + * @return The refresh rate as the nanoseconds between frames + * @hide + */ + long getFrameIntervalNanos() { + return mFrameIntervalNanos; + } + void dump(String prefix, PrintWriter writer) { String innerPrefix = prefix + " "; writer.print(prefix); writer.println("Choreographer:"); diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 34b85d9..11948b2 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -18,6 +18,7 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.CanvasProperty; import android.graphics.DrawFilter; import android.graphics.Matrix; import android.graphics.NinePatch; @@ -889,6 +890,16 @@ class GLES20Canvas extends HardwareCanvas { float radius, long paint); @Override + public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, + CanvasProperty<Float> radius, CanvasProperty<Paint> paint) { + nDrawCircle(mRenderer, cx.getNativeContainer(), cy.getNativeContainer(), + radius.getNativeContainer(), paint.getNativeContainer()); + } + + private static native void nDrawCircle(long renderer, long propCx, + long propCy, long propRadius, long propPaint); + + @Override public void drawColor(int color) { drawColor(color, PorterDuff.Mode.SRC_OVER); } diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index 2b29e5c..a94ec3a 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.NonNull; import android.util.Pools.SynchronizedPool; /** @@ -32,19 +33,25 @@ class GLES20RecordingCanvas extends GLES20Canvas { private static final SynchronizedPool<GLES20RecordingCanvas> sPool = new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT); + RenderNode mNode; + private GLES20RecordingCanvas() { super(true, true); } - static GLES20RecordingCanvas obtain() { + static GLES20RecordingCanvas obtain(@NonNull RenderNode node) { + if (node == null) throw new IllegalArgumentException("node cannot be null"); + GLES20RecordingCanvas canvas = sPool.acquire(); if (canvas == null) { canvas = new GLES20RecordingCanvas(); } + canvas.mNode = node; return canvas; } void recycle() { + mNode = null; sPool.release(this); } diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 233f846..7ec2cc6 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -18,6 +18,7 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.CanvasProperty; import android.graphics.Paint; import android.graphics.Rect; @@ -189,4 +190,7 @@ public abstract class HardwareCanvas extends Canvas { * @hide */ abstract void clearLayerUpdates(); + + public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, + CanvasProperty<Float> radius, CanvasProperty<Paint> paint); } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index c3f429c..05e202b 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -645,12 +645,11 @@ public class KeyEvent extends InputEvent implements Parcelable { // NOTE: If you add a new keycode here you must also add it to: // isSystem() // frameworks/native/include/android/keycodes.h - // frameworks/base/include/androidfw/KeycodeLabels.h + // frameworks/base/include/androidfw/InputEventAttributes.h // external/webkit/WebKit/android/plugins/ANPKeyCodes.h // frameworks/base/core/res/res/values/attrs.xml // emulator? // LAST_KEYCODE - // KEYCODE_SYMBOLIC_NAMES // // Also Android currently does not reserve code ranges for vendor- // specific key codes. If you have new key codes to have, you @@ -658,237 +657,6 @@ public class KeyEvent extends InputEvent implements Parcelable { // those new codes. This is intended to maintain a consistent // set of key code definitions across all Android devices. - // Symbolic names of all key codes. - private static final SparseArray<String> KEYCODE_SYMBOLIC_NAMES = new SparseArray<String>(); - private static void populateKeycodeSymbolicNames() { - SparseArray<String> names = KEYCODE_SYMBOLIC_NAMES; - names.append(KEYCODE_UNKNOWN, "KEYCODE_UNKNOWN"); - names.append(KEYCODE_SOFT_LEFT, "KEYCODE_SOFT_LEFT"); - names.append(KEYCODE_SOFT_RIGHT, "KEYCODE_SOFT_RIGHT"); - names.append(KEYCODE_HOME, "KEYCODE_HOME"); - names.append(KEYCODE_BACK, "KEYCODE_BACK"); - names.append(KEYCODE_CALL, "KEYCODE_CALL"); - names.append(KEYCODE_ENDCALL, "KEYCODE_ENDCALL"); - names.append(KEYCODE_0, "KEYCODE_0"); - names.append(KEYCODE_1, "KEYCODE_1"); - names.append(KEYCODE_2, "KEYCODE_2"); - names.append(KEYCODE_3, "KEYCODE_3"); - names.append(KEYCODE_4, "KEYCODE_4"); - names.append(KEYCODE_5, "KEYCODE_5"); - names.append(KEYCODE_6, "KEYCODE_6"); - names.append(KEYCODE_7, "KEYCODE_7"); - names.append(KEYCODE_8, "KEYCODE_8"); - names.append(KEYCODE_9, "KEYCODE_9"); - names.append(KEYCODE_STAR, "KEYCODE_STAR"); - names.append(KEYCODE_POUND, "KEYCODE_POUND"); - names.append(KEYCODE_DPAD_UP, "KEYCODE_DPAD_UP"); - names.append(KEYCODE_DPAD_DOWN, "KEYCODE_DPAD_DOWN"); - names.append(KEYCODE_DPAD_LEFT, "KEYCODE_DPAD_LEFT"); - names.append(KEYCODE_DPAD_RIGHT, "KEYCODE_DPAD_RIGHT"); - names.append(KEYCODE_DPAD_CENTER, "KEYCODE_DPAD_CENTER"); - names.append(KEYCODE_VOLUME_UP, "KEYCODE_VOLUME_UP"); - names.append(KEYCODE_VOLUME_DOWN, "KEYCODE_VOLUME_DOWN"); - names.append(KEYCODE_POWER, "KEYCODE_POWER"); - names.append(KEYCODE_CAMERA, "KEYCODE_CAMERA"); - names.append(KEYCODE_CLEAR, "KEYCODE_CLEAR"); - names.append(KEYCODE_A, "KEYCODE_A"); - names.append(KEYCODE_B, "KEYCODE_B"); - names.append(KEYCODE_C, "KEYCODE_C"); - names.append(KEYCODE_D, "KEYCODE_D"); - names.append(KEYCODE_E, "KEYCODE_E"); - names.append(KEYCODE_F, "KEYCODE_F"); - names.append(KEYCODE_G, "KEYCODE_G"); - names.append(KEYCODE_H, "KEYCODE_H"); - names.append(KEYCODE_I, "KEYCODE_I"); - names.append(KEYCODE_J, "KEYCODE_J"); - names.append(KEYCODE_K, "KEYCODE_K"); - names.append(KEYCODE_L, "KEYCODE_L"); - names.append(KEYCODE_M, "KEYCODE_M"); - names.append(KEYCODE_N, "KEYCODE_N"); - names.append(KEYCODE_O, "KEYCODE_O"); - names.append(KEYCODE_P, "KEYCODE_P"); - names.append(KEYCODE_Q, "KEYCODE_Q"); - names.append(KEYCODE_R, "KEYCODE_R"); - names.append(KEYCODE_S, "KEYCODE_S"); - names.append(KEYCODE_T, "KEYCODE_T"); - names.append(KEYCODE_U, "KEYCODE_U"); - names.append(KEYCODE_V, "KEYCODE_V"); - names.append(KEYCODE_W, "KEYCODE_W"); - names.append(KEYCODE_X, "KEYCODE_X"); - names.append(KEYCODE_Y, "KEYCODE_Y"); - names.append(KEYCODE_Z, "KEYCODE_Z"); - names.append(KEYCODE_COMMA, "KEYCODE_COMMA"); - names.append(KEYCODE_PERIOD, "KEYCODE_PERIOD"); - names.append(KEYCODE_ALT_LEFT, "KEYCODE_ALT_LEFT"); - names.append(KEYCODE_ALT_RIGHT, "KEYCODE_ALT_RIGHT"); - names.append(KEYCODE_SHIFT_LEFT, "KEYCODE_SHIFT_LEFT"); - names.append(KEYCODE_SHIFT_RIGHT, "KEYCODE_SHIFT_RIGHT"); - names.append(KEYCODE_TAB, "KEYCODE_TAB"); - names.append(KEYCODE_SPACE, "KEYCODE_SPACE"); - names.append(KEYCODE_SYM, "KEYCODE_SYM"); - names.append(KEYCODE_EXPLORER, "KEYCODE_EXPLORER"); - names.append(KEYCODE_ENVELOPE, "KEYCODE_ENVELOPE"); - names.append(KEYCODE_ENTER, "KEYCODE_ENTER"); - names.append(KEYCODE_DEL, "KEYCODE_DEL"); - names.append(KEYCODE_GRAVE, "KEYCODE_GRAVE"); - names.append(KEYCODE_MINUS, "KEYCODE_MINUS"); - names.append(KEYCODE_EQUALS, "KEYCODE_EQUALS"); - names.append(KEYCODE_LEFT_BRACKET, "KEYCODE_LEFT_BRACKET"); - names.append(KEYCODE_RIGHT_BRACKET, "KEYCODE_RIGHT_BRACKET"); - names.append(KEYCODE_BACKSLASH, "KEYCODE_BACKSLASH"); - names.append(KEYCODE_SEMICOLON, "KEYCODE_SEMICOLON"); - names.append(KEYCODE_APOSTROPHE, "KEYCODE_APOSTROPHE"); - names.append(KEYCODE_SLASH, "KEYCODE_SLASH"); - names.append(KEYCODE_AT, "KEYCODE_AT"); - names.append(KEYCODE_NUM, "KEYCODE_NUM"); - names.append(KEYCODE_HEADSETHOOK, "KEYCODE_HEADSETHOOK"); - names.append(KEYCODE_FOCUS, "KEYCODE_FOCUS"); - names.append(KEYCODE_PLUS, "KEYCODE_PLUS"); - names.append(KEYCODE_MENU, "KEYCODE_MENU"); - names.append(KEYCODE_NOTIFICATION, "KEYCODE_NOTIFICATION"); - names.append(KEYCODE_SEARCH, "KEYCODE_SEARCH"); - names.append(KEYCODE_MEDIA_PLAY_PAUSE, "KEYCODE_MEDIA_PLAY_PAUSE"); - names.append(KEYCODE_MEDIA_STOP, "KEYCODE_MEDIA_STOP"); - names.append(KEYCODE_MEDIA_NEXT, "KEYCODE_MEDIA_NEXT"); - names.append(KEYCODE_MEDIA_PREVIOUS, "KEYCODE_MEDIA_PREVIOUS"); - names.append(KEYCODE_MEDIA_REWIND, "KEYCODE_MEDIA_REWIND"); - names.append(KEYCODE_MEDIA_FAST_FORWARD, "KEYCODE_MEDIA_FAST_FORWARD"); - names.append(KEYCODE_MUTE, "KEYCODE_MUTE"); - names.append(KEYCODE_PAGE_UP, "KEYCODE_PAGE_UP"); - names.append(KEYCODE_PAGE_DOWN, "KEYCODE_PAGE_DOWN"); - names.append(KEYCODE_PICTSYMBOLS, "KEYCODE_PICTSYMBOLS"); - names.append(KEYCODE_SWITCH_CHARSET, "KEYCODE_SWITCH_CHARSET"); - names.append(KEYCODE_BUTTON_A, "KEYCODE_BUTTON_A"); - names.append(KEYCODE_BUTTON_B, "KEYCODE_BUTTON_B"); - names.append(KEYCODE_BUTTON_C, "KEYCODE_BUTTON_C"); - names.append(KEYCODE_BUTTON_X, "KEYCODE_BUTTON_X"); - names.append(KEYCODE_BUTTON_Y, "KEYCODE_BUTTON_Y"); - names.append(KEYCODE_BUTTON_Z, "KEYCODE_BUTTON_Z"); - names.append(KEYCODE_BUTTON_L1, "KEYCODE_BUTTON_L1"); - names.append(KEYCODE_BUTTON_R1, "KEYCODE_BUTTON_R1"); - names.append(KEYCODE_BUTTON_L2, "KEYCODE_BUTTON_L2"); - names.append(KEYCODE_BUTTON_R2, "KEYCODE_BUTTON_R2"); - names.append(KEYCODE_BUTTON_THUMBL, "KEYCODE_BUTTON_THUMBL"); - names.append(KEYCODE_BUTTON_THUMBR, "KEYCODE_BUTTON_THUMBR"); - names.append(KEYCODE_BUTTON_START, "KEYCODE_BUTTON_START"); - names.append(KEYCODE_BUTTON_SELECT, "KEYCODE_BUTTON_SELECT"); - names.append(KEYCODE_BUTTON_MODE, "KEYCODE_BUTTON_MODE"); - names.append(KEYCODE_ESCAPE, "KEYCODE_ESCAPE"); - names.append(KEYCODE_FORWARD_DEL, "KEYCODE_FORWARD_DEL"); - names.append(KEYCODE_CTRL_LEFT, "KEYCODE_CTRL_LEFT"); - names.append(KEYCODE_CTRL_RIGHT, "KEYCODE_CTRL_RIGHT"); - names.append(KEYCODE_CAPS_LOCK, "KEYCODE_CAPS_LOCK"); - names.append(KEYCODE_SCROLL_LOCK, "KEYCODE_SCROLL_LOCK"); - names.append(KEYCODE_META_LEFT, "KEYCODE_META_LEFT"); - names.append(KEYCODE_META_RIGHT, "KEYCODE_META_RIGHT"); - names.append(KEYCODE_FUNCTION, "KEYCODE_FUNCTION"); - names.append(KEYCODE_SYSRQ, "KEYCODE_SYSRQ"); - names.append(KEYCODE_BREAK, "KEYCODE_BREAK"); - names.append(KEYCODE_MOVE_HOME, "KEYCODE_MOVE_HOME"); - names.append(KEYCODE_MOVE_END, "KEYCODE_MOVE_END"); - names.append(KEYCODE_INSERT, "KEYCODE_INSERT"); - names.append(KEYCODE_FORWARD, "KEYCODE_FORWARD"); - names.append(KEYCODE_MEDIA_PLAY, "KEYCODE_MEDIA_PLAY"); - names.append(KEYCODE_MEDIA_PAUSE, "KEYCODE_MEDIA_PAUSE"); - names.append(KEYCODE_MEDIA_CLOSE, "KEYCODE_MEDIA_CLOSE"); - names.append(KEYCODE_MEDIA_EJECT, "KEYCODE_MEDIA_EJECT"); - names.append(KEYCODE_MEDIA_RECORD, "KEYCODE_MEDIA_RECORD"); - names.append(KEYCODE_F1, "KEYCODE_F1"); - names.append(KEYCODE_F2, "KEYCODE_F2"); - names.append(KEYCODE_F3, "KEYCODE_F3"); - names.append(KEYCODE_F4, "KEYCODE_F4"); - names.append(KEYCODE_F5, "KEYCODE_F5"); - names.append(KEYCODE_F6, "KEYCODE_F6"); - names.append(KEYCODE_F7, "KEYCODE_F7"); - names.append(KEYCODE_F8, "KEYCODE_F8"); - names.append(KEYCODE_F9, "KEYCODE_F9"); - names.append(KEYCODE_F10, "KEYCODE_F10"); - names.append(KEYCODE_F11, "KEYCODE_F11"); - names.append(KEYCODE_F12, "KEYCODE_F12"); - names.append(KEYCODE_NUM_LOCK, "KEYCODE_NUM_LOCK"); - names.append(KEYCODE_NUMPAD_0, "KEYCODE_NUMPAD_0"); - names.append(KEYCODE_NUMPAD_1, "KEYCODE_NUMPAD_1"); - names.append(KEYCODE_NUMPAD_2, "KEYCODE_NUMPAD_2"); - names.append(KEYCODE_NUMPAD_3, "KEYCODE_NUMPAD_3"); - names.append(KEYCODE_NUMPAD_4, "KEYCODE_NUMPAD_4"); - names.append(KEYCODE_NUMPAD_5, "KEYCODE_NUMPAD_5"); - names.append(KEYCODE_NUMPAD_6, "KEYCODE_NUMPAD_6"); - names.append(KEYCODE_NUMPAD_7, "KEYCODE_NUMPAD_7"); - names.append(KEYCODE_NUMPAD_8, "KEYCODE_NUMPAD_8"); - names.append(KEYCODE_NUMPAD_9, "KEYCODE_NUMPAD_9"); - names.append(KEYCODE_NUMPAD_DIVIDE, "KEYCODE_NUMPAD_DIVIDE"); - names.append(KEYCODE_NUMPAD_MULTIPLY, "KEYCODE_NUMPAD_MULTIPLY"); - names.append(KEYCODE_NUMPAD_SUBTRACT, "KEYCODE_NUMPAD_SUBTRACT"); - names.append(KEYCODE_NUMPAD_ADD, "KEYCODE_NUMPAD_ADD"); - names.append(KEYCODE_NUMPAD_DOT, "KEYCODE_NUMPAD_DOT"); - names.append(KEYCODE_NUMPAD_COMMA, "KEYCODE_NUMPAD_COMMA"); - names.append(KEYCODE_NUMPAD_ENTER, "KEYCODE_NUMPAD_ENTER"); - names.append(KEYCODE_NUMPAD_EQUALS, "KEYCODE_NUMPAD_EQUALS"); - names.append(KEYCODE_NUMPAD_LEFT_PAREN, "KEYCODE_NUMPAD_LEFT_PAREN"); - names.append(KEYCODE_NUMPAD_RIGHT_PAREN, "KEYCODE_NUMPAD_RIGHT_PAREN"); - names.append(KEYCODE_VOLUME_MUTE, "KEYCODE_VOLUME_MUTE"); - names.append(KEYCODE_INFO, "KEYCODE_INFO"); - names.append(KEYCODE_CHANNEL_UP, "KEYCODE_CHANNEL_UP"); - names.append(KEYCODE_CHANNEL_DOWN, "KEYCODE_CHANNEL_DOWN"); - names.append(KEYCODE_ZOOM_IN, "KEYCODE_ZOOM_IN"); - names.append(KEYCODE_ZOOM_OUT, "KEYCODE_ZOOM_OUT"); - names.append(KEYCODE_TV, "KEYCODE_TV"); - names.append(KEYCODE_WINDOW, "KEYCODE_WINDOW"); - names.append(KEYCODE_GUIDE, "KEYCODE_GUIDE"); - names.append(KEYCODE_DVR, "KEYCODE_DVR"); - names.append(KEYCODE_BOOKMARK, "KEYCODE_BOOKMARK"); - names.append(KEYCODE_CAPTIONS, "KEYCODE_CAPTIONS"); - names.append(KEYCODE_SETTINGS, "KEYCODE_SETTINGS"); - names.append(KEYCODE_TV_POWER, "KEYCODE_TV_POWER"); - names.append(KEYCODE_TV_INPUT, "KEYCODE_TV_INPUT"); - names.append(KEYCODE_STB_INPUT, "KEYCODE_STB_INPUT"); - names.append(KEYCODE_STB_POWER, "KEYCODE_STB_POWER"); - names.append(KEYCODE_AVR_POWER, "KEYCODE_AVR_POWER"); - names.append(KEYCODE_AVR_INPUT, "KEYCODE_AVR_INPUT"); - names.append(KEYCODE_PROG_RED, "KEYCODE_PROG_RED"); - names.append(KEYCODE_PROG_GREEN, "KEYCODE_PROG_GREEN"); - names.append(KEYCODE_PROG_YELLOW, "KEYCODE_PROG_YELLOW"); - names.append(KEYCODE_PROG_BLUE, "KEYCODE_PROG_BLUE"); - names.append(KEYCODE_APP_SWITCH, "KEYCODE_APP_SWITCH"); - names.append(KEYCODE_BUTTON_1, "KEYCODE_BUTTON_1"); - names.append(KEYCODE_BUTTON_2, "KEYCODE_BUTTON_2"); - names.append(KEYCODE_BUTTON_3, "KEYCODE_BUTTON_3"); - names.append(KEYCODE_BUTTON_4, "KEYCODE_BUTTON_4"); - names.append(KEYCODE_BUTTON_5, "KEYCODE_BUTTON_5"); - names.append(KEYCODE_BUTTON_6, "KEYCODE_BUTTON_6"); - names.append(KEYCODE_BUTTON_7, "KEYCODE_BUTTON_7"); - names.append(KEYCODE_BUTTON_8, "KEYCODE_BUTTON_8"); - names.append(KEYCODE_BUTTON_9, "KEYCODE_BUTTON_9"); - names.append(KEYCODE_BUTTON_10, "KEYCODE_BUTTON_10"); - names.append(KEYCODE_BUTTON_11, "KEYCODE_BUTTON_11"); - names.append(KEYCODE_BUTTON_12, "KEYCODE_BUTTON_12"); - names.append(KEYCODE_BUTTON_13, "KEYCODE_BUTTON_13"); - names.append(KEYCODE_BUTTON_14, "KEYCODE_BUTTON_14"); - names.append(KEYCODE_BUTTON_15, "KEYCODE_BUTTON_15"); - names.append(KEYCODE_BUTTON_16, "KEYCODE_BUTTON_16"); - names.append(KEYCODE_LANGUAGE_SWITCH, "KEYCODE_LANGUAGE_SWITCH"); - names.append(KEYCODE_MANNER_MODE, "KEYCODE_MANNER_MODE"); - names.append(KEYCODE_3D_MODE, "KEYCODE_3D_MODE"); - names.append(KEYCODE_CONTACTS, "KEYCODE_CONTACTS"); - names.append(KEYCODE_CALENDAR, "KEYCODE_CALENDAR"); - names.append(KEYCODE_MUSIC, "KEYCODE_MUSIC"); - names.append(KEYCODE_CALCULATOR, "KEYCODE_CALCULATOR"); - names.append(KEYCODE_ZENKAKU_HANKAKU, "KEYCODE_ZENKAKU_HANKAKU"); - names.append(KEYCODE_EISU, "KEYCODE_EISU"); - names.append(KEYCODE_MUHENKAN, "KEYCODE_MUHENKAN"); - names.append(KEYCODE_HENKAN, "KEYCODE_HENKAN"); - names.append(KEYCODE_KATAKANA_HIRAGANA, "KEYCODE_KATAKANA_HIRAGANA"); - names.append(KEYCODE_YEN, "KEYCODE_YEN"); - names.append(KEYCODE_RO, "KEYCODE_RO"); - names.append(KEYCODE_KANA, "KEYCODE_KANA"); - names.append(KEYCODE_ASSIST, "KEYCODE_ASSIST"); - names.append(KEYCODE_BRIGHTNESS_DOWN, "KEYCODE_BRIGHTNESS_DOWN"); - names.append(KEYCODE_BRIGHTNESS_UP, "KEYCODE_BRIGHTNESS_UP"); - names.append(KEYCODE_MEDIA_AUDIO_TRACK, "KEYCODE_MEDIA_AUDIO_TRACK"); - names.append(KEYCODE_SLEEP, "KEYCODE_SLEEP"); - names.append(KEYCODE_WAKEUP, "KEYCODE_WAKEUP"); - }; - // Symbolic names of all metakeys in bit order from least significant to most significant. // Accordingly there are exactly 32 values in this table. private static final String[] META_SYMBOLIC_NAMES = new String[] { @@ -926,6 +694,8 @@ public class KeyEvent extends InputEvent implements Parcelable { "0x80000000", }; + private static final String LABEL_PREFIX = "KEYCODE_"; + /** * @deprecated There are now more than MAX_KEYCODE keycodes. * Use {@link #getMaxKeyCode()} instead. @@ -1367,9 +1137,8 @@ public class KeyEvent extends InputEvent implements Parcelable { boolean onKeyMultiple(int keyCode, int count, KeyEvent event); } - static { - populateKeycodeSymbolicNames(); - } + private static native String nativeKeyCodeToString(int keyCode); + private static native int nativeKeyCodeFromString(String keyCode); private KeyEvent() { } @@ -1792,19 +1561,15 @@ public class KeyEvent extends InputEvent implements Parcelable { return mAction == ACTION_DOWN; } - /** - * Is this a system key? System keys can not be used for menu shortcuts. - * - * TODO: this information should come from a table somewhere. - * TODO: should the dpad keys be here? arguably, because they also shouldn't be menu shortcuts + /** Is this a system key? System keys can not be used for menu shortcuts. */ public final boolean isSystem() { - return native_isSystemKey(mKeyCode); + return isSystemKey(mKeyCode); } /** @hide */ - public final boolean hasDefaultAction() { - return native_hasDefaultAction(mKeyCode); + public final boolean isWakeKey() { + return isWakeKey(mKeyCode); } /** @@ -1887,6 +1652,62 @@ public class KeyEvent extends InputEvent implements Parcelable { return false; } + + /** Is this a system key? System keys can not be used for menu shortcuts. + * @hide + */ + public static final boolean isSystemKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_MENU: + case KeyEvent.KEYCODE_SOFT_RIGHT: + case KeyEvent.KEYCODE_HOME: + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_CALL: + case KeyEvent.KEYCODE_ENDCALL: + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_MUTE: + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_POWER: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + case KeyEvent.KEYCODE_CAMERA: + case KeyEvent.KEYCODE_FOCUS: + case KeyEvent.KEYCODE_SEARCH: + case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: + case KeyEvent.KEYCODE_BRIGHTNESS_UP: + case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + return true; + } + + return false; + } + + /** @hide */ + public static final boolean isWakeKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_POWER: + case KeyEvent.KEYCODE_MENU: + case KeyEvent.KEYCODE_SLEEP: + case KeyEvent.KEYCODE_WAKEUP: + return true; + } + return false; + } + /** {@inheritDoc} */ @Override public final int getDeviceId() { @@ -2866,8 +2687,8 @@ public class KeyEvent extends InputEvent implements Parcelable { * @see KeyCharacterMap#getDisplayLabel */ public static String keyCodeToString(int keyCode) { - String symbolicName = KEYCODE_SYMBOLIC_NAMES.get(keyCode); - return symbolicName != null ? symbolicName : Integer.toString(keyCode); + String symbolicName = nativeKeyCodeToString(keyCode); + return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(keyCode); } /** @@ -2879,17 +2700,13 @@ public class KeyEvent extends InputEvent implements Parcelable { * @see #keycodeToString(int) */ public static int keyCodeFromString(String symbolicName) { - if (symbolicName == null) { - throw new IllegalArgumentException("symbolicName must not be null"); + if (symbolicName.startsWith(LABEL_PREFIX)) { + symbolicName = symbolicName.substring(LABEL_PREFIX.length()); } - - final int count = KEYCODE_SYMBOLIC_NAMES.size(); - for (int i = 0; i < count; i++) { - if (symbolicName.equals(KEYCODE_SYMBOLIC_NAMES.valueAt(i))) { - return i; - } + int keyCode = nativeKeyCodeFromString(symbolicName); + if (keyCode > 0) { + return keyCode; } - try { return Integer.parseInt(symbolicName, 10); } catch (NumberFormatException ex) { @@ -2977,7 +2794,4 @@ public class KeyEvent extends InputEvent implements Parcelable { out.writeLong(mDownTime); out.writeLong(mEventTime); } - - private native boolean native_isSystemKey(int keyCode); - private native boolean native_hasDefaultAction(int keyCode); } diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index e19bda9..b9ed801 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -21,13 +21,16 @@ import android.os.Handler; import android.os.Message; import android.os.Trace; import android.widget.FrameLayout; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.util.AttributeSet; +import android.util.Log; import android.util.Xml; import java.io.IOException; @@ -61,7 +64,8 @@ import java.util.HashMap; * @see Context#getSystemService */ public abstract class LayoutInflater { - private final boolean DEBUG = false; + private static final String TAG = LayoutInflater.class.getSimpleName(); + private static final boolean DEBUG = false; /** * This field should be made private, so it is hidden from the SDK. @@ -395,8 +399,13 @@ public abstract class LayoutInflater { * the inflated XML file. */ public View inflate(int resource, ViewGroup root, boolean attachToRoot) { - if (DEBUG) System.out.println("INFLATING from resource: " + resource); - XmlResourceParser parser = getContext().getResources().getLayout(resource); + final Resources res = getContext().getResources(); + if (DEBUG) { + Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + + Integer.toHexString(resource) + ")"); + } + + final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 6378ffd..0626ab9 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -167,6 +167,7 @@ import android.util.SparseArray; */ public final class MotionEvent extends InputEvent implements Parcelable { private static final long NS_PER_MS = 1000000; + private static final String LABEL_PREFIX = "AXIS_"; /** * An invalid pointer id. @@ -1369,6 +1370,9 @@ public final class MotionEvent extends InputEvent implements Parcelable { private static native long nativeReadFromParcel(long nativePtr, Parcel parcel); private static native void nativeWriteToParcel(long nativePtr, Parcel parcel); + private static native String nativeAxisToString(int axis); + private static native int nativeAxisFromString(String label); + private MotionEvent() { } @@ -3051,8 +3055,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @return The symbolic name of the specified axis. */ public static String axisToString(int axis) { - String symbolicName = AXIS_SYMBOLIC_NAMES.get(axis); - return symbolicName != null ? symbolicName : Integer.toString(axis); + String symbolicName = nativeAxisToString(axis); + return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(axis); } /** @@ -3064,17 +3068,13 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see KeyEvent#keyCodeToString(int) */ public static int axisFromString(String symbolicName) { - if (symbolicName == null) { - throw new IllegalArgumentException("symbolicName must not be null"); + if (symbolicName.startsWith(LABEL_PREFIX)) { + symbolicName = symbolicName.substring(LABEL_PREFIX.length()); } - - final int count = AXIS_SYMBOLIC_NAMES.size(); - for (int i = 0; i < count; i++) { - if (symbolicName.equals(AXIS_SYMBOLIC_NAMES.valueAt(i))) { - return i; - } + int axis = nativeAxisFromString(symbolicName); + if (axis >= 0) { + return axis; } - try { return Integer.parseInt(symbolicName, 10); } catch (NumberFormatException ex) { diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 3dc09c4..0cfde94 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -20,6 +20,9 @@ import android.annotation.NonNull; import android.graphics.Matrix; import android.graphics.Outline; +import java.util.ArrayList; +import java.util.List; + /** * <p>A display list records a series of graphics related operations and can replay * them later. Display lists are usually built by recording operations on a @@ -176,11 +179,24 @@ public class RenderNode { private boolean mValid; private final long mNativeRenderNode; + // We need to keep a strong reference to all running animators to ensure that + // they can call removeAnimator when they have finished, as the native-side + // object can only hold a WeakReference<> to avoid leaking memory due to + // cyclic references. + private List<RenderNodeAnimator> mActiveAnimators; + private RenderNode(String name) { mNativeRenderNode = nCreate(name); } /** + * @see RenderNode#adopt(long) + */ + private RenderNode(long nativePtr) { + mNativeRenderNode = nativePtr; + } + + /** * Creates a new display list that can be used to record batches of * drawing operations. * @@ -195,6 +211,17 @@ public class RenderNode { } /** + * Adopts an existing native render node. + * + * Note: This will *NOT* incRef() on the native object, however it will + * decRef() when it is destroyed. The caller should have already incRef'd it + */ + public static RenderNode adopt(long nativePtr) { + return new RenderNode(nativePtr); + } + + + /** * Starts recording a display list for the render node. All * operations performed on the returned canvas are recorded and * stored in this display list. @@ -212,7 +239,7 @@ public class RenderNode { * @see #isValid() */ public HardwareCanvas start(int width, int height) { - HardwareCanvas canvas = GLES20RecordingCanvas.obtain(); + HardwareCanvas canvas = GLES20RecordingCanvas.obtain(this); canvas.setViewport(width, height); // The dirty rect should always be null for a display list canvas.onPreDraw(null); @@ -822,6 +849,23 @@ public class RenderNode { } /////////////////////////////////////////////////////////////////////////// + // Animations + /////////////////////////////////////////////////////////////////////////// + + public void addAnimator(RenderNodeAnimator animator) { + if (mActiveAnimators == null) { + mActiveAnimators = new ArrayList<RenderNodeAnimator>(); + } + mActiveAnimators.add(animator); + nAddAnimator(mNativeRenderNode, animator.getNativeAnimator()); + } + + public void removeAnimator(RenderNodeAnimator animator) { + nRemoveAnimator(mNativeRenderNode, animator.getNativeAnimator()); + mActiveAnimators.remove(animator); + } + + /////////////////////////////////////////////////////////////////////////// // Native methods /////////////////////////////////////////////////////////////////////////// @@ -896,6 +940,13 @@ public class RenderNode { private static native void nOutput(long renderNode); /////////////////////////////////////////////////////////////////////////// + // Animations + /////////////////////////////////////////////////////////////////////////// + + private static native void nAddAnimator(long renderNode, long animatorPtr); + private static native void nRemoveAnimator(long renderNode, long animatorPtr); + + /////////////////////////////////////////////////////////////////////////// // Finalization /////////////////////////////////////////////////////////////////////////// diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java new file mode 100644 index 0000000..a675821 --- /dev/null +++ b/core/java/android/view/RenderNodeAnimator.java @@ -0,0 +1,155 @@ +/* + * 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.view; + +import android.graphics.Canvas; +import android.graphics.CanvasProperty; +import android.graphics.Paint; +import android.util.SparseIntArray; + +import java.lang.ref.WeakReference; + +/** + * @hide + */ +public final class RenderNodeAnimator { + + // Keep in sync with enum RenderProperty in Animator.h + public static final int TRANSLATION_X = 0; + public static final int TRANSLATION_Y = 1; + public static final int TRANSLATION_Z = 2; + public static final int SCALE_X = 3; + public static final int SCALE_Y = 4; + public static final int ROTATION = 5; + public static final int ROTATION_X = 6; + public static final int ROTATION_Y = 7; + public static final int X = 8; + public static final int Y = 9; + public static final int Z = 10; + public static final int ALPHA = 11; + + // Keep in sync with enum PaintFields in Animator.h + public static final int PAINT_STROKE_WIDTH = 0; + public static final int PAINT_ALPHA = 1; + + // ViewPropertyAnimator uses a mask for its values, we need to remap them + // to the enum values here. RenderPropertyAnimator can't use the mask values + // directly as internally it uses a lookup table so it needs the values to + // be sequential starting from 0 + private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{ + put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X); + put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y); + put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z); + put(ViewPropertyAnimator.SCALE_X, SCALE_X); + put(ViewPropertyAnimator.SCALE_Y, SCALE_Y); + put(ViewPropertyAnimator.ROTATION, ROTATION); + put(ViewPropertyAnimator.ROTATION_X, ROTATION_X); + put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y); + put(ViewPropertyAnimator.X, X); + put(ViewPropertyAnimator.Y, Y); + put(ViewPropertyAnimator.Z, Z); + put(ViewPropertyAnimator.ALPHA, ALPHA); + }}; + + // Keep in sync DeltaValueType in Animator.h + public static final int DELTA_TYPE_ABSOLUTE = 0; + public static final int DELTA_TYPE_DELTA = 1; + + private RenderNode mTarget; + private long mNativePtr; + + public int mapViewPropertyToRenderProperty(int viewProperty) { + return sViewPropertyAnimatorMap.get(viewProperty); + } + + public RenderNodeAnimator(int property, int deltaType, float deltaValue) { + mNativePtr = nCreateAnimator(new WeakReference<RenderNodeAnimator>(this), + property, deltaType, deltaValue); + } + + public RenderNodeAnimator(CanvasProperty<Float> property, int deltaType, float deltaValue) { + mNativePtr = nCreateCanvasPropertyFloatAnimator( + new WeakReference<RenderNodeAnimator>(this), + property.getNativeContainer(), deltaType, deltaValue); + } + + public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, + int deltaType, float deltaValue) { + mNativePtr = nCreateCanvasPropertyPaintAnimator( + new WeakReference<RenderNodeAnimator>(this), + property.getNativeContainer(), paintField, deltaType, deltaValue); + } + + public void start(View target) { + mTarget = target.mRenderNode; + mTarget.addAnimator(this); + // Kick off a frame to start the process + target.invalidateViewProperty(true, false); + } + + public void start(Canvas canvas) { + if (!(canvas instanceof GLES20RecordingCanvas)) { + throw new IllegalArgumentException("Not a GLES20RecordingCanvas"); + } + GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas; + mTarget = recordingCanvas.mNode; + mTarget.addAnimator(this); + } + + public void cancel() { + mTarget.removeAnimator(this); + } + + public void setDuration(int duration) { + nSetDuration(mNativePtr, duration); + } + + long getNativeAnimator() { + return mNativePtr; + } + + private void onFinished() { + mTarget.removeAnimator(this); + } + + // Called by native + private static void callOnFinished(WeakReference<RenderNodeAnimator> weakThis) { + RenderNodeAnimator animator = weakThis.get(); + if (animator != null) { + animator.onFinished(); + } + } + + @Override + protected void finalize() throws Throwable { + try { + nUnref(mNativePtr); + mNativePtr = 0; + } finally { + super.finalize(); + } + } + + private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis, + int property, int deltaValueType, float deltaValue); + private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis, + long canvasProperty, int deltaValueType, float deltaValue); + private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis, + long canvasProperty, int paintField, int deltaValueType, float deltaValue); + private static native void nSetDuration(long nativePtr, int duration); + private static native void nUnref(long nativePtr); +} diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 2d55a01..c15ce44 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -74,8 +74,10 @@ public class SurfaceControl { IBinder displayToken, int orientation, int l, int t, int r, int b, int L, int T, int R, int B); - private static native boolean nativeGetDisplayInfo( - IBinder displayToken, SurfaceControl.PhysicalDisplayInfo outInfo); + private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs( + IBinder displayToken); + private static native int nativeGetActiveConfig(IBinder displayToken); + private static native boolean nativeSetActiveConfig(IBinder displayToken, int id); private static native void nativeBlankDisplay(IBinder displayToken); private static native void nativeUnblankDisplay(IBinder displayToken); @@ -499,14 +501,25 @@ public class SurfaceControl { nativeBlankDisplay(displayToken); } - public static boolean getDisplayInfo(IBinder displayToken, SurfaceControl.PhysicalDisplayInfo outInfo) { + public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); } - if (outInfo == null) { - throw new IllegalArgumentException("outInfo must not be null"); + return nativeGetDisplayConfigs(displayToken); + } + + public static int getActiveConfig(IBinder displayToken) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + return nativeGetActiveConfig(displayToken); + } + + public static boolean setActiveConfig(IBinder displayToken, int id) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); } - return nativeGetDisplayInfo(displayToken, outInfo); + return nativeSetActiveConfig(displayToken, id); } public static void setDisplayProjection(IBinder displayToken, diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 3cfe5e9..1765c43 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -23,7 +23,6 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; -import android.os.Looper; import android.util.AttributeSet; import android.util.Log; @@ -119,8 +118,6 @@ public class TextureView extends View { private boolean mUpdateLayer; private boolean mUpdateSurface; - private SurfaceTexture.OnFrameAvailableListener mUpdateListener; - private Canvas mCanvas; private int mSaveCount; @@ -370,21 +367,7 @@ public class TextureView extends View { mSurface.setDefaultBufferSize(getWidth(), getHeight()); nCreateNativeWindow(mSurface); - mUpdateListener = new SurfaceTexture.OnFrameAvailableListener() { - @Override - public void onFrameAvailable(SurfaceTexture surfaceTexture) { - // Per SurfaceTexture's documentation, the callback may be invoked - // from an arbitrary thread - updateLayer(); - - if (Looper.myLooper() == Looper.getMainLooper()) { - invalidate(); - } else { - postInvalidate(); - } - } - }; - mSurface.setOnFrameAvailableListener(mUpdateListener); + mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler); if (mListener != null && !mUpdateSurface) { mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight()); @@ -422,7 +405,7 @@ public class TextureView extends View { // To cancel updates, the easiest thing to do is simply to remove the // updates listener if (visibility == VISIBLE) { - mSurface.setOnFrameAvailableListener(mUpdateListener); + mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler); updateLayerAndInvalidate(); } else { mSurface.setOnFrameAvailableListener(null); @@ -767,6 +750,15 @@ public class TextureView extends View { mListener = listener; } + private final SurfaceTexture.OnFrameAvailableListener mUpdateListener = + new SurfaceTexture.OnFrameAvailableListener() { + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + updateLayer(); + invalidate(); + } + }; + /** * This listener can be used to be notified when the surface texture * associated with this texture view is available. diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index c7a6d41..0bf99d3 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -19,7 +19,6 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.SurfaceTexture; -import android.os.SystemClock; import android.os.Trace; import android.view.Surface.OutOfResourcesException; import android.view.View.AttachInfo; @@ -50,17 +49,31 @@ import java.io.PrintWriter; public class ThreadedRenderer extends HardwareRenderer { private static final String LOGTAG = "ThreadedRenderer"; - private static final Rect NULL_RECT = new Rect(-1, -1, -1, -1); + private static final Rect NULL_RECT = new Rect(); + + private static final long NANOS_PER_MS = 1000000; + + // Keep in sync with DrawFrameTask.h SYNC_* flags + // Nothing interesting to report + private static final int SYNC_OK = 0x0; + // Needs a ViewRoot invalidate + private static final int SYNC_INVALIDATE_REQUIRED = 0x1; private int mWidth, mHeight; private long mNativeProxy; private boolean mInitialized = false; private RenderNode mRootNode; + private Choreographer mChoreographer; ThreadedRenderer(boolean translucent) { - mNativeProxy = nCreateProxy(translucent); - mRootNode = RenderNode.create("RootNode"); + long rootNodePtr = nCreateRootRenderNode(); + mRootNode = RenderNode.adopt(rootNodePtr); mRootNode.setClipToBounds(false); + mNativeProxy = nCreateProxy(translucent, rootNodePtr); + + // Setup timing + mChoreographer = Choreographer.getInstance(); + nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos()); } @Override @@ -157,16 +170,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override boolean loadSystemProperties() { - return false; - } - - /** - * TODO: Remove - * Temporary hack to allow RenderThreadTest prototype app to trigger - * replaying a DisplayList after modifying the displaylist properties - * - * @hide */ - public void repeatLastDraw() { + return nLoadSystemProperties(mNativeProxy); } private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) { @@ -193,7 +197,8 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { attachInfo.mIgnoreDirtyState = true; - attachInfo.mDrawingTime = SystemClock.uptimeMillis(); + long frameTimeNanos = mChoreographer.getFrameTimeNanos(); + attachInfo.mDrawingTime = frameTimeNanos / NANOS_PER_MS; updateRootDisplayList(view, callbacks); @@ -202,8 +207,11 @@ public class ThreadedRenderer extends HardwareRenderer { if (dirty == null) { dirty = NULL_RECT; } - nDrawDisplayList(mNativeProxy, mRootNode.getNativeDisplayList(), + int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos, dirty.left, dirty.top, dirty.right, dirty.bottom); + if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) { + attachInfo.mViewRootImpl.invalidate(); + } } @Override @@ -293,16 +301,20 @@ public class ThreadedRenderer extends HardwareRenderer { /** @hide */ public static native void postToRenderThread(Runnable runnable); - private static native long nCreateProxy(boolean translucent); + private static native long nCreateRootRenderNode(); + private static native long nCreateProxy(boolean translucent, long rootRenderNode); private static native void nDeleteProxy(long nativeProxy); + private static native void nSetFrameInterval(long nativeProxy, long frameIntervalNanos); + private static native boolean nLoadSystemProperties(long nativeProxy); + private static native boolean nInitialize(long nativeProxy, Surface window); private static native void nUpdateSurface(long nativeProxy, Surface window); private static native void nPauseSurface(long nativeProxy, Surface window); private static native void nSetup(long nativeProxy, int width, int height); private static native void nSetDisplayListData(long nativeProxy, long displayList, long newData); - private static native void nDrawDisplayList(long nativeProxy, long displayList, + private static native int nSyncAndDrawFrame(long nativeProxy, long frameTimeNanos, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom); private static native void nRunWithGlContext(long nativeProxy, Runnable runnable); private static native void nDestroyCanvasAndSurface(long nativeProxy); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 463a2f7..69840c4 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6147,12 +6147,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // call into it as a fallback in case we're in a class that overrides it // and has logic to perform. if (fitSystemWindows(insets.getSystemWindowInsets())) { - return insets.cloneWithSystemWindowInsetsConsumed(); + return insets.consumeSystemWindowInsets(); } } else { // We were called from within a direct call to fitSystemWindows. if (fitSystemWindowsInt(insets.getSystemWindowInsets())) { - return insets.cloneWithSystemWindowInsetsConsumed(); + return insets.consumeSystemWindowInsets(); } } return insets; @@ -9958,7 +9958,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @attr ref android.R.styleable#View_transformPivotX */ public void setPivotX(float pivotX) { - if (mRenderNode.isPivotExplicitlySet() || pivotX != getPivotX()) { + if (!mRenderNode.isPivotExplicitlySet() || pivotX != getPivotX()) { invalidateViewProperty(true, false); mRenderNode.setPivotX(pivotX); invalidateViewProperty(false, true); @@ -9999,7 +9999,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @attr ref android.R.styleable#View_transformPivotY */ public void setPivotY(float pivotY) { - if (mRenderNode.isPivotExplicitlySet() || pivotY != getPivotY()) { + if (!mRenderNode.isPivotExplicitlySet() || pivotY != getPivotY()) { invalidateViewProperty(true, false); mRenderNode.setPivotY(pivotY); invalidateViewProperty(false, true); @@ -10633,8 +10633,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { // always copy the path since caller may reuse if (mOutline == null) { - mOutline = new Outline(outline); + mOutline = new Outline(); } + mOutline.set(outline); } mRenderNode.setOutline(mOutline); } @@ -17988,7 +17989,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * <p>If this property is set to true the view will be permitted to initiate nested * scrolling operations with a compatible parent view in the current hierarchy. If this - * view does not implement nested scrolling this will have no effect.</p> + * view does not implement nested scrolling this will have no effect. Disabling nested scrolling + * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping} + * the nested scroll.</p> * * @param enabled true to enable nested scrolling, false to disable * @@ -17998,6 +18001,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (enabled) { mPrivateFlags3 |= PFLAG3_NESTED_SCROLLING_ENABLED; } else { + stopNestedScroll(); mPrivateFlags3 &= ~PFLAG3_NESTED_SCROLLING_ENABLED; } } @@ -18137,23 +18141,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { - int startX = 0; - int startY = 0; - if (offsetInWindow != null) { - getLocationInWindow(offsetInWindow); - startX = offsetInWindow[0]; - startY = offsetInWindow[1]; - } + if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) { + int startX = 0; + int startY = 0; + if (offsetInWindow != null) { + getLocationInWindow(offsetInWindow); + startX = offsetInWindow[0]; + startY = offsetInWindow[1]; + } - mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed, - dxUnconsumed, dyUnconsumed); + mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed, + dxUnconsumed, dyUnconsumed); - if (offsetInWindow != null) { - getLocationInWindow(offsetInWindow); - offsetInWindow[0] -= startX; - offsetInWindow[1] -= startY; + if (offsetInWindow != null) { + getLocationInWindow(offsetInWindow); + offsetInWindow[0] -= startX; + offsetInWindow[1] -= startY; + } + return true; + } else if (offsetInWindow != null) { + // No motion, no dispatch. Keep offsetInWindow up to date. + offsetInWindow[0] = 0; + offsetInWindow[1] = 0; } - return true; } return false; } @@ -18179,30 +18189,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { - int startX = 0; - int startY = 0; - if (offsetInWindow != null) { - getLocationInWindow(offsetInWindow); - startX = offsetInWindow[0]; - startY = offsetInWindow[1]; - } - - if (consumed == null) { - if (mTempNestedScrollConsumed == null) { - mTempNestedScrollConsumed = new int[2]; + if (dx != 0 || dy != 0) { + int startX = 0; + int startY = 0; + if (offsetInWindow != null) { + getLocationInWindow(offsetInWindow); + startX = offsetInWindow[0]; + startY = offsetInWindow[1]; } - consumed = mTempNestedScrollConsumed; - } - consumed[0] = 0; - consumed[1] = 0; - mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed); - if (offsetInWindow != null) { - getLocationInWindow(offsetInWindow); - offsetInWindow[0] -= startX; - offsetInWindow[1] -= startY; + if (consumed == null) { + if (mTempNestedScrollConsumed == null) { + mTempNestedScrollConsumed = new int[2]; + } + consumed = mTempNestedScrollConsumed; + } + consumed[0] = 0; + consumed[1] = 0; + mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed); + + if (offsetInWindow != null) { + getLocationInWindow(offsetInWindow); + offsetInWindow[0] -= startX; + offsetInWindow[1] -= startY; + } + return consumed[0] != 0 || consumed[1] != 0; + } else if (offsetInWindow != null) { + offsetInWindow[0] = 0; + offsetInWindow[1] = 0; } - return consumed[0] != 0 || consumed[1] != 0; } return false; } @@ -18210,18 +18225,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Dispatch a fling to a nested scrolling parent. * - * <p>If a nested scrolling child view would normally fling but it is at the edge of its - * own content it should use this method to delegate the fling to its nested scrolling parent. - * The view implementation can use a {@link VelocityTracker} to obtain the velocity values - * to pass.</p> + * <p>This method should be used to indicate that a nested scrolling child has detected + * suitable conditions for a fling. Generally this means that a touch scroll has ended with a + * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds + * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity} + * along a scrollable axis.</p> + * + * <p>If a nested scrolling child view would normally fling but it is at the edge of + * its own content, it can use this method to delegate the fling to its nested scrolling + * parent instead. The parent may optionally consume the fling or observe a child fling.</p> * * @param velocityX Horizontal fling velocity in pixels per second * @param velocityY Vertical fling velocity in pixels per second - * @return true if the nested scrolling parent consumed the fling + * @param consumed true if the child consumed the fling, false otherwise + * @return true if the nested scrolling parent consumed or otherwise reacted to the fling */ - public boolean dispatchNestedFling(float velocityX, float velocityY) { + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { - return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY); + return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY, consumed); } return false; } @@ -18863,6 +18884,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, }; /** + * A Property wrapper around the <code>z</code> functionality handled by the + * {@link View#setZ(float)} and {@link View#getZ()} methods. + */ + public static final Property<View, Float> Z = new FloatProperty<View>("z") { + @Override + public void setValue(View object, float value) { + object.setZ(value); + } + + @Override + public Float get(View object) { + return object.getZ(); + } + }; + + /** * A Property wrapper around the <code>rotation</code> functionality handled by the * {@link View#setRotation(float)} and {@link View#getRotation()} methods. */ diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 5112b9a..4e91ad4 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -234,6 +234,7 @@ public class ViewConfiguration { private final int mOverscrollDistance; private final int mOverflingDistance; private final boolean mFadingMarqueeEnabled; + private final long mGlobalActionsKeyTimeout; private boolean sHasPermanentMenuKey; private boolean sHasPermanentMenuKeySet; @@ -261,6 +262,7 @@ public class ViewConfiguration { mOverscrollDistance = OVERSCROLL_DISTANCE; mOverflingDistance = OVERFLING_DISTANCE; mFadingMarqueeEnabled = true; + mGlobalActionsKeyTimeout = GLOBAL_ACTIONS_KEY_TIMEOUT; } /** @@ -287,8 +289,6 @@ public class ViewConfiguration { mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f); mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f); - mMinimumFlingVelocity = (int) (density * MINIMUM_FLING_VELOCITY + 0.5f); - mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f); mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f); mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f); mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f); @@ -339,6 +339,13 @@ public class ViewConfiguration { mPagingTouchSlop = mTouchSlop * 2; mDoubleTapTouchSlop = mTouchSlop; + + mMinimumFlingVelocity = res.getDimensionPixelSize( + com.android.internal.R.dimen.config_viewMinFlingVelocity); + mMaximumFlingVelocity = res.getDimensionPixelSize( + com.android.internal.R.dimen.config_viewMaxFlingVelocity); + mGlobalActionsKeyTimeout = res.getInteger( + com.android.internal.R.integer.config_globalActionsKeyTimeout); } /** @@ -695,12 +702,26 @@ public class ViewConfiguration { * * @return how long a user needs to press the relevant key to bring up * the global actions dialog. + * @deprecated This timeout should not be used by applications */ + @Deprecated public static long getGlobalActionKeyTimeout() { return GLOBAL_ACTIONS_KEY_TIMEOUT; } /** + * The amount of time a user needs to press the relevant key to bring up + * the global actions dialog. + * + * @return how long a user needs to press the relevant key to bring up + * the global actions dialog. + * @hide + */ + public long getDeviceGlobalActionKeyTimeout() { + return mGlobalActionsKeyTimeout; + } + + /** * The amount of friction applied to scrolls and flings. * * @return A scalar dimensionless value representing the coefficient of diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 8865ab4..43bc0b6 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2342,7 +2342,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; - stopNestedScroll(); } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } @@ -5914,7 +5913,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @inheritDoc */ @Override - public boolean onNestedFling(View target, float velocityX, float velocityY) { + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return false; } diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 3cd6449..588b9cd 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -512,14 +512,21 @@ public interface ViewParent { /** * Request a fling from a nested scroll. * + * <p>This method signifies that a nested scrolling child has detected suitable conditions + * for a fling. Generally this means that a touch scroll has ended with a + * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds + * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity} + * along a scrollable axis.</p> + * * <p>If a nested scrolling child view would normally fling but it is at the edge of - * its own content, it can delegate the fling to its nested scrolling parent instead. - * This method allows the parent to optionally consume the fling.</p> + * its own content, it can use this method to delegate the fling to its nested scrolling + * parent instead. The parent may optionally consume the fling or observe a child fling.</p> * * @param target View that initiated the nested scroll * @param velocityX Horizontal velocity in pixels per second. * @param velocityY Vertical velocity in pixels per second - * @return true if this parent consumed the fling + * @param consumed true if the child consumed the fling, false otherwise + * @return true if this parent consumed or otherwise reacted to the fling */ - public boolean onNestedFling(View target, float velocityX, float velocityY); + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed); } diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index b1aa7b2..11d2622 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -133,19 +133,19 @@ public class ViewPropertyAnimator { * Constants used to associate a property being requested and the mechanism used to set * the property (this class calls directly into View to set the properties in question). */ - private static final int NONE = 0x0000; - private static final int TRANSLATION_X = 0x0001; - private static final int TRANSLATION_Y = 0x0002; - private static final int TRANSLATION_Z = 0x0004; - private static final int SCALE_X = 0x0008; - private static final int SCALE_Y = 0x0010; - private static final int ROTATION = 0x0020; - private static final int ROTATION_X = 0x0040; - private static final int ROTATION_Y = 0x0080; - private static final int X = 0x0100; - private static final int Y = 0x0200; - private static final int Z = 0x0400; - private static final int ALPHA = 0x0800; + static final int NONE = 0x0000; + static final int TRANSLATION_X = 0x0001; + static final int TRANSLATION_Y = 0x0002; + static final int TRANSLATION_Z = 0x0004; + static final int SCALE_X = 0x0008; + static final int SCALE_Y = 0x0010; + static final int ROTATION = 0x0020; + static final int ROTATION_X = 0x0040; + static final int ROTATION_Y = 0x0080; + static final int X = 0x0100; + static final int Y = 0x0200; + static final int Z = 0x0400; + static final int ALPHA = 0x0800; private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z | SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 14e422c..db87394 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -234,6 +234,7 @@ public final class ViewRootImpl implements ViewParent, InputStage mFirstInputStage; InputStage mFirstPostImeInputStage; + InputStage mSyntheticInputStage; boolean mWindowAttributesChanged = false; int mWindowAttributesChangesFlag = 0; @@ -599,8 +600,8 @@ public final class ViewRootImpl implements ViewParent, // Set up the input pipeline. CharSequence counterSuffix = attrs.getTitle(); - InputStage syntheticInputStage = new SyntheticInputStage(); - InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticInputStage); + mSyntheticInputStage = new SyntheticInputStage(); + InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); @@ -2587,10 +2588,6 @@ public final class ViewRootImpl implements ViewParent, * @param canvas The canvas on which to draw. */ private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) { - if (!mAttachInfo.mHasWindowFocus) { - return; - } - final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext); if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) { return; @@ -3007,6 +3004,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_INVALIDATE_WORLD = 23; private final static int MSG_WINDOW_MOVED = 24; private final static int MSG_FLUSH_LAYER_UPDATES = 25; + private final static int MSG_SYNTHESIZE_INPUT_EVENT = 26; final class ViewRootHandler extends Handler { @Override @@ -3056,6 +3054,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_WINDOW_MOVED"; case MSG_FLUSH_LAYER_UPDATES: return "MSG_FLUSH_LAYER_UPDATES"; + case MSG_SYNTHESIZE_INPUT_EVENT: + return "MSG_SYNTHESIZE_INPUT_EVENT"; } return super.getMessageName(message); } @@ -3218,6 +3218,10 @@ public final class ViewRootImpl implements ViewParent, enqueueInputEvent(event, receiver, 0, true); args.recycle(); } break; + case MSG_SYNTHESIZE_INPUT_EVENT: { + InputEvent event = (InputEvent)msg.obj; + enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true); + } break; case MSG_DISPATCH_KEY_FROM_IME: { if (LOCAL_LOGV) Log.v( TAG, "Dispatching key " @@ -3227,7 +3231,8 @@ public final class ViewRootImpl implements ViewParent, // The IME is trying to say this event is from the // system! Bad bad bad! //noinspection UnusedAssignment - event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM); + event = KeyEvent.changeFlags(event, event.getFlags() & + ~KeyEvent.FLAG_FROM_SYSTEM); } enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true); } break; @@ -4023,6 +4028,7 @@ public final class ViewRootImpl implements ViewParent, private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler(); private final SyntheticTouchNavigationHandler mTouchNavigation = new SyntheticTouchNavigationHandler(); + private final SyntheticKeyboardHandler mKeyboard = new SyntheticKeyboardHandler(); public SyntheticInputStage() { super(null); @@ -4045,7 +4051,11 @@ public final class ViewRootImpl implements ViewParent, mTouchNavigation.process(event); return FINISH_HANDLED; } + } else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) { + mKeyboard.process((KeyEvent)q.mEvent); + return FINISH_HANDLED; } + return FORWARD; } @@ -4882,6 +4892,33 @@ public final class ViewRootImpl implements ViewParent, }; } + final class SyntheticKeyboardHandler { + public void process(KeyEvent event) { + if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) { + return; + } + + final KeyCharacterMap kcm = event.getKeyCharacterMap(); + final int keyCode = event.getKeyCode(); + final int metaState = event.getMetaState(); + + // Check for fallback actions specified by the key character map. + KeyCharacterMap.FallbackAction fallbackAction = + kcm.getFallbackAction(keyCode, metaState); + if (fallbackAction != null) { + final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK; + KeyEvent fallbackEvent = KeyEvent.obtain( + event.getDownTime(), event.getEventTime(), + event.getAction(), fallbackAction.keyCode, + event.getRepeatCount(), fallbackAction.metaState, + event.getDeviceId(), event.getScanCode(), + flags, event.getSource(), null); + fallbackAction.recycle(); + enqueueInputEvent(fallbackEvent); + } + } + } + /** * Returns true if the key is used for keyboard navigation. * @param keyEvent The key event. @@ -5461,6 +5498,7 @@ public final class ViewRootImpl implements ViewParent, public static final int FLAG_FINISHED = 1 << 2; public static final int FLAG_FINISHED_HANDLED = 1 << 3; public static final int FLAG_RESYNTHESIZED = 1 << 4; + public static final int FLAG_UNHANDLED = 1 << 5; public QueuedInputEvent mNext; @@ -5475,6 +5513,14 @@ public final class ViewRootImpl implements ViewParent, return mEvent instanceof MotionEvent && mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER); } + + public boolean shouldSendToSynthesizer() { + if ((mFlags & FLAG_UNHANDLED) != 0) { + return true; + } + + return false; + } } private QueuedInputEvent obtainQueuedInputEvent(InputEvent event, @@ -5578,7 +5624,13 @@ public final class ViewRootImpl implements ViewParent, mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0); } - InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; + InputStage stage; + if (q.shouldSendToSynthesizer()) { + stage = mSyntheticInputStage; + } else { + stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; + } + if (stage != null) { stage.deliver(q); } else { @@ -5810,43 +5862,29 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } - public void dispatchKeyFromIme(KeyEvent event) { - Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_IME, event); + public void synthesizeInputEvent(InputEvent event) { + Message msg = mHandler.obtainMessage(MSG_SYNTHESIZE_INPUT_EVENT, event); msg.setAsynchronous(true); mHandler.sendMessage(msg); } - public void dispatchUnhandledKey(KeyEvent event) { - if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { - // Some fallback keys are decided by the ViewRoot as they might have special - // properties (e.g. are locale aware). These take precedence over fallbacks defined by - // the kcm. - final KeyCharacterMap kcm = event.getKeyCharacterMap(); - final int keyCode = event.getKeyCode(); - final int metaState = event.getMetaState(); - - // Check for fallback actions specified by the key character map. - KeyCharacterMap.FallbackAction fallbackAction = - kcm.getFallbackAction(keyCode, metaState); - if (fallbackAction != null) { - final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK; - KeyEvent fallbackEvent = KeyEvent.obtain( - event.getDownTime(), event.getEventTime(), - event.getAction(), fallbackAction.keyCode, - event.getRepeatCount(), fallbackAction.metaState, - event.getDeviceId(), event.getScanCode(), - flags, event.getSource(), null); - fallbackAction.recycle(); - dispatchInputEvent(fallbackEvent); - } - } + public void dispatchKeyFromIme(KeyEvent event) { + Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_IME, event); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); } + /** + * Reinject unhandled {@link InputEvent}s in order to synthesize fallbacks events. + * + * Note that it is the responsibility of the caller of this API to recycle the InputEvent it + * passes in. + */ public void dispatchUnhandledInputEvent(InputEvent event) { - if (event instanceof KeyEvent) { - dispatchUnhandledKey((KeyEvent) event); - return; + if (event instanceof MotionEvent) { + event = MotionEvent.obtain((MotionEvent) event); } + synthesizeInputEvent(event); } public void dispatchAppVisibility(boolean visible) { @@ -6131,7 +6169,7 @@ public final class ViewRootImpl implements ViewParent, } @Override - public boolean onNestedFling(View target, float velocityX, float velocityY) { + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return false; } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 9c44bd1..375f5e3 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -147,6 +147,7 @@ public abstract class Window { private TypedArray mWindowStyle; private Callback mCallback; + private OnWindowDismissedCallback mOnWindowDismissedCallback; private WindowManager mWindowManager; private IBinder mAppToken; private String mAppName; @@ -405,7 +406,10 @@ public abstract class Window { * @param mode The mode that was just finished. */ public void onActionModeFinished(ActionMode mode); + } + /** @hide */ + public interface OnWindowDismissedCallback { /** * Called when a window is dismissed. This informs the callback that the * window is gone, and it should finish itself. @@ -586,6 +590,18 @@ public abstract class Window { return mCallback; } + /** @hide */ + public final void setOnWindowDismissedCallback(OnWindowDismissedCallback dcb) { + mOnWindowDismissedCallback = dcb; + } + + /** @hide */ + public final void dispatchOnWindowDismissed() { + if (mOnWindowDismissedCallback != null) { + mOnWindowDismissedCallback.onWindowDismissed(); + } + } + /** * Take ownership of this window's surface. The window's view hierarchy * will no longer draw into the surface, though it will otherwise continue diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 2160efe..294f472 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -29,7 +29,7 @@ import android.graphics.Rect; * @see View.OnApplyWindowInsetsListener * @see View#onApplyWindowInsets(WindowInsets) */ -public class WindowInsets { +public final class WindowInsets { private Rect mSystemWindowInsets; private Rect mWindowDecorInsets; private Rect mTempRect; @@ -151,6 +151,7 @@ public class WindowInsets { * This can include action bars, title bars, toolbars, etc.</p> * * @return The left window decor inset + * @hide pending API */ public int getWindowDecorInsetLeft() { return mWindowDecorInsets.left; @@ -164,6 +165,7 @@ public class WindowInsets { * This can include action bars, title bars, toolbars, etc.</p> * * @return The top window decor inset + * @hide pending API */ public int getWindowDecorInsetTop() { return mWindowDecorInsets.top; @@ -177,6 +179,7 @@ public class WindowInsets { * This can include action bars, title bars, toolbars, etc.</p> * * @return The right window decor inset + * @hide pending API */ public int getWindowDecorInsetRight() { return mWindowDecorInsets.right; @@ -190,6 +193,7 @@ public class WindowInsets { * This can include action bars, title bars, toolbars, etc.</p> * * @return The bottom window decor inset + * @hide pending API */ public int getWindowDecorInsetBottom() { return mWindowDecorInsets.bottom; @@ -217,6 +221,7 @@ public class WindowInsets { * This can include action bars, title bars, toolbars, etc.</p> * * @return true if any of the window decor inset values are nonzero + * @hide pending API */ public boolean hasWindowDecorInsets() { return mWindowDecorInsets.left != 0 || mWindowDecorInsets.top != 0 || @@ -246,13 +251,28 @@ public class WindowInsets { return mIsRound; } - public WindowInsets cloneWithSystemWindowInsetsConsumed() { + /** + * Returns a copy of this WindowInsets with the system window insets fully consumed. + * + * @return A modified copy of this WindowInsets + */ + public WindowInsets consumeSystemWindowInsets() { final WindowInsets result = new WindowInsets(this); result.mSystemWindowInsets = new Rect(0, 0, 0, 0); return result; } - public WindowInsets cloneWithSystemWindowInsetsConsumed(boolean left, boolean top, + /** + * Returns a copy of this WindowInsets with selected system window insets fully consumed. + * + * @param left true to consume the left system window inset + * @param top true to consume the top system window inset + * @param right true to consume the right system window inset + * @param bottom true to consume the bottom system window inset + * @return A modified copy of this WindowInsets + * @hide pending API + */ + public WindowInsets consumeSystemWindowInsets(boolean left, boolean top, boolean right, boolean bottom) { if (left || top || right || bottom) { final WindowInsets result = new WindowInsets(this); @@ -265,19 +285,36 @@ public class WindowInsets { return this; } - public WindowInsets cloneWithSystemWindowInsets(int left, int top, int right, int bottom) { + /** + * Returns a copy of this WindowInsets with selected system window insets replaced + * with new values. + * + * @param left New left inset in pixels + * @param top New top inset in pixels + * @param right New right inset in pixels + * @param bottom New bottom inset in pixels + * @return A modified copy of this WindowInsets + */ + public WindowInsets replaceSystemWindowInsets(int left, int top, + int right, int bottom) { final WindowInsets result = new WindowInsets(this); result.mSystemWindowInsets = new Rect(left, top, right, bottom); return result; } - public WindowInsets cloneWithWindowDecorInsetsConsumed() { + /** + * @hide + */ + public WindowInsets consumeWindowDecorInsets() { final WindowInsets result = new WindowInsets(this); result.mWindowDecorInsets.set(0, 0, 0, 0); return result; } - public WindowInsets cloneWithWindowDecorInsetsConsumed(boolean left, boolean top, + /** + * @hide + */ + public WindowInsets consumeWindowDecorInsets(boolean left, boolean top, boolean right, boolean bottom) { if (left || top || right || bottom) { final WindowInsets result = new WindowInsets(this); @@ -290,7 +327,10 @@ public class WindowInsets { return this; } - public WindowInsets cloneWithWindowDecorInsets(int left, int top, int right, int bottom) { + /** + * @hide + */ + public WindowInsets replaceWindowDecorInsets(int left, int top, int right, int bottom) { final WindowInsets result = new WindowInsets(this); result.mWindowDecorInsets = new Rect(left, top, right, bottom); return result; diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index c9be54d..4fde1e4 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -76,14 +76,7 @@ import java.lang.annotation.RetentionPolicy; public interface WindowManagerPolicy { // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h. public final static int FLAG_WAKE = 0x00000001; - public final static int FLAG_WAKE_DROPPED = 0x00000002; - public final static int FLAG_SHIFT = 0x00000004; - public final static int FLAG_CAPS_LOCK = 0x00000008; - public final static int FLAG_ALT = 0x00000010; - public final static int FLAG_ALT_GR = 0x00000020; - public final static int FLAG_MENU = 0x00000040; - public final static int FLAG_LAUNCHER = 0x00000080; - public final static int FLAG_VIRTUAL = 0x00000100; + public final static int FLAG_VIRTUAL = 0x00000002; public final static int FLAG_INJECTED = 0x01000000; public final static int FLAG_TRUSTED = 0x02000000; @@ -450,8 +443,6 @@ public interface WindowManagerPolicy { public final int OFF_BECAUSE_OF_USER = 2; /** Screen turned off because of timeout */ public final int OFF_BECAUSE_OF_TIMEOUT = 3; - /** Screen turned off because of proximity sensor */ - public final int OFF_BECAUSE_OF_PROX_SENSOR = 4; /** @hide */ @IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED}) @@ -907,23 +898,23 @@ public interface WindowManagerPolicy { public int focusChangedLw(WindowState lastFocus, WindowState newFocus); /** - * Called after the screen turns off. + * Called when the device is going to sleep. * * @param why {@link #OFF_BECAUSE_OF_USER} or * {@link #OFF_BECAUSE_OF_TIMEOUT}. */ - public void screenTurnedOff(int why); + public void goingToSleep(int why); public interface ScreenOnListener { void onScreenOn(); } /** - * Called when the power manager would like to turn the screen on. + * Called when the device is waking up. * Must call back on the listener to tell it when the higher-level system * is ready for the screen to go on (i.e. the lock screen is shown). */ - public void screenTurningOn(ScreenOnListener screenOnListener); + public void wakingUp(ScreenOnListener screenOnListener); /** * Return whether the screen is about to turn on or is currently on. diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index f8160c8..bc2d7ec 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -259,7 +259,7 @@ public final class InputMethodInfo implements Parcelable { public InputMethodInfo(String packageName, String className, CharSequence label, String settingsActivity) { this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null, - 0, false); + 0, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */); } /** @@ -269,6 +269,17 @@ public final class InputMethodInfo implements Parcelable { public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault) { + this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, + forceDefault, true /* supportsSwitchingToNextInputMethod */); + } + + /** + * Temporary API for creating a built-in input method for test. + * @hide + */ + public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, + String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, + boolean forceDefault, boolean supportsSwitchingToNextInputMethod) { final ServiceInfo si = ri.serviceInfo; mService = ri; mId = new ComponentName(si.packageName, si.name).flattenToShortString(); @@ -277,7 +288,7 @@ public final class InputMethodInfo implements Parcelable { mIsAuxIme = isAuxIme; mSubtypes = new InputMethodSubtypeArray(subtypes); mForceDefault = forceDefault; - mSupportsSwitchingToNextInputMethod = true; + mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; } private static ResolveInfo buildDummyResolveInfo(String packageName, String className, diff --git a/core/java/android/webkit/ClientCertRequest.java b/core/java/android/webkit/ClientCertRequest.java index 8951786..588b868 100644 --- a/core/java/android/webkit/ClientCertRequest.java +++ b/core/java/android/webkit/ClientCertRequest.java @@ -36,8 +36,6 @@ import java.security.cert.X509Certificate; * host/port pair. The user can clear the cached data using * {@link WebView#clearClientCertPreferences}. * - * TODO(sgurun) unhide - * @hide */ public interface ClientCertRequest { /** diff --git a/core/java/android/webkit/PermissionRequest.java b/core/java/android/webkit/PermissionRequest.java index 2f8850b..3e33498 100644 --- a/core/java/android/webkit/PermissionRequest.java +++ b/core/java/android/webkit/PermissionRequest.java @@ -19,14 +19,11 @@ package android.webkit; import android.net.Uri; /** - * This class wraps a permission request, and is used to request permission for - * the web content to access the resources. + * This interface defines a permission request and is used when web content + * requests access to protected resources. * - * Either {@link #grant(long) grant()} or {@link #deny()} must be called to response the - * request, otherwise, {@link WebChromeClient#onPermissionRequest(PermissionRequest)} will - * not be invoked again if there is other permission request in this WebView. - * - * @hide + * Either {@link #grant(long) grant()} or {@link #deny()} must be called in UI + * thread to respond to the request. */ public interface PermissionRequest { /** @@ -62,8 +59,6 @@ public interface PermissionRequest { * must be equals or a subset of granted resources. * This parameter is designed to avoid granting permission by accident * especially when new resources are requested by web content. - * Calling grant(getResources()) has security issue, the new permission - * will be granted without being noticed. */ public void grant(long resources); diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 60cba86..d630a9a 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -304,7 +304,6 @@ public class WebChromeClient { * If this method isn't overridden, the permission is denied. * * @param request the PermissionRequest from current web content. - * @hide */ public void onPermissionRequest(PermissionRequest request) { request.deny(); @@ -314,8 +313,7 @@ public class WebChromeClient { * Notify the host application that the given permission request * has been canceled. Any related UI should therefore be hidden. * - * @param request the PermissionRequest need be canceled. - * @hide + * @param request the PermissionRequest that needs be canceled. */ public void onPermissionRequestCanceled(PermissionRequest request) {} diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 98ef66e..7c32c5b 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -171,6 +171,38 @@ public abstract class WebSettings { } /** + * Used with {@link #setMixedContentMode} + * + * In this mode, the WebView will allow a secure origin to load content from any other origin, + * even if that origin is insecure. This is the least secure mode of operation for the WebView, + * and where possible apps should not set this mode. + */ + public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0; + + /** + * Used with {@link #setMixedContentMode} + * + * In this mode, the WebView will not allow a secure origin to load content from an insecure + * origin. This is the preferred and most secure mode of operation for the WebView and apps are + * strongly advised to use this mode. + */ + public static final int MIXED_CONTENT_NEVER_ALLOW = 1; + + /** + * Used with {@link #setMixedContentMode} + * + * In this mode, the WebView will attempt to be compatible with the approach of a modern web + * browser with regard to mixed content. Some insecure content may be allowed to be loaded by + * a secure origin and other types of content will be blocked. The types of content are allowed + * or blocked may change release to release and are not explicitly defined. + * + * This mode is intended to be used by apps that are not in control of the content that they + * render but desire to operate in a reasonably secure environment. For highest security, apps + * are recommended to use {@link #MIXED_CONTENT_NEVER_ALLOW}. + */ + public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2; + + /** * Hidden constructor to prevent clients from creating a new settings * instance or deriving the class. * @@ -1403,4 +1435,29 @@ public abstract class WebSettings { public int getCacheMode() { throw new MustOverrideException(); } + + /** + * Configures the WebView's behavior when a secure origin attempts to load a resource from an + * insecure origin. + * + * By default, apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below default + * to {@link #MIXED_CONTENT_ALWAYS_ALLOW}. Apps targeting + * {@link android.os.Build.VERSION_CODES#L} default to {@link #MIXED_CONTENT_NEVER_ALLOW}. + * + * The preferred and most secure mode of operation for the WebView is + * {@link #MIXED_CONTENT_NEVER_ALLOW} and use of {@link #MIXED_CONTENT_ALWAYS_ALLOW} is + * strongly discouraged. + * + * @param mode The mixed content mode to use. One of {@link #MIXED_CONTENT_NEVER_ALLOW}, + * {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}. + */ + public abstract void setMixedContentMode(int mode); + + /** + * Gets the current behavior of the WebView with regard to loading insecure content from a + * secure origin. + * @return The current setting, one of {@link #MIXED_CONTENT_NEVER_ALLOW}, + * {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}. + */ + public abstract int getMixedContentMode(); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index c914e52..91ca7b4 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -33,6 +33,7 @@ import android.os.Looper; import android.os.Message; import android.os.StrictMode; import android.print.PrintDocumentAdapter; +import android.security.KeyChain; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; @@ -250,6 +251,15 @@ public class WebView extends AbsoluteLayout implements ViewTreeObserver.OnGlobalFocusChangeListener, ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler { + /** + * Broadcast Action: Indicates the data reduction proxy setting changed. + * Sent by the settings app when user changes the data reduction proxy value. This intent will + * always stay as a hidden API. + * @hide + */ + public static final String DATA_REDUCTION_PROXY_SETTING_CHANGED = + "android.webkit.DATA_REDUCTION_PROXY_SETTING_CHANGED"; + private static final String LOGTAG = "WebView"; // Throwing an exception for incorrect thread usage if the @@ -701,7 +711,7 @@ public class WebView extends AbsoluteLayout */ @Deprecated public static void enablePlatformNotifications() { - getFactory().getStatics().setPlatformNotificationsEnabled(true); + // noop } /** @@ -713,7 +723,7 @@ public class WebView extends AbsoluteLayout */ @Deprecated public static void disablePlatformNotifications() { - getFactory().getStatics().setPlatformNotificationsEnabled(false); + // noop } /** @@ -1479,18 +1489,16 @@ public class WebView extends AbsoluteLayout * Clears the client certificate preferences table stored in response * to proceeding/cancelling client cert requests. Note that webview * automatically clears these preferences when it receives a - * {@link KeyChain.ACTION_STORAGE_CHANGED} - * - * @param resultCallback A callback to be invoked when client certs are cleared. - * The embedder can pass null if not interested in the callback. + * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The client certificate + * preferences are global for all Webviews. * - * TODO(sgurun) unhide - * @hide + * @param onCleared A runnable to be invoked when client certs are cleared. + * The embedder can pass null if not interested in the + * callback. The runnable will be called in UI thread. */ - public void clearClientCertPreferences(ValueCallback<Void> resultCallback) { - checkThread(); + public static void clearClientCertPreferences(Runnable onCleared) { if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearClientCertPreferences"); - mProvider.clearClientCertPreferences(resultCallback); + getFactory().getStatics().clearClientCertPreferences(onCleared); } /** @@ -1610,6 +1618,8 @@ public class WebView extends AbsoluteLayout * @return the address, or if no address is found, null */ public static String findAddress(String addr) { + // TODO: Rewrite this in Java so it is not needed to start up chromium + // Could also be deprecated return getFactory().getStatics().findAddress(addr); } @@ -1672,13 +1682,15 @@ public class WebView extends AbsoluteLayout /** * Preauthorize the given origin to access resources. - * This authorization only valid for this WebView instance life cycle and + * The authorization only valid for this WebView instance's life cycle and * will not retained. * + * In the case that an origin has had resources preauthorized, calls to + * {@link WebChromeClient#onPermissionRequest(PermissionRequest)} will not be + * made for those resources from that origin. + * * @param origin the origin authorized to access resources * @param resources the resource authorized to be accessed by origin. - * - * @hide */ public void preauthorizePermission(Uri origin, long resources) { checkThread(); diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 688c251..62b80c4 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -19,6 +19,7 @@ package android.webkit; import android.graphics.Bitmap; import android.net.http.SslError; import android.os.Message; +import android.view.InputEvent; import android.view.KeyEvent; import android.view.ViewRootImpl; @@ -223,8 +224,6 @@ public class WebViewClient { * @param view The WebView that is initiating the callback * @param request An instance of a {@link ClientCertRequest} * - * TODO(sgurun) unhide - * @hide */ public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { request.cancel(); @@ -272,11 +271,43 @@ public class WebViewClient { * * @param view The WebView that is initiating the callback. * @param event The key event. + * @deprecated This method is subsumed by the more generic onUnhandledInputEvent. */ + @Deprecated public void onUnhandledKeyEvent(WebView view, KeyEvent event) { + onUnhandledInputEventInternal(view, event); + } + + /** + * Notify the host application that a input event was not handled by the WebView. + * Except system keys, WebView always consumes input events in the normal flow + * or if shouldOverrideKeyEvent returns true. This is called asynchronously + * from where the event is dispatched. It gives the host application a chance + * to handle the unhandled input events. + * + * Note that if the event is a {@link android.view.MotionEvent}, then it's lifetime is only + * that of the function call. If the WebViewClient wishes to use the event beyond that, then it + * <i>must</i> create a copy of the event. + * + * It is the responsibility of overriders of this method to call + * {@link #onUnhandledKeyEvent(WebView, KeyEvent)} + * when appropriate if they wish to continue receiving events through it. + * + * @param view The WebView that is initiating the callback. + * @param event The input event. + */ + public void onUnhandledInputEvent(WebView view, InputEvent event) { + if (event instanceof KeyEvent) { + onUnhandledKeyEvent(view, (KeyEvent) event); + return; + } + onUnhandledInputEventInternal(view, event); + } + + private void onUnhandledInputEventInternal(WebView view, InputEvent event) { ViewRootImpl root = view.getViewRootImpl(); if (root != null) { - root.dispatchUnhandledKey(event); + root.dispatchUnhandledInputEvent(event); } } diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index e391aaf..945e0e3 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -59,6 +59,13 @@ public interface WebViewFactoryProvider { * {@link android.webkit.WebView#setWebContentsDebuggingEnabled(boolean) } */ void setWebContentsDebuggingEnabled(boolean enable); + + /** + * Implements the API method: + * {@link android.webkit.WebView#clearClientCertPreferences(Runnable) } + */ + void clearClientCertPreferences(Runnable onCleared); + } Statics getStatics(); diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index efa5497..5081ff5 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -198,8 +198,6 @@ public interface WebViewProvider { public void clearSslPreferences(); - public void clearClientCertPreferences(ValueCallback<Void> resultCallback); - public WebBackForwardList copyBackForwardList(); public void setFindListener(WebView.FindListener listener); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index becda67..f4cd5fc 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -110,6 +110,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * @see #setTranscriptMode(int) */ public static final int TRANSCRIPT_MODE_DISABLED = 0; + /** * The list will automatically scroll to the bottom when a data set change * notification is received and only if the last item is already visible @@ -118,6 +119,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * @see #setTranscriptMode(int) */ public static final int TRANSCRIPT_MODE_NORMAL = 1; + /** * The list will automatically scroll to the bottom, no matter what items * are currently visible. @@ -609,6 +611,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final boolean[] mIsScrap = new boolean[1]; + private final int[] mScrollOffset = new int[2]; + private final int[] mScrollConsumed = new int[2]; + // True when the popup should be hidden because of a call to // dispatchDisplayHint() private boolean mPopupHidden; @@ -2489,8 +2494,30 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + /** + * Positions the selector in a way that mimics keyboard focus. If the + * selector drawable supports hotspots, this manages the focus hotspot. + */ + void positionSelectorLikeFocus(int position, View sel) { + positionSelector(position, sel); + + final Drawable selector = mSelector; + if (selector != null && selector.supportsHotspots() && position != INVALID_POSITION) { + final Rect bounds = mSelectorRect; + final float x = bounds.exactCenterX(); + final float y = bounds.exactCenterY(); + selector.setHotspot(R.attr.state_focused, x, y); + } + } + void positionSelector(int position, View sel) { if (position != INVALID_POSITION) { + if (mSelectorPosition != position) { + final Drawable selector = mSelector; + if (selector != null && selector.supportsHotspots()) { + selector.clearHotspots(); + } + } mSelectorPosition = position; } @@ -3240,13 +3267,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } - private boolean startScrollIfNeeded(int y) { + private boolean startScrollIfNeeded(int y, MotionEvent vtev) { // Check if we have moved far enough that it looks more like a // scroll than a tap final int deltaY = y - mMotionY; final int distance = Math.abs(deltaY); final boolean overscroll = mScrollY != 0; - if (overscroll || distance > mTouchSlop) { + if ((overscroll || distance > mTouchSlop) && + (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) { createScrollingCache(); if (overscroll) { mTouchMode = TOUCH_MODE_OVERSCROLL; @@ -3268,17 +3296,28 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } - scrollIfNeeded(y); + scrollIfNeeded(y, vtev); return true; } return false; } - private void scrollIfNeeded(int y) { - final int rawDeltaY = y - mMotionY; + private void scrollIfNeeded(int y, MotionEvent vtev) { + int rawDeltaY = y - mMotionY; + if (dispatchNestedPreScroll(0, rawDeltaY, mScrollConsumed, mScrollOffset)) { + rawDeltaY -= mScrollConsumed[1]; + mMotionCorrection -= mScrollOffset[1]; + if (mLastY != Integer.MIN_VALUE) { + mLastY -= mScrollOffset[1] + mScrollConsumed[1]; + } + if (vtev != null) { + vtev.offsetLocation(0, mScrollOffset[1]); + } + } final int deltaY = rawDeltaY - mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; + int lastYCorrection = 0; if (mTouchMode == TOUCH_MODE_SCROLL) { if (PROFILE_SCROLLING) { @@ -3337,39 +3376,48 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te int overscroll = -incrementalDeltaY - (motionViewRealTop - motionViewPrevTop); - overScrollBy(0, overscroll, 0, mScrollY, 0, 0, - 0, mOverscrollDistance, true); - if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) { - // Don't allow overfling if we're at the edge. - if (mVelocityTracker != null) { - mVelocityTracker.clear(); + if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll, + mScrollOffset)) { + mMotionCorrection -= mScrollOffset[1]; + lastYCorrection -= mScrollOffset[1]; + if (vtev != null) { + vtev.offsetLocation(0, mScrollOffset[1]); } - } - - final int overscrollMode = getOverScrollMode(); - if (overscrollMode == OVER_SCROLL_ALWAYS || - (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && - !contentFits())) { - mDirection = 0; // Reset when entering overscroll. - mTouchMode = TOUCH_MODE_OVERSCROLL; - if (rawDeltaY > 0) { - mEdgeGlowTop.onPull((float) overscroll / getHeight()); - if (!mEdgeGlowBottom.isFinished()) { - mEdgeGlowBottom.onRelease(); + } else { + overScrollBy(0, overscroll, 0, mScrollY, 0, 0, + 0, mOverscrollDistance, true); + if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) { + // Don't allow overfling if we're at the edge. + if (mVelocityTracker != null) { + mVelocityTracker.clear(); } - invalidate(mEdgeGlowTop.getBounds(false)); - } else if (rawDeltaY < 0) { - mEdgeGlowBottom.onPull((float) overscroll / getHeight()); - if (!mEdgeGlowTop.isFinished()) { - mEdgeGlowTop.onRelease(); + } + + final int overscrollMode = getOverScrollMode(); + if (overscrollMode == OVER_SCROLL_ALWAYS || + (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && + !contentFits())) { + mDirection = 0; // Reset when entering overscroll. + mTouchMode = TOUCH_MODE_OVERSCROLL; + if (deltaY > 0) { + mEdgeGlowTop.onPull((float) overscroll / getHeight()); + if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onRelease(); + } + invalidate(mEdgeGlowTop.getBounds(false)); + } else if (deltaY < 0) { + mEdgeGlowBottom.onPull((float) overscroll / getHeight()); + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onRelease(); + } + invalidate(mEdgeGlowBottom.getBounds(true)); } - invalidate(mEdgeGlowBottom.getBounds(true)); } } } mMotionY = y; } - mLastY = y; + mLastY = y + lastYCorrection; } } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) { if (y != mLastY) { @@ -3493,6 +3541,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return false; } + startNestedScroll(SCROLL_AXIS_VERTICAL); + if (mFastScroll != null) { boolean intercepted = mFastScroll.onTouchEvent(ev); if (intercepted) { @@ -3501,7 +3551,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } initVelocityTrackerIfNotExists(); - mVelocityTracker.addMovement(ev); + final MotionEvent vtev = MotionEvent.obtain(ev); final int actionMasked = ev.getActionMasked(); switch (actionMasked) { @@ -3511,7 +3561,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } case MotionEvent.ACTION_MOVE: { - onTouchMove(ev); + onTouchMove(ev, vtev); break; } @@ -3562,6 +3612,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(vtev); + } + vtev.recycle(); return true; } @@ -3628,7 +3682,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } - private void onTouchMove(MotionEvent ev) { + private void onTouchMove(MotionEvent ev, MotionEvent vtev) { int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { pointerIndex = 0; @@ -3649,7 +3703,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te case TOUCH_MODE_DONE_WAITING: // Check if we have moved far enough that it looks more like a // scroll than a tap. If so, we'll enter scrolling mode. - if (startScrollIfNeeded(y)) { + if (startScrollIfNeeded(y, vtev)) { break; } // Otherwise, check containment within list bounds. If we're @@ -3669,7 +3723,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te break; case TOUCH_MODE_SCROLL: case TOUCH_MODE_OVERSCROLL: - scrollIfNeeded(y); + scrollIfNeeded(y, vtev); break; } } @@ -3911,6 +3965,49 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override + public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0); + } + + @Override + public void onNestedScrollAccepted(View child, View target, int axes) { + super.onNestedScrollAccepted(child, target, axes); + startNestedScroll(SCROLL_AXIS_VERTICAL); + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { + final int motionIndex = getChildCount() / 2; + final View motionView = getChildAt(motionIndex); + final int oldTop = motionView != null ? motionView.getTop() : 0; + if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) { + int myUnconsumed = dyUnconsumed; + int myConsumed = 0; + if (motionView != null) { + myConsumed = motionView.getTop() - oldTop; + myUnconsumed -= myConsumed; + } + dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null); + } + } + + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + final int childCount = getChildCount(); + if (!consumed && childCount > 0 && canScrollList((int) velocityY) && + Math.abs(velocityY) > mMinimumVelocity) { + reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); + if (mFlingRunnable == null) { + mFlingRunnable = new FlingRunnable(); + } + mFlingRunnable.start((int) velocityY); + return true; + } + return dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override public void draw(Canvas canvas) { super.draw(canvas); if (mEdgeGlowTop != null) { @@ -4046,6 +4143,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mLastY = Integer.MIN_VALUE; initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); + startNestedScroll(SCROLL_AXIS_VERTICAL); if (touchMode == TOUCH_MODE_FLING) { return true; } @@ -4063,7 +4161,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int y = (int) ev.getY(pointerIndex); initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); - if (startScrollIfNeeded(y)) { + if (startScrollIfNeeded(y, null)) { return true; } break; @@ -4077,6 +4175,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); + stopNestedScroll(); break; } diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index e4575e5..51759c5 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -649,7 +649,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter private class OverflowPopup extends MenuPopupHelper { public OverflowPopup(Context context, MenuBuilder menu, View anchorView, boolean overflowOnly) { - super(context, menu, anchorView, overflowOnly); + super(context, menu, anchorView, overflowOnly, + com.android.internal.R.attr.actionOverflowMenuStyle); setGravity(Gravity.END); setCallback(mPopupPresenterCallback); } diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index b47177a..f91865b 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -622,8 +622,8 @@ public class ListPopupWindow { // only set this if the dropdown is not always visible mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); mPopup.setTouchInterceptor(mTouchInterceptor); - mPopup.showAsDropDown(getAnchorView(), - mDropDownHorizontalOffset, mDropDownVerticalOffset, mDropDownGravity); + mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset, + mDropDownVerticalOffset, mDropDownGravity); mDropDownList.setSelection(ListView.INVALID_POSITION); if (!mModal || mDropDownList.isInTouchMode()) { @@ -1565,7 +1565,7 @@ public class ListPopupWindow { // Ensure that keyboard focus starts from the last touched position. setSelectedPositionInt(position); - positionSelector(position, child); + positionSelectorLikeFocus(position, child); // Refresh the drawable state to reflect the new pressed state, // which will also update the selector state. diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 5de67c8..eeb8015 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -2564,7 +2564,7 @@ public class ListView extends AbsListView { if (needToRedraw) { if (selectedView != null) { - positionSelector(selectedPos, selectedView); + positionSelectorLikeFocus(selectedPos, selectedView); mSelectedTop = selectedView.getTop(); } if (!awakenScrollBars()) { diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 6e71a5c..01632ae 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -18,6 +18,7 @@ package android.widget; import com.android.internal.R; +import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -116,6 +117,10 @@ public class PopupWindow { private Drawable mAboveAnchorBackgroundDrawable; private Drawable mBelowAnchorBackgroundDrawable; + // Temporary animation centers. Should be moved into window params? + private int mAnchorRelativeX; + private int mAnchorRelativeY; + private boolean mAboveAnchor; private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; @@ -129,12 +134,14 @@ public class PopupWindow { }; private WeakReference<View> mAnchor; - private OnScrollChangedListener mOnScrollChangedListener = + + private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() { + @Override public void onScrollChanged() { - View anchor = mAnchor != null ? mAnchor.get() : null; + final View anchor = mAnchor != null ? mAnchor.get() : null; if (anchor != null && mPopupView != null) { - WindowManager.LayoutParams p = (WindowManager.LayoutParams) + final WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, @@ -143,7 +150,9 @@ public class PopupWindow { } } }; + private int mAnchorXoff, mAnchorYoff, mAnchoredGravity; + private boolean mOverlapAnchor; private boolean mPopupViewInitialLayoutDirectionInherited; @@ -187,6 +196,7 @@ public class PopupWindow { attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes); mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground); + mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); mAnimationStyle = animStyle == com.android.internal.R.style.Animation_PopupWindow ? -1 : @@ -934,9 +944,9 @@ public class PopupWindow { // do the job. if (mAboveAnchorBackgroundDrawable != null) { if (mAboveAnchor) { - mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable); + mPopupView.setBackground(mAboveAnchorBackgroundDrawable); } else { - mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable); + mPopupView.setBackground(mBelowAnchorBackgroundDrawable); } } else { mPopupView.refreshDrawableState(); @@ -1114,36 +1124,43 @@ public class PopupWindow { } return mAnimationStyle; } - + /** - * <p>Positions the popup window on screen. When the popup window is too - * tall to fit under the anchor, a parent scroll view is seeked and scrolled - * up to reclaim space. If scrolling is not possible or not enough, the - * popup window gets moved on top of the anchor.</p> - * - * <p>The height must have been set on the layout parameters prior to - * calling this method.</p> - * + * Positions the popup window on screen. When the popup window is too tall + * to fit under the anchor, a parent scroll view is seeked and scrolled up + * to reclaim space. If scrolling is not possible or not enough, the popup + * window gets moved on top of the anchor. + * <p> + * The height must have been set on the layout parameters prior to calling + * this method. + * * @param anchor the view on which the popup window must be anchored * @param p the layout parameters used to display the drop down - * + * @param xoff horizontal offset used to adjust for background padding + * @param yoff vertical offset used to adjust for background padding + * @param gravity horizontal gravity specifying popup alignment * @return true if the popup is translated upwards to fit on screen */ - private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, - int xoff, int yoff, int gravity) { - + private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, + int yoff, int gravity) { final int anchorHeight = anchor.getHeight(); + final int anchorWidth = anchor.getWidth(); + if (mOverlapAnchor) { + yoff -= anchorHeight; + } + anchor.getLocationInWindow(mDrawingLocation); p.x = mDrawingLocation[0] + xoff; p.y = mDrawingLocation[1] + anchorHeight + yoff; - final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) & - Gravity.HORIZONTAL_GRAVITY_MASK; + final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) + & Gravity.HORIZONTAL_GRAVITY_MASK; if (hgrav == Gravity.RIGHT) { - // Flip the location to align the right sides of the popup and anchor instead of left - p.x -= mPopupWidth - anchor.getWidth(); + // Flip the location to align the right sides of the popup and + // anchor instead of left. + p.x -= mPopupWidth - anchorWidth; } - + boolean onTop = false; p.gravity = Gravity.LEFT | Gravity.TOP; @@ -1152,60 +1169,58 @@ public class PopupWindow { final Rect displayFrame = new Rect(); anchor.getWindowVisibleDisplayFrame(displayFrame); - int screenY = mScreenLocation[1] + anchorHeight + yoff; - + final int screenY = mScreenLocation[1] + anchorHeight + yoff; final View root = anchor.getRootView(); - if (screenY + mPopupHeight > displayFrame.bottom || - p.x + mPopupWidth - root.getWidth() > 0) { - // if the drop down disappears at the bottom of the screen. we try to - // scroll a parent scrollview or move the drop down back up on top of - // the edit box + if (screenY + mPopupHeight > displayFrame.bottom + || p.x + mPopupWidth - root.getWidth() > 0) { + // If the drop down disappears at the bottom of the screen, we try + // to scroll a parent scrollview or move the drop down back up on + // top of the edit box. if (mAllowScrollingAnchorParent) { - int scrollX = anchor.getScrollX(); - int scrollY = anchor.getScrollY(); - Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff, - scrollY + mPopupHeight + anchor.getHeight() + yoff); + final int scrollX = anchor.getScrollX(); + final int scrollY = anchor.getScrollY(); + final Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff, + scrollY + mPopupHeight + anchorHeight + yoff); anchor.requestRectangleOnScreen(r, true); } - // now we re-evaluate the space available, and decide from that + // Now we re-evaluate the space available, and decide from that // whether the pop-up will go above or below the anchor. anchor.getLocationInWindow(mDrawingLocation); p.x = mDrawingLocation[0] + xoff; - p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; + p.y = mDrawingLocation[1] + anchorHeight + yoff; - // Preserve the gravity adjustment + // Preserve the gravity adjustment. if (hgrav == Gravity.RIGHT) { - p.x -= mPopupWidth - anchor.getWidth(); + p.x -= mPopupWidth - anchorWidth; } - - // determine whether there is more space above or below the anchor + + // Determine whether there is more space above or below the anchor. anchor.getLocationOnScreen(mScreenLocation); - - onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) < + onTop = (displayFrame.bottom - mScreenLocation[1] - anchorHeight - yoff) < (mScreenLocation[1] - yoff - displayFrame.top); if (onTop) { p.gravity = Gravity.LEFT | Gravity.BOTTOM; p.y = root.getHeight() - mDrawingLocation[1] + yoff; } else { - p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; + p.y = mDrawingLocation[1] + anchorHeight + yoff; } } if (mClipToScreen) { final int displayFrameWidth = displayFrame.right - displayFrame.left; - - int right = p.x + p.width; + final int right = p.x + p.width; if (right > displayFrameWidth) { p.x -= right - displayFrameWidth; } + if (p.x < displayFrame.left) { p.x = displayFrame.left; p.width = Math.min(p.width, displayFrameWidth); } if (onTop) { - int popupTop = mScreenLocation[1] + yoff - mPopupHeight; + final int popupTop = mScreenLocation[1] + yoff - mPopupHeight; if (popupTop < 0) { p.y += popupTop; } @@ -1215,7 +1230,11 @@ public class PopupWindow { } p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; - + + // Compute the position of the anchor relative to the popup. + mAnchorRelativeX = mDrawingLocation[0] - p.x + anchorHeight / 2; + mAnchorRelativeY = mDrawingLocation[1] - p.y + anchorWidth / 2; + return onTop; } @@ -1503,7 +1522,8 @@ public class PopupWindow { } WeakReference<View> oldAnchor = mAnchor; - final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff); + final boolean needsUpdate = updateLocation + && (mAnchorXoff != xoff || mAnchorYoff != yoff); if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) { registerForScrollChanged(anchor, xoff, yoff, gravity); } else if (needsUpdate) { diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 7e8f6b4..0fa75a6 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -583,7 +583,8 @@ public class ScrollView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent ev) { initVelocityTrackerIfNotExists(); - mVelocityTracker.addMovement(ev); + + MotionEvent vtev = MotionEvent.obtain(ev); final int action = ev.getAction(); @@ -627,7 +628,8 @@ public class ScrollView extends FrameLayout { final int y = (int) ev.getY(activePointerIndex); int deltaY = mLastMotionY - y; if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { - deltaY -= mScrollConsumed[1] + mScrollOffset[1]; + deltaY -= mScrollConsumed[1]; + vtev.offsetLocation(0, mScrollOffset[1]); } if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { final ViewParent parent = getParent(); @@ -643,7 +645,7 @@ public class ScrollView extends FrameLayout { } if (mIsBeingDragged) { // Scroll to follow the motion event - mLastMotionY = y; + mLastMotionY = y - mScrollOffset[1]; final int oldY = mScrollY; final int range = getScrollRange(); @@ -663,6 +665,7 @@ public class ScrollView extends FrameLayout { final int unconsumedY = deltaY - scrolledDeltaY; if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) { mLastMotionY -= mScrollOffset[1]; + vtev.offsetLocation(0, mScrollOffset[1]); } else if (canOverscroll) { final int pulledToY = oldY + deltaY; if (pulledToY < 0) { @@ -720,6 +723,11 @@ public class ScrollView extends FrameLayout { mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); break; } + + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(vtev); + } + vtev.recycle(); return true; } @@ -1565,10 +1573,10 @@ public class ScrollView extends FrameLayout { } private void flingWithNestedDispatch(int velocityY) { - if (mScrollY == 0 && velocityY < 0 || - mScrollY == getScrollRange() && velocityY > 0) { - dispatchNestedFling(0, velocityY); - } else { + final boolean canFling = (mScrollY > 0 || velocityY > 0) && + (mScrollY < getScrollRange() || velocityY < 0); + dispatchNestedFling(0, velocityY, canFling); + if (canFling) { fling(velocityY); } } @@ -1627,6 +1635,12 @@ public class ScrollView extends FrameLayout { return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0; } + @Override + public void onNestedScrollAccepted(View child, View target, int axes) { + super.onNestedScrollAccepted(child, target, axes); + startNestedScroll(SCROLL_AXIS_VERTICAL); + } + /** * @inheritDoc */ @@ -1638,16 +1652,23 @@ public class ScrollView extends FrameLayout { @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + final int oldScrollY = mScrollY; scrollBy(0, dyUnconsumed); + final int myConsumed = mScrollY - oldScrollY; + final int myUnconsumed = dyUnconsumed - myConsumed; + dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null); } /** * @inheritDoc */ @Override - public boolean onNestedFling(View target, float velocityX, float velocityY) { - flingWithNestedDispatch((int) velocityY); - return true; + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + if (!consumed) { + flingWithNestedDispatch((int) velocityY); + return true; + } + return false; } @Override |
