diff options
Diffstat (limited to 'core/java/android')
54 files changed, 2075 insertions, 443 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 2b35cd4..51ececc 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2010,27 +2010,47 @@ public class ActivityManager { public int lastTrimLevel; /** - * Constant for {@link #importance}: this process is running the - * foreground UI. + * Constant for {@link #importance}: This process is running the + * foreground UI; that is, it is the thing currently at the top of the screen + * that the user is interacting with. */ public static final int IMPORTANCE_FOREGROUND = 100; /** - * Constant for {@link #importance}: this process is running something + * Constant for {@link #importance}: This process is running a foreground + * service, for example to perform music playback even while the user is + * not immediately in the app. This generally indicates that the process + * is doing something the user actively cares about. + */ + public static final int IMPORTANCE_FOREGROUND_SERVICE = 125; + + /** + * Constant for {@link #importance}: This process is running the foreground + * UI, but the device is asleep so it is not visible to the user. This means + * the user is not really aware of the process, because they can not see or + * interact with it, but it is quite important because it what they expect to + * return to once unlocking the device. + */ + public static final int IMPORTANCE_TOP_SLEEPING = 150; + + /** + * Constant for {@link #importance}: This process is running something * that is actively visible to the user, though not in the immediate - * foreground. + * foreground. This may be running a window that is behind the current + * foreground (so paused and with its state saved, not interacting with + * the user, but visible to them to some degree); it may also be running + * other services under the system's control that it inconsiders important. */ public static final int IMPORTANCE_VISIBLE = 200; /** - * Constant for {@link #importance}: this process is running something - * that is considered to be actively perceptible to the user. An - * example would be an application performing background music playback. + * Constant for {@link #importance}: This process is not something the user + * is directly aware of, but is otherwise perceptable to them to some degree. */ public static final int IMPORTANCE_PERCEPTIBLE = 130; /** - * Constant for {@link #importance}: this process is running an + * Constant for {@link #importance}: This process is running an * application that can not save its state, and thus can't be killed * while in the background. * @hide @@ -2038,42 +2058,51 @@ public class ActivityManager { public static final int IMPORTANCE_CANT_SAVE_STATE = 170; /** - * Constant for {@link #importance}: this process is contains services - * that should remain running. + * Constant for {@link #importance}: This process is contains services + * that should remain running. These are background services apps have + * started, not something the user is aware of, so they may be killed by + * the system relatively freely (though it is generally desired that they + * stay running as long as they want to). */ public static final int IMPORTANCE_SERVICE = 300; /** - * Constant for {@link #importance}: this process process contains + * Constant for {@link #importance}: This process process contains * background code that is expendable. */ public static final int IMPORTANCE_BACKGROUND = 400; /** - * Constant for {@link #importance}: this process is empty of any + * Constant for {@link #importance}: This process is empty of any * actively running code. */ public static final int IMPORTANCE_EMPTY = 500; /** - * Constant for {@link #importance}: this process does not exist. + * Constant for {@link #importance}: This process does not exist. */ public static final int IMPORTANCE_GONE = 1000; /** @hide */ public static int procStateToImportance(int procState) { - if (procState >= ActivityManager.PROCESS_STATE_HOME) { - return ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND; - } else if (procState >= ActivityManager.PROCESS_STATE_SERVICE) { - return ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE; - } else if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { - return ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE; - } else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) { - return ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE; - } else if (procState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { - return ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; + if (procState == PROCESS_STATE_NONEXISTENT) { + return IMPORTANCE_GONE; + } else if (procState >= PROCESS_STATE_HOME) { + return IMPORTANCE_BACKGROUND; + } else if (procState >= PROCESS_STATE_SERVICE) { + return IMPORTANCE_SERVICE; + } else if (procState > PROCESS_STATE_HEAVY_WEIGHT) { + return IMPORTANCE_CANT_SAVE_STATE; + } else if (procState >= PROCESS_STATE_IMPORTANT_BACKGROUND) { + return IMPORTANCE_PERCEPTIBLE; + } else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) { + return IMPORTANCE_VISIBLE; + } else if (procState >= PROCESS_STATE_TOP_SLEEPING) { + return IMPORTANCE_TOP_SLEEPING; + } else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) { + return IMPORTANCE_FOREGROUND_SERVICE; } else { - return ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; + return IMPORTANCE_FOREGROUND; } } @@ -2253,6 +2282,22 @@ public class ActivityManager { } /** + * Return the importance of a given package name, based on the processes that are + * currently running. The return value is one of the importance constants defined + * in {@link RunningAppProcessInfo}, giving you the highest importance of all the + * processes that this package has code running inside of. If there are no processes + * running its code, {@link RunningAppProcessInfo#IMPORTANCE_GONE} is returned. + */ + public int getPackageImportance(String packageName) { + try { + int procState = ActivityManagerNative.getDefault().getPackageProcessState(packageName); + return RunningAppProcessInfo.procStateToImportance(procState); + } catch (RemoteException e) { + return RunningAppProcessInfo.IMPORTANCE_GONE; + } + } + + /** * Return global memory state information for the calling process. This * does not fill in all fields of the {@link RunningAppProcessInfo}. The * only fields that will be filled in are diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index f63d13c..256d87d 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -106,9 +106,24 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM } } - static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg) { + static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg, + String tag) { try { - getDefault().noteWakeupAlarm(ps.getTarget(), sourceUid, sourcePkg); + getDefault().noteWakeupAlarm(ps.getTarget(), sourceUid, sourcePkg, tag); + } catch (RemoteException ex) { + } + } + + static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) { + try { + getDefault().noteAlarmStart(ps.getTarget(), sourceUid, tag); + } catch (RemoteException ex) { + } + } + + static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) { + try { + getDefault().noteAlarmFinish(ps.getTarget(), sourceUid, tag); } catch (RemoteException ex) { } } @@ -1375,7 +1390,30 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.readStrongBinder()); int sourceUid = data.readInt(); String sourcePkg = data.readString(); - noteWakeupAlarm(is, sourceUid, sourcePkg); + String tag = data.readString(); + noteWakeupAlarm(is, sourceUid, sourcePkg, tag); + reply.writeNoException(); + return true; + } + + case NOTE_ALARM_START_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IIntentSender is = IIntentSender.Stub.asInterface( + data.readStrongBinder()); + int sourceUid = data.readInt(); + String tag = data.readString(); + noteAlarmStart(is, sourceUid, tag); + reply.writeNoException(); + return true; + } + + case NOTE_ALARM_FINISH_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IIntentSender is = IIntentSender.Stub.asInterface( + data.readStrongBinder()); + int sourceUid = data.readInt(); + String tag = data.readString(); + noteAlarmFinish(is, sourceUid, tag); reply.writeNoException(); return true; } @@ -2461,6 +2499,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case GET_PACKAGE_PROCESS_STATE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String pkg = data.readString(); + int res = getPackageProcessState(pkg); + reply.writeNoException(); + reply.writeInt(res); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -3304,7 +3351,7 @@ class ActivityManagerProxy implements IActivityManager data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(stackBoxId); r.writeToParcel(data, 0); - mRemote.transact(RESIZE_STACK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + mRemote.transact(RESIZE_STACK_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -3360,7 +3407,7 @@ class ActivityManagerProxy implements IActivityManager Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(stackId); - mRemote.transact(SET_FOCUSED_STACK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + mRemote.transact(SET_FOCUSED_STACK_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -3384,7 +3431,7 @@ class ActivityManagerProxy implements IActivityManager Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(listener.asBinder()); - mRemote.transact(REGISTER_TASK_STACK_LISTENER_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + mRemote.transact(REGISTER_TASK_STACK_LISTENER_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -4214,16 +4261,37 @@ class ActivityManagerProxy implements IActivityManager mRemote.transact(ENTER_SAFE_MODE_TRANSACTION, data, null, 0); data.recycle(); } - public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg) + public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(sender.asBinder()); data.writeInt(sourceUid); data.writeString(sourcePkg); + data.writeString(tag); mRemote.transact(NOTE_WAKEUP_ALARM_TRANSACTION, data, null, 0); data.recycle(); } + public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(sender.asBinder()); + data.writeInt(sourceUid); + data.writeString(tag); + mRemote.transact(NOTE_ALARM_START_TRANSACTION, data, null, 0); + data.recycle(); + } + public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(sender.asBinder()); + data.writeInt(sourceUid); + data.writeString(tag); + mRemote.transact(NOTE_ALARM_FINISH_TRANSACTION, data, null, 0); + data.recycle(); + } public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -5486,7 +5554,7 @@ class ActivityManagerProxy implements IActivityManager data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); values.writeToParcel(data, 0); - mRemote.transact(SET_TASK_DESCRIPTION_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + mRemote.transact(SET_TASK_DESCRIPTION_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -5499,7 +5567,7 @@ class ActivityManagerProxy implements IActivityManager data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(taskId); data.writeInt(resizeable ? 1 : 0); - mRemote.transact(SET_TASK_RESIZEABLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + mRemote.transact(SET_TASK_RESIZEABLE_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -5513,7 +5581,7 @@ class ActivityManagerProxy implements IActivityManager data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(taskId); r.writeToParcel(data, 0); - mRemote.transact(RESIZE_TASK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + mRemote.transact(RESIZE_TASK_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -5545,7 +5613,7 @@ class ActivityManagerProxy implements IActivityManager data.writeInt(1); data.writeBundle(options.toBundle()); } - mRemote.transact(START_IN_PLACE_ANIMATION_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + mRemote.transact(START_IN_PLACE_ANIMATION_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -5586,8 +5654,7 @@ class ActivityManagerProxy implements IActivityManager Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); - mRemote.transact(BACKGROUND_RESOURCES_RELEASED_TRANSACTION, data, reply, - IBinder.FLAG_ONEWAY); + mRemote.transact(BACKGROUND_RESOURCES_RELEASED_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -5599,8 +5666,7 @@ class ActivityManagerProxy implements IActivityManager Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); - mRemote.transact(NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION, data, reply, - IBinder.FLAG_ONEWAY); + mRemote.transact(NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -5612,8 +5678,7 @@ class ActivityManagerProxy implements IActivityManager Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); - mRemote.transact(NOTIFY_ENTER_ANIMATION_COMPLETE_TRANSACTION, data, reply, - IBinder.FLAG_ONEWAY); + mRemote.transact(NOTIFY_ENTER_ANIMATION_COMPLETE_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -5703,10 +5768,24 @@ class ActivityManagerProxy implements IActivityManager data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(userId); data.writeStringArray(packages); - mRemote.transact(UPDATE_LOCK_TASK_PACKAGES_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + mRemote.transact(UPDATE_LOCK_TASK_PACKAGES_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override + public int getPackageProcessState(String packageName) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + mRemote.transact(GET_PACKAGE_PROCESS_STATE_TRANSACTION, data, reply, 0); reply.readException(); + int res = reply.readInt(); data.recycle(); reply.recycle(); + return res; } private IBinder mRemote; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 9ddfd88..fe6e4f3 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -17,6 +17,8 @@ package android.app; import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.XmlRes; import android.content.ComponentName; @@ -733,13 +735,16 @@ final class ApplicationPackageManager extends PackageManager { } } - @Override public Drawable getDrawable(String packageName, @DrawableRes int resid, - ApplicationInfo appInfo) { - ResourceName name = new ResourceName(packageName, resid); - Drawable dr = getCachedIcon(name); - if (dr != null) { - return dr; + @Nullable + @Override + public Drawable getDrawable(String packageName, @DrawableRes int resId, + @Nullable ApplicationInfo appInfo) { + final ResourceName name = new ResourceName(packageName, resId); + final Drawable cachedIcon = getCachedIcon(name); + if (cachedIcon != null) { + return cachedIcon; } + if (appInfo == null) { try { appInfo = getApplicationInfo(packageName, sDefaultFlags); @@ -747,36 +752,46 @@ final class ApplicationPackageManager extends PackageManager { return null; } } - try { - Resources r = getResourcesForApplication(appInfo); - dr = r.getDrawable(resid); - if (false) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Log.w(TAG, "Getting drawable 0x" + Integer.toHexString(resid) - + " from package " + packageName - + ": app scale=" + r.getCompatibilityInfo().applicationScale - + ", caller scale=" + mContext.getResources().getCompatibilityInfo().applicationScale, - e); + + if (resId != 0) { + try { + final Resources r = getResourcesForApplication(appInfo); + final Drawable dr = r.getDrawable(resId, null); + if (dr != null) { + putCachedIcon(name, dr); + } + + if (false) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "Getting drawable 0x" + Integer.toHexString(resId) + + " from package " + packageName + + ": app scale=" + r.getCompatibilityInfo().applicationScale + + ", caller scale=" + mContext.getResources() + .getCompatibilityInfo().applicationScale, + e); + } + if (DEBUG_ICONS) { + Log.v(TAG, "Getting drawable 0x" + + Integer.toHexString(resId) + " from " + r + + ": " + dr); + } + return dr; + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for " + + appInfo.packageName); + } catch (Resources.NotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for " + + appInfo.packageName + ": " + e.getMessage()); + } catch (Exception e) { + // If an exception was thrown, fall through to return + // default icon. + Log.w("PackageManager", "Failure retrieving icon 0x" + + Integer.toHexString(resId) + " in package " + + packageName, e); } - if (DEBUG_ICONS) Log.v(TAG, "Getting drawable 0x" - + Integer.toHexString(resid) + " from " + r - + ": " + dr); - putCachedIcon(name, dr); - return dr; - } catch (NameNotFoundException e) { - Log.w("PackageManager", "Failure retrieving resources for " - + appInfo.packageName); - } catch (Resources.NotFoundException e) { - Log.w("PackageManager", "Failure retrieving resources for " - + appInfo.packageName + ": " + e.getMessage()); - } catch (RuntimeException e) { - // If an exception was thrown, fall through to return - // default icon. - Log.w("PackageManager", "Failure retrieving icon 0x" - + Integer.toHexString(resid) + " in package " - + packageName, e); } + return null; } @@ -923,19 +938,21 @@ final class ApplicationPackageManager extends PackageManager { return label; } - @Override public Resources getResourcesForActivity( - ComponentName activityName) throws NameNotFoundException { + @Override + public Resources getResourcesForActivity(ComponentName activityName) + throws NameNotFoundException { return getResourcesForApplication( getActivityInfo(activityName, sDefaultFlags).applicationInfo); } - @Override public Resources getResourcesForApplication( - ApplicationInfo app) throws NameNotFoundException { + @Override + public Resources getResourcesForApplication(@NonNull ApplicationInfo app) + throws NameNotFoundException { if (app.packageName.equals("system")) { return mContext.mMainThread.getSystemContext().getResources(); } final boolean sameUid = (app.uid == Process.myUid()); - Resources r = mContext.mMainThread.getTopLevelResources( + final Resources r = mContext.mMainThread.getTopLevelResources( sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, @@ -946,8 +963,9 @@ final class ApplicationPackageManager extends PackageManager { throw new NameNotFoundException("Unable to open " + app.publicSourceDir); } - @Override public Resources getResourcesForApplication( - String appPackageName) throws NameNotFoundException { + @Override + public Resources getResourcesForApplication(String appPackageName) + throws NameNotFoundException { return getResourcesForApplication( getApplicationInfo(appPackageName, sDefaultFlags)); } @@ -999,13 +1017,14 @@ final class ApplicationPackageManager extends PackageManager { mPM = pm; } - private Drawable getCachedIcon(ResourceName name) { + @Nullable + private Drawable getCachedIcon(@NonNull ResourceName name) { synchronized (sSync) { - WeakReference<Drawable.ConstantState> wr = sIconCache.get(name); + final WeakReference<Drawable.ConstantState> wr = sIconCache.get(name); if (DEBUG_ICONS) Log.v(TAG, "Get cached weak drawable ref for " + name + ": " + wr); if (wr != null) { // we have the activity - Drawable.ConstantState state = wr.get(); + final Drawable.ConstantState state = wr.get(); if (state != null) { if (DEBUG_ICONS) { Log.v(TAG, "Get cached drawable state for " + name + ": " + state); @@ -1025,9 +1044,9 @@ final class ApplicationPackageManager extends PackageManager { return null; } - private void putCachedIcon(ResourceName name, Drawable dr) { + private void putCachedIcon(@NonNull ResourceName name, @NonNull Drawable dr) { synchronized (sSync) { - sIconCache.put(name, new WeakReference<Drawable.ConstantState>(dr.getConstantState())); + sIconCache.put(name, new WeakReference<>(dr.getConstantState())); if (DEBUG_ICONS) Log.v(TAG, "Added cached drawable state for " + name + ": " + dr); } } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 4a1d6ff..59de281 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -262,7 +262,11 @@ public interface IActivityManager extends IInterface { public void enterSafeMode() throws RemoteException; - public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg) + public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) + throws RemoteException; + public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) + throws RemoteException; + public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) throws RemoteException; public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException; @@ -490,6 +494,8 @@ public interface IActivityManager extends IInterface { throws RemoteException; public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException; + public int getPackageProcessState(String packageName) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -825,4 +831,7 @@ public interface IActivityManager extends IInterface { int DUMP_HEAP_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+288; int SET_VOICE_KEEP_AWAKE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+289; int UPDATE_LOCK_TASK_PACKAGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+290; + int NOTE_ALARM_START_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+291; + int NOTE_ALARM_FINISH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+292; + int GET_PACKAGE_PROCESS_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+293; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index a0a6c4c..44760ce 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4210,4 +4210,20 @@ public class DevicePolicyManager { return false; } } + + /** + * Called by device owner to set the enabled state of the status bar. Disabling the status + * bar blocks notifications, quick settings and other screen overlays that allow escaping from + * a single use device. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param enabled New state of the status bar. + */ + public void setStatusBarEnabledState(ComponentName admin, boolean enabled) { + try { + mService.setStatusBarEnabledState(admin, enabled); + } catch (RemoteException re) { + Log.w(TAG, "Failed talking with device policy service", re); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 131b99c..7502e1d 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -223,4 +223,5 @@ interface IDevicePolicyManager { PersistableBundle getOtaPolicy(); boolean setKeyguardEnabledState(in ComponentName admin, boolean enabled); + void setStatusBarEnabledState(in ComponentName who, boolean enabled); } diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java index 547a2c3..8aeb22d 100644 --- a/core/java/android/content/ComponentName.java +++ b/core/java/android/content/ComponentName.java @@ -18,6 +18,7 @@ package android.content; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.io.PrintWriter; import java.lang.Comparable; @@ -37,6 +38,56 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co private final String mClass; /** + * Create a new component identifier where the class name may be specified + * as either absolute or relative to the containing package. + * + * <p>Relative package names begin with a <code>'.'</code> character. For a package + * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method + * will return a ComponentName with the package <code>"com.example"</code>and class name + * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also + * permitted.</p> + * + * @param pkg the name of the package the component exists in + * @param cls the name of the class inside of <var>pkg</var> that implements + * the component + * @return the new ComponentName + */ + public static ComponentName createRelative(String pkg, String cls) { + if (TextUtils.isEmpty(cls)) { + throw new IllegalArgumentException("class name cannot be empty"); + } + + final String fullName; + if (cls.charAt(0) == '.') { + // Relative to the package. Prepend the package name. + fullName = pkg + cls; + } else { + // Fully qualified package name. + fullName = cls; + } + return new ComponentName(pkg, fullName); + } + + /** + * Create a new component identifier where the class name may be specified + * as either absolute or relative to the containing package. + * + * <p>Relative package names begin with a <code>'.'</code> character. For a package + * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method + * will return a ComponentName with the package <code>"com.example"</code>and class name + * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also + * permitted.</p> + * + * @param pkg a Context for the package implementing the component + * @param cls the name of the class inside of <var>pkg</var> that implements + * the component + * @return the new ComponentName + */ + public static ComponentName createRelative(Context pkg, String cls) { + return createRelative(pkg.getPackageName(), cls); + } + + /** * Create a new component identifier. * * @param pkg The name of the package that the component exists in. Can diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 763a017..7464cab 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -4655,7 +4655,7 @@ public class PackageParser { private static boolean copyNeeded(int flags, Package p, PackageUserState state, Bundle metaData, int userId) { - if (userId != 0) { + if (userId != UserHandle.USER_OWNER) { // We always need to copy for other users, since we need // to fix up the uid. return true; @@ -4737,11 +4737,9 @@ public class PackageParser { // Make shallow copy so we can store the metadata/libraries safely ApplicationInfo ai = new ApplicationInfo(p.applicationInfo); - if (userId != 0) { - ai.uid = UserHandle.getUid(userId, ai.uid); - ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId) - .getAbsolutePath(); - } + ai.uid = UserHandle.getUid(userId, ai.uid); + ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId) + .getAbsolutePath(); if ((flags & PackageManager.GET_META_DATA) != 0) { ai.metaData = p.mAppMetaData; } @@ -4766,11 +4764,9 @@ public class PackageParser { // This is only used to return the ResolverActivity; we will just always // make a copy. ai = new ApplicationInfo(ai); - if (userId != 0) { - ai.uid = UserHandle.getUid(userId, ai.uid); - ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId) - .getAbsolutePath(); - } + ai.uid = UserHandle.getUid(userId, ai.uid); + ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId) + .getAbsolutePath(); if (state.stopped) { ai.flags |= ApplicationInfo.FLAG_STOPPED; } else { diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 49f6513..d88594d 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -1803,8 +1803,6 @@ public class Camera { public Point mouth = null; } - // Error codes match the enum in include/ui/Camera.h - /** * Unspecified camera error. * @see Camera.ErrorCallback @@ -1812,6 +1810,12 @@ public class Camera { public static final int CAMERA_ERROR_UNKNOWN = 1; /** + * Camera was disconnected due to use by higher priority user. + * @see Camera.ErrorCallback + */ + public static final int CAMERA_ERROR_EVICTED = 2; + + /** * Media server died. In this case, the application must release the * Camera object and instantiate a new one. * @see Camera.ErrorCallback diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index ce83028..31e6e25 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -17,21 +17,32 @@ package android.hardware.camera2; import android.os.Handler; +import android.view.Surface; + import java.util.List; + /** - * A configured capture session for a {@link CameraDevice}, used for capturing - * images from the camera. + * A configured capture session for a {@link CameraDevice}, used for capturing images from the + * camera or reprocessing images captured from the camera in the same session previously. * * <p>A CameraCaptureSession is created by providing a set of target output surfaces to - * {@link CameraDevice#createCaptureSession createCaptureSession}. Once created, the session is - * active until a new session is created by the camera device, or the camera device is closed.</p> + * {@link CameraDevice#createCaptureSession createCaptureSession}, or by providing an + * {@link android.hardware.camera2.params.InputConfiguration} and a set of target output surfaces to + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} for a + * reprocessible capture session. Once created, the session is active until a new session is + * created by the camera device, or the camera device is closed.</p> + * + * <p>All capture sessions can be used for capturing images from the camera but only reprocessible + * capture sessions can reprocess images captured from the camera in the same session previously. + * </p> * * <p>Creating a session is an expensive operation and can take several hundred milliseconds, since * it requires configuring the camera device's internal pipelines and allocating memory buffers for * sending images to the desired targets. Therefore the setup is done asynchronously, and - * {@link CameraDevice#createCaptureSession createCaptureSession} will send the ready-to-use - * CameraCaptureSession to the provided listener's + * {@link CameraDevice#createCaptureSession createCaptureSession} and + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} will + * send the ready-to-use CameraCaptureSession to the provided listener's * {@link CameraCaptureSession.StateCallback#onConfigured onConfigured} callback. If configuration * cannot be completed, then the * {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is called, and the @@ -59,6 +70,61 @@ public abstract class CameraCaptureSession implements AutoCloseable { public abstract CameraDevice getDevice(); /** + * <p>Pre-allocate all buffers for an output Surface.</p> + * + * <p>Normally, the image buffers for a given output Surface are allocated on-demand, + * to minimize startup latency and memory overhead.</p> + * + * <p>However, in some cases, it may be desirable for the buffers to be allocated before + * any requests targeting the Surface are actually submitted to the device. Large buffers + * may take some time to allocate, which can result in delays in submitting requests until + * sufficient buffers are allocated to reach steady-state behavior. Such delays can cause + * bursts to take longer than desired, or cause skips or stutters in preview output.</p> + * + * <p>The prepare() method can be used to perform this preallocation. It may only be called for + * a given output Surface before that Surface is used as a target for a request. The number of + * buffers allocated is the sum of the count needed by the consumer providing the output + * Surface, and the maximum number needed by the camera device to fill its pipeline. Since this + * may be a larger number than what is actually required for steady-state operation, using + * prepare may result in higher memory consumption than the normal on-demand behavior results + * in. Prepare() will also delay the time to first output to a given Surface, in exchange for + * smoother frame rate once the allocation is complete.</p> + * + * <p>For example, an application that creates an + * {@link android.media.ImageReader#newInstance ImageReader} with a maxImages argument of 10, + * but only uses 3 simultaneous Images at once would normally only cause those 3 images to be + * allocated (plus what is needed by the camera device for smooth operation). But using + * prepare() on the ImageReader Surface will result in all 10 Images being allocated. So + * applications using this method should take care to request only the number of buffers + * actually necessary for their application.</p> + * + * <p>If the same output Surface is used in consecutive sessions (without closing the first + * session explicitly), then its already-allocated buffers are carried over, and if it was + * used as a target of a capture request in the first session, prepare cannot be called on it + * in the second session.</p> + * + * <p>Once allocation is complete, {@link StateCallback#onSurfacePrepared} will be invoked with + * the Surface provided to this method. Between the prepare call and the onSurfacePrepared call, + * the Surface provided to prepare must not be used as a target of a CaptureRequest submitted + * to this session.</p> + * + * @param surface the output Surface for which buffers should be pre-allocated. Must be one of + * the output Surfaces used to create this session. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if this session is no longer active, either because the session + * was explicitly closed, a new session has been created + * or the camera device has been closed. + * @throws IllegalArgumentException if the Surface is invalid, not part of this Session, or has + * already been used as a target of a CaptureRequest in this + * session or immediately prior sessions. + * + * @see StateCallback#onSurfacePrepared + */ + public abstract void prepare(Surface surface) throws CameraAccessException; + + /** * <p>Submit a request for an image to be captured by the camera device.</p> * * <p>The request defines all the parameters for capturing the single image, @@ -77,6 +143,12 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link #setRepeatingBurst}, and will be processed as soon as the current * repeat/repeatBurst processing completes.</p> * + * <p>All capture sessions can be used for capturing images from the camera but only capture + * sessions created by + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} + * can submit reprocess capture requests. Submitting a reprocess request to a regular capture + * session will result in an {@link IllegalArgumentException}.</p> + * * @param request the settings for this capture * @param listener The callback object to notify once this request has been * processed. If null, no metadata will be produced for this capture, @@ -94,7 +166,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * was explicitly closed, a new session has been created * or the camera device has been closed. * @throws IllegalArgumentException if the request targets no Surfaces or Surfaces that are not - * configured as outputs for this session. Or if the handler is + * configured as outputs for this session; or a reprocess + * capture request is submitted in a non-reprocessible capture + * session; or the capture targets a Surface in the middle + * of being {@link #prepare prepared}; or the handler is * null, the listener is not null, and the calling thread has * no looper. * @@ -102,6 +177,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures + * @see CameraDevice#createReprocessibleCaptureSession */ public abstract int capture(CaptureRequest request, CaptureCallback listener, Handler handler) throws CameraAccessException; @@ -121,6 +197,13 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link #capture} repeatedly is that this method guarantees that no * other requests will be interspersed with the burst.</p> * + * <p>All capture sessions can be used for capturing images from the camera but only capture + * sessions created by + * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} + * can submit reprocess capture requests. The list of requests must all be capturing images from + * the camera or all be reprocess capture requests. Submitting a reprocess request to a regular + * capture session will result in an {@link IllegalArgumentException}.</p> + * * @param requests the list of settings for this burst capture * @param listener The callback object to notify each time one of the * requests in the burst has been processed. If null, no metadata will be @@ -138,9 +221,15 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException If the requests target no Surfaces or Surfaces not currently - * configured as outputs. Or if the handler is null, the - * listener is not null, and the calling thread has no looper. + * @throws IllegalArgumentException If the requests target no Surfaces, or the requests target + * Surfaces not currently configured as outputs; or a reprocess + * capture request is submitted in a non-reprocessible capture + * session; or the list of requests contains both requests to + * capture images from the camera and reprocess capture + * requests; or one of the captures targets a Surface in the + * middle of being {@link #prepare prepared}; or if the handler + * is null, the listener is not null, and the calling thread + * has no looper. * * @see #capture * @see #setRepeatingRequest @@ -175,6 +264,14 @@ public abstract class CameraCaptureSession implements AutoCloseable { * in-progress burst will be completed before the new repeat request will be * used.</p> * + * <p>This method does not support reprocess capture requests because each reprocess + * {@link CaptureRequest} must be created from the {@link TotalCaptureResult} that matches + * the input image to be reprocessed. This is either the {@link TotalCaptureResult} of capture + * that is sent for reprocessing, or one of the {@link TotalCaptureResult TotalCaptureResults} + * of a set of captures, when data from the whole set is combined by the application into a + * single reprocess input image. The request must be capturing images from the camera. If a + * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> + * * @param request the request to repeat indefinitely * @param listener The callback object to notify every time the * request finishes processing. If null, no metadata will be @@ -192,10 +289,12 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException If the requests reference no Surfaces or Surfaces that are - * not currently configured as outputs. Or if the handler is - * null, the listener is not null, and the calling thread has - * no looper. Or if no requests were passed in. + * @throws IllegalArgumentException If the request references no Surfaces or references Surfaces + * that are not currently configured as outputs; or the request + * is a reprocess capture request; or the capture targets a + * Surface in the middle of being {@link #prepare prepared}; or + * the handler is null, the listener is not null, and the + * calling thread has no looper; or no requests were passed in. * * @see #capture * @see #captureBurst @@ -235,6 +334,14 @@ public abstract class CameraCaptureSession implements AutoCloseable { * in-progress burst will be completed before the new repeat burst will be * used.</p> * + * <p>This method does not support reprocess capture requests because each reprocess + * {@link CaptureRequest} must be created from the {@link TotalCaptureResult} that matches + * the input image to be reprocessed. This is either the {@link TotalCaptureResult} of capture + * that is sent for reprocessing, or one of the {@link TotalCaptureResult TotalCaptureResults} + * of a set of captures, when data from the whole set is combined by the application into a + * single reprocess input image. The request must be capturing images from the camera. If a + * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> + * * @param requests the list of requests to cycle through indefinitely * @param listener The callback object to notify each time one of the * requests in the repeating bursts has finished processing. If null, no @@ -252,10 +359,13 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalStateException if this session is no longer active, either because the session * was explicitly closed, a new session has been created * or the camera device has been closed. - * @throws IllegalArgumentException If the requests reference no Surfaces or Surfaces not - * currently configured as outputs. Or if the handler is null, - * the listener is not null, and the calling thread has no - * looper. Or if no requests were passed in. + * @throws IllegalArgumentException If the requests reference no Surfaces or reference Surfaces + * not currently configured as outputs; or one of the requests + * is a reprocess capture request; or one of the captures + * targets a Surface in the middle of being + * {@link #prepare prepared}; or the handler is null, the + * listener is not null, and the calling thread has no looper; + * or no requests were passed in. * * @see #capture * @see #captureBurst @@ -298,9 +408,10 @@ public abstract class CameraCaptureSession implements AutoCloseable { * request or a repeating burst is set, it will be cleared.</p> * * <p>This method is the fastest way to switch the camera device to a new session with - * {@link CameraDevice#createCaptureSession}, at the cost of discarding in-progress work. It - * must be called before the new session is created. Once all pending requests are either - * completed or thrown away, the {@link StateCallback#onReady} callback will be called, + * {@link CameraDevice#createCaptureSession} or + * {@link CameraDevice#createReprocessibleCaptureSession}, at the cost of discarding in-progress + * work. It must be called before the new session is created. Once all pending requests are + * either completed or thrown away, the {@link StateCallback#onReady} callback will be called, * if the session has not been closed. Otherwise, the {@link StateCallback#onClosed} * callback will be fired when a new session is created by the camera device.</p> * @@ -321,10 +432,39 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingRequest * @see #setRepeatingBurst * @see CameraDevice#createCaptureSession + * @see CameraDevice#createReprocessibleCaptureSession */ public abstract void abortCaptures() throws CameraAccessException; /** + * Return if the application can submit reprocess capture requests with this camera capture + * session. + * + * @return {@code true} if the application can submit reprocess capture requests with this + * camera capture session. {@code false} otherwise. + * + * @see CameraDevice#createReprocessibleCaptureSession + */ + public abstract boolean isReprocessible(); + + /** + * Get the input Surface associated with a reprocessible capture session. + * + * <p>Each reprocessible capture session has an input {@link Surface} where the reprocess + * capture requests get the input images from, rather than the camera device. The application + * can create a {@link android.media.ImageWriter} with this input {@link Surface} and use it to + * provide input images for reprocess capture requests.</p> + * + * @return The {@link Surface} where reprocessing capture requests get the input images from. If + * this is not a reprocess capture session, {@code null} will be returned. + * + * @see CameraDevice#createReprocessibleCaptureSession + * @see android.media.ImageWriter + * @see android.media.ImageReader + */ + public abstract Surface getInputSurface(); + + /** * Close this capture session asynchronously. * * <p>Closing a session frees up the target output Surfaces of the session for reuse with either @@ -436,6 +576,25 @@ public abstract class CameraCaptureSession implements AutoCloseable { public void onClosed(CameraCaptureSession session) { // default empty implementation } + + /** + * This method is called when the buffer pre-allocation for an output Surface is complete. + * + * <p>Buffer pre-allocation for an output Surface is started by the {@link #prepare} call. + * While allocation is underway, the Surface must not be used as a capture target. + * Once this callback fires, the output Surface provided can again be used as a target for + * a capture request.</p> + * + * <p>In case of a error during pre-allocation (such as running out of suitable memory), + * this callback is still invoked after the error is encountered, though some buffers may + * not have been successfully pre-allocated.</p> + * + * @param session the session returned by {@link CameraDevice#createCaptureSession} + * @param surface the Surface that was used with the {@link #prepare} call. + */ + public void onSurfacePrepared(CameraCaptureSession session, Surface surface) { + // default empty implementation + } } /** diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index fd4cf3c..51b326b 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -16,6 +16,7 @@ package android.hardware.camera2; +import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.OutputConfiguration; import android.os.Handler; @@ -135,7 +136,7 @@ public abstract class CameraDevice implements AutoCloseable { * * <p>The active capture session determines the set of potential output Surfaces for * the camera device for each capture request. A given request may use all - * or a only some of the outputs. Once the CameraCaptureSession is created, requests can be + * or only some of the outputs. Once the CameraCaptureSession is created, requests can be * can be submitted with {@link CameraCaptureSession#capture capture}, * {@link CameraCaptureSession#captureBurst captureBurst}, * {@link CameraCaptureSession#setRepeatingRequest setRepeatingRequest}, or @@ -393,6 +394,75 @@ public abstract class CameraDevice implements AutoCloseable { List<OutputConfiguration> outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException; + /** + * Create a new reprocessible camera capture session by providing the desired reprocessing + * input Surface configuration and the target output set of Surfaces to the camera device. + * + * <p>If a camera device supports YUV reprocessing + * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or OPAQUE + * reprocessing + * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING}), besides + * the capture session created via {@link #createCaptureSession}, the application can also + * create a reprocessible capture session to submit reprocess capture requests in addition to + * regular capture requests. A reprocess capture request takes the next available buffer from + * the session's input Surface, and sends it through the camera device's processing pipeline + * again, to produce buffers for the request's target output Surfaces. No new image data is + * captured for a reprocess request. However the input buffer provided by + * the application must be captured previously by the same camera device in the same session + * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output + * images).</p> + * + * <p>The active reprocessible capture session determines an input {@link Surface} and the set + * of potential output Surfaces for the camera devices for each capture request. The application + * can use {@link #createCaptureRequest} to create regular capture requests to capture new + * images from the camera device, and use {@link #createReprocessCaptureRequest} to create + * reprocess capture requests to process buffers from the input {@link Surface}. A request may + * use all or only some of the outputs. All the output Surfaces in one capture request will come + * from the same source, either from a new capture by the camera device, or from the input + * Surface depending on if the request is a reprocess capture request.</p> + * + * <p>Input formats and sizes supported by the camera device can be queried via + * {@link StreamConfigurationMap#getInputFormats} and + * {@link StreamConfigurationMap#getInputSizes}. For each supported input format, the camera + * device supports a set of output formats and sizes for reprocessing that can be queried via + * {@link StreamConfigurationMap#getValidOutputFormatsForInput} and + * {@link StreamConfigurationMap#getOutputSizes}. While output Surfaces with formats that + * aren't valid reprocess output targets for the input configuration can be part of a session, + * they cannot be used as targets for a reprocessing request.</p> + * + * <p>Since the application cannot access {@link android.graphics.ImageFormat#PRIVATE} images + * directly, an output Surface created by {@link android.media.ImageReader#newOpaqueInstance} + * will be considered as intended to be used for reprocessing input and thus the + * {@link android.media.ImageReader} size must match one of the supported input sizes for + * {@link android.graphics.ImageFormat#PRIVATE} format. Otherwise, creating a reprocessible + * capture session will fail.</p> + * + * @param inputConfig The configuration for the input {@link Surface} + * @param outputs The new set of Surfaces that should be made available as + * targets for captured image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the input configuration is null or not supported, the set + * of output Surfaces do not meet the requirements, the + * callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CameraCaptureSession + * @see StreamConfigurationMap#getInputFormats + * @see StreamConfigurationMap#getInputSizes + * @see StreamConfigurationMap#getValidOutputFormatsForInput + * @see StreamConfigurationMap#getOutputSizes + * @see android.media.ImageWriter + * @see android.media.ImageReader + */ + public abstract void createReprocessibleCaptureSession(InputConfiguration inputConfig, + List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException; /** * <p>Create a {@link CaptureRequest.Builder} for new capture requests, @@ -423,6 +493,36 @@ public abstract class CameraDevice implements AutoCloseable { throws CameraAccessException; /** + * <p>Create a {@link CaptureRequest.Builder} for a new reprocess {@link CaptureRequest} from a + * {@link TotalCaptureResult}. + * + * <p>Each reprocess {@link CaptureRequest} processes one buffer from + * {@link CameraCaptureSession}'s input {@link Surface} to all output {@link Surface Surfaces} + * included in the reprocess capture request. The reprocess input images must be generated from + * one or multiple output images captured from the same camera device. The application can + * provide input images to camera device via + * {{@link android.media.ImageWriter#queueInputImage ImageWriter#queueInputImage}}. + * The application must use the capture result of one of those output images to create a + * reprocess capture request so that the camera device can use the information to achieve + * optimal reprocess image quality. + * + * @param inputResult The capture result of the output image or one of the output images used + * to generate the reprocess input image for this capture request. + * + * @throws IllegalArgumentException if inputResult is null. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CaptureRequest.Builder + * @see TotalCaptureResult + * @see CameraDevice#createReprocessibleCaptureSession + * @see android.media.ImageWriter + */ + public abstract CaptureRequest.Builder createReprocessCaptureRequest( + TotalCaptureResult inputResult) throws CameraAccessException; + + /** * Close the connection to this camera device as quickly as possible. * * <p>Immediately after this call, all calls to the camera device or active session interface diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index b513379..1a00a05 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -148,7 +148,7 @@ public final class CameraManager { * new one provided.</p> * * <p>The first time a callback is registered, it is immediately called - * with the torch mode status of all currently known camera devices.</p> + * with the torch mode status of all currently known camera devices with a flash unit.</p> * * <p>Since this callback will be registered with the camera service, remember to unregister it * once it is no longer needed; otherwise the callback will continue to receive events @@ -524,7 +524,7 @@ public final class CameraManager { * A callback for camera flash torch modes becoming unavailable, disabled, or enabled. * * <p>The torch mode becomes unavailable when the camera device it belongs to becomes - * unavailable or other camera resouces it needs become busy due to other higher priority + * unavailable or other camera resources it needs become busy due to other higher priority * camera activities. The torch mode becomes disabled when it was turned off or when the camera * device it belongs to is no longer in use and other camera resources it needs are no longer * busy. A camera's torch mode is turned off when an application calls {@link #setTorchMode} to diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index b8fb8e7..35727e8 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -157,6 +157,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private final HashSet<Surface> mSurfaceSet; private final CameraMetadataNative mSettings; + private boolean mIsReprocess; private Object mUserTag; @@ -168,6 +169,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest() { mSettings = new CameraMetadataNative(); mSurfaceSet = new HashSet<Surface>(); + mIsReprocess = false; } /** @@ -179,6 +181,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest(CaptureRequest source) { mSettings = new CameraMetadataNative(source.mSettings); mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone(); + mIsReprocess = source.mIsReprocess; mUserTag = source.mUserTag; } @@ -187,9 +190,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * Used by the Builder to create a mutable CaptureRequest. */ - private CaptureRequest(CameraMetadataNative settings) { + private CaptureRequest(CameraMetadataNative settings, boolean isReprocess) { mSettings = CameraMetadataNative.move(settings); mSurfaceSet = new HashSet<Surface>(); + mIsReprocess = isReprocess; } /** @@ -257,10 +261,27 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } /** + * Determine if this is a reprocess capture request. + * + * <p>A reprocess capture request produces output images from an input buffer from the + * {@link CameraCaptureSession}'s input {@link Surface}. A reprocess capture request can be + * created by {@link CameraDevice#createReprocessCaptureRequest}.</p> + * + * @return {@code true} if this is a reprocess capture request. {@code false} if this is not a + * reprocess capture request. + * + * @see CameraDevice#createReprocessCaptureRequest + */ + public boolean isReprocess() { + return mIsReprocess; + } + + /** * Determine whether this CaptureRequest is equal to another CaptureRequest. * * <p>A request is considered equal to another is if it's set of key/values is equal, it's - * list of output surfaces is equal, and the user tag is equal.</p> + * list of output surfaces is equal, the user tag is equal, and the return values of + * isReprocess() are equal.</p> * * @param other Another instance of CaptureRequest. * @@ -276,7 +297,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> return other != null && Objects.equals(mUserTag, other.mUserTag) && mSurfaceSet.equals(other.mSurfaceSet) - && mSettings.equals(other.mSettings); + && mSettings.equals(other.mSettings) + && mIsReprocess == other.mIsReprocess; } @Override @@ -323,6 +345,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> Surface s = (Surface) p; mSurfaceSet.add(s); } + + mIsReprocess = (in.readInt() == 0) ? false : true; } @Override @@ -334,6 +358,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> public void writeToParcel(Parcel dest, int flags) { mSettings.writeToParcel(dest, flags); dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags); + dest.writeInt(mIsReprocess ? 1 : 0); } /** @@ -374,8 +399,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @hide */ - public Builder(CameraMetadataNative template) { - mRequest = new CaptureRequest(template); + public Builder(CameraMetadataNative template, boolean reprocess) { + mRequest = new CaptureRequest(template, reprocess); } /** diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index d8f92e5..4134d28 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -295,11 +295,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Whenever a request has been processed, regardless of failure or success, * it gets a unique frame number assigned to its future result/failure.</p> * - * <p>This value monotonically increments, starting with 0, - * for every new result or failure; and the scope is the lifetime of the - * {@link CameraDevice}.</p> + * <p>For the same type of request (capturing from the camera device or reprocessing), this + * value monotonically increments, starting with 0, for every new result or failure and the + * scope is the lifetime of the {@link CameraDevice}. Between different types of requests, + * the frame number may not monotonically increment. For example, the frame number of a newer + * reprocess result may be smaller than the frame number of an older result of capturing new + * images from the camera device, but the frame number of a newer reprocess result will never be + * smaller than the frame number of an older reprocess result.</p> * * @return The frame number + * + * @see CameraDevice#createCaptureRequest + * @see CameraDevice#createReprocessCaptureRequest */ public long getFrameNumber() { return mFrameNumber; diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl index ca0935c..151c918 100644 --- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl +++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl @@ -31,4 +31,5 @@ interface ICameraDeviceCallbacks oneway void onCaptureStarted(in CaptureResultExtras resultExtras, long timestamp); oneway void onResultReceived(in CameraMetadataNative result, in CaptureResultExtras resultExtras); + oneway void onPrepared(int streamId); } diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl index 01f2396..375b310 100644 --- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl +++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl @@ -16,11 +16,11 @@ package android.hardware.camera2; -import android.hardware.camera2.params.OutputConfiguration; -import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.CaptureRequest; - +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.LongParcelable; +import android.view.Surface; /** @hide */ interface ICameraDeviceUser @@ -68,6 +68,29 @@ interface ICameraDeviceUser // non-negative value is the stream ID. negative value is status_t int createStream(in OutputConfiguration outputConfiguration); + /** + * Create an input stream + * + * <p>Create an input stream of width, height, and format</p> + * + * @param width Width of the input buffers + * @param height Height of the input buffers + * @param format Format of the input buffers. One of HAL_PIXEL_FORMAT_*. + * + * @return stream ID if it's a non-negative value. status_t if it's a negative value. + */ + int createInputStream(int width, int height, int format); + + /** + * Get the surface of the input stream. + * + * <p>It's valid to call this method only after a stream configuration is completed + * successfully and the stream configuration includes a input stream.</p> + * + * @param surface An output argument for the surface of the input stream buffer queue. + */ + int getInputSurface(out Surface surface); + int createDefaultRequest(int templateId, out CameraMetadataNative request); int getCameraInfo(out CameraMetadataNative info); @@ -75,4 +98,6 @@ interface ICameraDeviceUser int waitUntilIdle(); int flush(out LongParcelable lastFrameNumber); + + int prepare(int streamId); } diff --git a/core/java/android/hardware/camera2/impl/CallbackProxies.java b/core/java/android/hardware/camera2/impl/CallbackProxies.java index f0217ac..dac2ef8 100644 --- a/core/java/android/hardware/camera2/impl/CallbackProxies.java +++ b/core/java/android/hardware/camera2/impl/CallbackProxies.java @@ -23,6 +23,7 @@ import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.dispatch.Dispatchable; import android.hardware.camera2.dispatch.MethodNameInvoker; +import android.view.Surface; import static com.android.internal.util.Preconditions.*; @@ -175,6 +176,12 @@ public class CallbackProxies { public void onClosed(CameraCaptureSession session) { mProxy.invoke("onClosed", session); } + + @Override + public void onSurfacePrepared(CameraCaptureSession session, Surface surface) { + mProxy.invoke("onSurfacePrepared", session, surface); + } + } private CallbackProxies() { diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index e87a2f8..c74204d 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -44,6 +44,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { private final int mId; private final String mIdString; + /** Input surface configured by native camera framework based on user-specified configuration */ + private final Surface mInput; /** User-specified set of surfaces used as the configuration outputs */ private final List<Surface> mOutputs; /** @@ -85,7 +87,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { * There must be no pending actions * (e.g. no pending captures, no repeating requests, no flush).</p> */ - CameraCaptureSessionImpl(int id, List<Surface> outputs, + CameraCaptureSessionImpl(int id, Surface input, List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler stateHandler, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, Handler deviceStateHandler, boolean configureSuccess) { @@ -100,6 +102,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { // TODO: extra verification of outputs mOutputs = outputs; + mInput = input; mStateHandler = checkHandler(stateHandler); mStateCallback = createUserStateCallbackProxy(mStateHandler, callback); @@ -141,12 +144,21 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } @Override + public void prepare(Surface surface) throws CameraAccessException { + mDeviceImpl.prepare(surface); + } + + @Override public synchronized int capture(CaptureRequest request, CaptureCallback callback, Handler handler) throws CameraAccessException { if (request == null) { throw new IllegalArgumentException("request must not be null"); + } else if (request.isReprocess() && !isReprocessible()) { + throw new IllegalArgumentException("this capture session cannot handle reprocess " + + "requests"); } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -169,6 +181,19 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { throw new IllegalArgumentException("requests must have at least one element"); } + boolean reprocess = requests.get(0).isReprocess(); + if (reprocess && !isReprocessible()) { + throw new IllegalArgumentException("this capture session cannot handle reprocess " + + "requests"); + } + + for (int i = 1; i < requests.size(); i++) { + if (requests.get(i).isReprocess() != reprocess) { + throw new IllegalArgumentException("cannot mix regular and reprocess capture " + + " requests"); + } + } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -188,8 +213,11 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { Handler handler) throws CameraAccessException { if (request == null) { throw new IllegalArgumentException("request must not be null"); + } else if (request.isReprocess()) { + throw new IllegalArgumentException("repeating reprocess requests are not supported"); } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -212,6 +240,13 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { throw new IllegalArgumentException("requests must have at least one element"); } + for (CaptureRequest r : requests) { + if (r.isReprocess()) { + throw new IllegalArgumentException("repeating reprocess burst requests are not " + + "supported"); + } + } + checkNotClosed(); handler = checkHandler(handler, callback); @@ -257,6 +292,16 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { // The next BUSY -> IDLE set of transitions will mark the end of the abort. } + @Override + public boolean isReprocessible() { + return mInput != null; + } + + @Override + public Surface getInputSurface() { + return mInput; + } + /** * Replace this session with another session. * @@ -549,6 +594,13 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } } } + + @Override + public void onSurfacePrepared(Surface surface) { + if (VERBOSE) Log.v(TAG, mIdString + "onPrepared"); + mStateCallback.onSurfacePrepared(session, surface); + } + }; } @@ -658,8 +710,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { mUnconfigureDrainer.taskStarted(); try { - mDeviceImpl - .configureOutputsChecked(null); // begin transition to unconfigured + // begin transition to unconfigured + mDeviceImpl.configureStreamsChecked(null, null); } catch (CameraAccessException e) { // OK: do not throw checked exceptions. Log.e(TAG, mIdString + "Exception while configuring outputs: ", e); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 38f8e39..1e680dfd 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -28,7 +28,10 @@ import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.ReprocessFormatsMap; +import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.hardware.camera2.utils.LongParcelable; @@ -37,6 +40,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import android.util.Size; import android.util.SparseArray; import android.view.Surface; @@ -46,7 +50,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.TreeSet; +import java.util.LinkedList; +import java.util.TreeMap; /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate @@ -78,9 +83,11 @@ public class CameraDeviceImpl extends CameraDevice { private int mRepeatingRequestId = REQUEST_ID_NONE; private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>(); - // Map stream IDs to Surfaces + // Map stream IDs to input/output configurations + private SimpleEntry<Integer, InputConfiguration> mConfiguredInput = + new SimpleEntry<>(REQUEST_ID_NONE, null); private final SparseArray<OutputConfiguration> mConfiguredOutputs = - new SparseArray<OutputConfiguration>(); + new SparseArray<>(); private final String mCameraId; private final CameraCharacteristics mCharacteristics; @@ -320,38 +327,48 @@ public class CameraDeviceImpl extends CameraDevice { for (Surface s : outputs) { outputConfigs.add(new OutputConfiguration(s)); } - configureOutputsChecked(outputConfigs); + configureStreamsChecked(/*inputConfig*/null, outputConfigs); + } /** - * Attempt to configure the outputs; the device goes to idle and then configures the - * new outputs if possible. + * Attempt to configure the input and outputs; the device goes to idle and then configures the + * new input and outputs if possible. * - * <p>The configuration may gracefully fail, if there are too many outputs, if the formats - * are not supported, or if the sizes for that format is not supported. In this case this - * function will return {@code false} and the unconfigured callback will be fired.</p> + * <p>The configuration may gracefully fail, if input configuration is not supported, + * if there are too many outputs, if the formats are not supported, or if the sizes for that + * format is not supported. In this case this function will return {@code false} and the + * unconfigured callback will be fired.</p> * - * <p>If the configuration succeeds (with 1 or more outputs), then the idle callback is fired. - * Unconfiguring the device always fires the idle callback.</p> + * <p>If the configuration succeeds (with 1 or more outputs with or without an input), + * then the idle callback is fired. Unconfiguring the device always fires the idle callback.</p> * + * @param inputConfig input configuration or {@code null} for no input * @param outputs a list of one or more surfaces, or {@code null} to unconfigure * @return whether or not the configuration was successful * * @throws CameraAccessException if there were any unexpected problems during configuration */ - public boolean configureOutputsChecked(List<OutputConfiguration> outputs) - throws CameraAccessException { + public boolean configureStreamsChecked(InputConfiguration inputConfig, + List<OutputConfiguration> outputs) throws CameraAccessException { // Treat a null input the same an empty list if (outputs == null) { outputs = new ArrayList<OutputConfiguration>(); } + if (outputs.size() == 0 && inputConfig != null) { + throw new IllegalArgumentException("cannot configure an input stream without " + + "any output streams"); + } + + checkInputConfiguration(inputConfig); + boolean success = false; synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); // Streams to create HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs); - // Streams to delete + // Streams to delete List<Integer> deleteList = new ArrayList<Integer>(); // Determine which streams need to be created, which to be deleted @@ -373,6 +390,24 @@ public class CameraDeviceImpl extends CameraDevice { waitUntilIdle(); mRemoteDevice.beginConfigure(); + + // reconfigure the input stream if the input configuration is different. + InputConfiguration currentInputConfig = mConfiguredInput.getValue(); + if (inputConfig != currentInputConfig && + (inputConfig == null || !inputConfig.equals(currentInputConfig))) { + if (currentInputConfig != null) { + mRemoteDevice.deleteStream(mConfiguredInput.getKey()); + mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( + REQUEST_ID_NONE, null); + } + if (inputConfig != null) { + int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(), + inputConfig.getHeight(), inputConfig.getFormat()); + mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( + streamId, inputConfig); + } + } + // Delete all streams first (to free up HW resources) for (Integer streamId : deleteList) { mRemoteDevice.deleteStream(streamId); @@ -429,7 +464,7 @@ public class CameraDeviceImpl extends CameraDevice { for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } - createCaptureSessionByOutputConfiguration(outConfigurations, callback, handler); + createCaptureSessionInternal(null, outConfigurations, callback, handler); } @Override @@ -437,9 +472,39 @@ public class CameraDeviceImpl extends CameraDevice { List<OutputConfiguration> outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException { + if (DEBUG) { + Log.d(TAG, "createCaptureSessionByOutputConfiguration"); + } + + createCaptureSessionInternal(null, outputConfigurations, callback, handler); + } + + @Override + public void createReprocessibleCaptureSession(InputConfiguration inputConfig, + List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException { + if (DEBUG) { + Log.d(TAG, "createReprocessibleCaptureSession"); + } + + if (inputConfig == null) { + throw new IllegalArgumentException("inputConfig cannot be null when creating a " + + "reprocessible capture session"); + } + List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); + for (Surface surface : outputs) { + outConfigurations.add(new OutputConfiguration(surface)); + } + createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler); + } + + private void createCaptureSessionInternal(InputConfiguration inputConfig, + List<OutputConfiguration> outputConfigurations, + CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException { synchronized(mInterfaceLock) { if (DEBUG) { - Log.d(TAG, "createCaptureSession"); + Log.d(TAG, "createCaptureSessionInternal"); } checkIfCameraClosedOrInError(); @@ -453,15 +518,24 @@ public class CameraDeviceImpl extends CameraDevice { // TODO: dont block for this boolean configureSuccess = true; CameraAccessException pendingException = null; + Surface input = null; try { - // configure outputs and then block until IDLE - configureSuccess = configureOutputsChecked(outputConfigurations); + // configure streams and then block until IDLE + configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations); + if (inputConfig != null) { + input = new Surface(); + mRemoteDevice.getInputSurface(/*out*/input); + } } catch (CameraAccessException e) { configureSuccess = false; pendingException = e; + input = null; if (DEBUG) { Log.v(TAG, "createCaptureSession - failed with exception ", e); } + } catch (RemoteException e) { + // impossible + return; } List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size()); @@ -470,7 +544,7 @@ public class CameraDeviceImpl extends CameraDevice { } // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. CameraCaptureSessionImpl newSession = - new CameraCaptureSessionImpl(mNextSessionId++, + new CameraCaptureSessionImpl(mNextSessionId++, input, outSurfaces, callback, handler, this, mDeviceHandler, configureSuccess); @@ -512,12 +586,48 @@ public class CameraDeviceImpl extends CameraDevice { } CaptureRequest.Builder builder = - new CaptureRequest.Builder(templatedRequest); + new CaptureRequest.Builder(templatedRequest, /*reprocess*/false); return builder; } } + @Override + public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult) + throws CameraAccessException { + synchronized(mInterfaceLock) { + checkIfCameraClosedOrInError(); + + CameraMetadataNative resultMetadata = new + CameraMetadataNative(inputResult.getNativeCopy()); + + return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true); + } + } + + public void prepare(Surface surface) throws CameraAccessException { + synchronized(mInterfaceLock) { + int streamId = -1; + for (int i = 0; i < mConfiguredOutputs.size(); i++) { + if (surface == mConfiguredOutputs.valueAt(i).getSurface()) { + streamId = mConfiguredOutputs.keyAt(i); + break; + } + } + if (streamId == -1) { + throw new IllegalArgumentException("Surface is not part of this session"); + } + try { + mRemoteDevice.prepare(streamId); + } catch (CameraRuntimeException e) { + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return; + } + } + } + public int capture(CaptureRequest request, CaptureCallback callback, Handler handler) throws CameraAccessException { if (DEBUG) { @@ -810,6 +920,40 @@ public class CameraDeviceImpl extends CameraDevice { } } + private void checkInputConfiguration(InputConfiguration inputConfig) { + if (inputConfig != null) { + StreamConfigurationMap configMap = mCharacteristics.get( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + + int[] inputFormats = configMap.getInputFormats(); + boolean validFormat = false; + for (int format : inputFormats) { + if (format == inputConfig.getFormat()) { + validFormat = true; + } + } + + if (validFormat == false) { + throw new IllegalArgumentException("input format " + inputConfig.getFormat() + + " is not valid"); + } + + boolean validSize = false; + Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat()); + for (Size s : inputSizes) { + if (inputConfig.getWidth() == s.getWidth() && + inputConfig.getHeight() == s.getHeight()) { + validSize = true; + } + } + + if (validSize == false) { + throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" + + inputConfig.getHeight() + " is not valid"); + } + } + } + /** * <p>A callback for tracking the progress of a {@link CaptureRequest} * submitted to the camera device.</p> @@ -935,6 +1079,14 @@ public class CameraDeviceImpl extends CameraDevice { public void onIdle(CameraDevice camera) { // Default empty implementation } + + /** + * The method called when the camera device has finished preparing + * an output Surface + */ + public void onSurfacePrepared(Surface surface) { + // Default empty implementation + } } static class CaptureCallbackHolder { @@ -996,19 +1148,46 @@ public class CameraDeviceImpl extends CameraDevice { public class FrameNumberTracker { private long mCompletedFrameNumber = -1; - private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>(); + private long mCompletedReprocessFrameNumber = -1; + /** the skipped frame numbers that belong to regular results */ + private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>(); + /** the skipped frame numbers that belong to reprocess results */ + private final LinkedList<Long> mSkippedReprocessFrameNumbers = new LinkedList<Long>(); + /** frame number -> is reprocess */ + private final TreeMap<Long, Boolean> mFutureErrorMap = new TreeMap<Long, Boolean>(); /** Map frame numbers to list of partial results */ private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>(); private void update() { - Iterator<Long> iter = mFutureErrorSet.iterator(); + Iterator iter = mFutureErrorMap.entrySet().iterator(); while (iter.hasNext()) { - long errorFrameNumber = iter.next(); - if (errorFrameNumber == mCompletedFrameNumber + 1) { - mCompletedFrameNumber++; - iter.remove(); + TreeMap.Entry pair = (TreeMap.Entry)iter.next(); + Long errorFrameNumber = (Long)pair.getKey(); + Boolean reprocess = (Boolean)pair.getValue(); + Boolean removeError = true; + if (reprocess) { + if (errorFrameNumber == mCompletedReprocessFrameNumber + 1) { + mCompletedReprocessFrameNumber = errorFrameNumber; + } else if (mSkippedReprocessFrameNumbers.isEmpty() != true && + errorFrameNumber == mSkippedReprocessFrameNumbers.element()) { + mCompletedReprocessFrameNumber = errorFrameNumber; + mSkippedReprocessFrameNumbers.remove(); + } else { + removeError = false; + } } else { - break; + if (errorFrameNumber == mCompletedFrameNumber + 1) { + mCompletedFrameNumber = errorFrameNumber; + } else if (mSkippedRegularFrameNumbers.isEmpty() != true && + errorFrameNumber == mSkippedRegularFrameNumbers.element()) { + mCompletedFrameNumber = errorFrameNumber; + mSkippedRegularFrameNumbers.remove(); + } else { + removeError = false; + } + } + if (removeError) { + iter.remove(); } } } @@ -1017,25 +1196,21 @@ public class CameraDeviceImpl extends CameraDevice { * This function is called every time when a result or an error is received. * @param frameNumber the frame number corresponding to the result or error * @param isError true if it is an error, false if it is not an error + * @param isReprocess true if it is a reprocess result, false if it is a regular result. */ - public void updateTracker(long frameNumber, boolean isError) { + public void updateTracker(long frameNumber, boolean isError, boolean isReprocess) { if (isError) { - mFutureErrorSet.add(frameNumber); + mFutureErrorMap.put(frameNumber, isReprocess); } else { - /** - * HAL cannot send an OnResultReceived for frame N unless it knows for - * sure that all frames prior to N have either errored out or completed. - * So if the current frame is not an error, then all previous frames - * should have arrived. The following line checks whether this holds. - */ - if (frameNumber != mCompletedFrameNumber + 1) { - Log.e(TAG, String.format( - "result frame number %d comes out of order, should be %d + 1", - frameNumber, mCompletedFrameNumber)); - // Continue on to set the completed frame number to this frame anyway, - // to be robust to lower-level errors and allow for clean shutdowns. + try { + if (isReprocess) { + updateCompletedReprocessFrameNumber(frameNumber); + } else { + updateCompletedFrameNumber(frameNumber); + } + } catch (IllegalArgumentException e) { + Log.e(TAG, e.getMessage()); } - mCompletedFrameNumber = frameNumber; } update(); } @@ -1049,12 +1224,13 @@ public class CameraDeviceImpl extends CameraDevice { * @param frameNumber the frame number corresponding to the result * @param result the total or partial result * @param partial {@true} if the result is partial, {@code false} if total + * @param isReprocess true if it is a reprocess result, false if it is a regular result. */ - public void updateTracker(long frameNumber, CaptureResult result, boolean partial) { - + public void updateTracker(long frameNumber, CaptureResult result, boolean partial, + boolean isReprocess) { if (!partial) { // Update the total result's frame status as being successful - updateTracker(frameNumber, /*isError*/false); + updateTracker(frameNumber, /*isError*/false, isReprocess); // Don't keep a list of total results, we don't need to track them return; } @@ -1093,28 +1269,112 @@ public class CameraDeviceImpl extends CameraDevice { return mCompletedFrameNumber; } + public long getCompletedReprocessFrameNumber() { + return mCompletedReprocessFrameNumber; + } + + /** + * Update the completed frame number for regular results. + * + * It validates that all previous frames have arrived except for reprocess frames. + * + * If there is a gap since previous regular frame number, assume the frames in the gap are + * reprocess frames and store them in the skipped reprocess frame number queue to check + * against when reprocess frames arrive. + */ + private void updateCompletedFrameNumber(long frameNumber) throws IllegalArgumentException { + if (frameNumber <= mCompletedFrameNumber) { + throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); + } else if (frameNumber <= mCompletedReprocessFrameNumber) { + // if frame number is smaller than completed reprocess frame number, + // it must be the head of mSkippedRegularFrameNumbers + if (mSkippedRegularFrameNumbers.isEmpty() == true || + frameNumber < mSkippedRegularFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " is a repeat"); + } else if (frameNumber > mSkippedRegularFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " comes out of order. Expecting " + + mSkippedRegularFrameNumbers.element()); + } + // frame number matches the head of the skipped frame number queue. + mSkippedRegularFrameNumbers.remove(); + } else { + // there is a gap of unseen frame numbers which should belong to reprocess result + // put all the skipped frame numbers in the queue + for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; + i < frameNumber; i++) { + mSkippedReprocessFrameNumbers.add(i); + } + } + + mCompletedFrameNumber = frameNumber; + } + + /** + * Update the completed frame number for reprocess results. + * + * It validates that all previous frames have arrived except for regular frames. + * + * If there is a gap since previous reprocess frame number, assume the frames in the gap are + * regular frames and store them in the skipped regular frame number queue to check + * against when regular frames arrive. + */ + private void updateCompletedReprocessFrameNumber(long frameNumber) + throws IllegalArgumentException { + if (frameNumber < mCompletedReprocessFrameNumber) { + throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); + } else if (frameNumber < mCompletedFrameNumber) { + // if reprocess frame number is smaller than completed regular frame number, + // it must be the head of the skipped reprocess frame number queue. + if (mSkippedReprocessFrameNumbers.isEmpty() == true || + frameNumber < mSkippedReprocessFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " is a repeat"); + } else if (frameNumber > mSkippedReprocessFrameNumbers.element()) { + throw new IllegalArgumentException("frame number " + frameNumber + + " comes out of order. Expecting " + + mSkippedReprocessFrameNumbers.element()); + } + // frame number matches the head of the skipped frame number queue. + mSkippedReprocessFrameNumbers.remove(); + } else { + // put all the skipped frame numbers in the queue + for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; + i < frameNumber; i++) { + mSkippedRegularFrameNumbers.add(i); + } + } + mCompletedReprocessFrameNumber = frameNumber; + } } private void checkAndFireSequenceComplete() { long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); + long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); + boolean isReprocess = false; Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator(); while (iter.hasNext()) { final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next(); - if (frameNumberRequestPair.getKey() <= completedFrameNumber) { - - // remove request from mCaptureCallbackMap - final int requestId = frameNumberRequestPair.getValue(); - final CaptureCallbackHolder holder; - synchronized(mInterfaceLock) { - if (mRemoteDevice == null) { - Log.w(TAG, "Camera closed while checking sequences"); - return; - } + boolean sequenceCompleted = false; + final int requestId = frameNumberRequestPair.getValue(); + final CaptureCallbackHolder holder; + synchronized(mInterfaceLock) { + if (mRemoteDevice == null) { + Log.w(TAG, "Camera closed while checking sequences"); + return; + } - int index = mCaptureCallbackMap.indexOfKey(requestId); - holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) - : null; - if (holder != null) { + int index = mCaptureCallbackMap.indexOfKey(requestId); + holder = (index >= 0) ? + mCaptureCallbackMap.valueAt(index) : null; + if (holder != null) { + isReprocess = holder.getRequest().isReprocess(); + // check if it's okay to remove request from mCaptureCallbackMap + if ((isReprocess && frameNumberRequestPair.getKey() <= + completedReprocessFrameNumber) || (!isReprocess && + frameNumberRequestPair.getKey() <= completedFrameNumber)) { + sequenceCompleted = true; mCaptureCallbackMap.removeAt(index); if (DEBUG) { Log.v(TAG, String.format( @@ -1125,36 +1385,40 @@ public class CameraDeviceImpl extends CameraDevice { } } } + } + + // If no callback is registered for this requestId or sequence completed, remove it + // from the frame number->request pair because it's not needed anymore. + if (holder == null || sequenceCompleted) { iter.remove(); + } - // Call onCaptureSequenceCompleted - if (holder != null) { - Runnable resultDispatch = new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()){ - if (DEBUG) { - Log.d(TAG, String.format( - "fire sequence complete for request %d", - requestId)); - } + // Call onCaptureSequenceCompleted + if (sequenceCompleted) { + Runnable resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()){ + if (DEBUG) { + Log.d(TAG, String.format( + "fire sequence complete for request %d", + requestId)); + } - long lastFrameNumber = frameNumberRequestPair.getKey(); - if (lastFrameNumber < Integer.MIN_VALUE - || lastFrameNumber > Integer.MAX_VALUE) { - throw new AssertionError(lastFrameNumber - + " cannot be cast to int"); - } - holder.getCallback().onCaptureSequenceCompleted( - CameraDeviceImpl.this, - requestId, - lastFrameNumber); + long lastFrameNumber = frameNumberRequestPair.getKey(); + if (lastFrameNumber < Integer.MIN_VALUE + || lastFrameNumber > Integer.MAX_VALUE) { + throw new AssertionError(lastFrameNumber + + " cannot be cast to int"); } + holder.getCallback().onCaptureSequenceCompleted( + CameraDeviceImpl.this, + requestId, + lastFrameNumber); } - }; - holder.getHandler().post(resultDispatch); - } - + } + }; + holder.getHandler().post(resultDispatch); } } } @@ -1319,9 +1583,11 @@ public class CameraDeviceImpl extends CameraDevice { final CaptureCallbackHolder holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); + final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); boolean isPartialResult = (resultExtras.getPartialResultCount() < mTotalPartialCount); + boolean isReprocess = request.isReprocess(); // Check if we have a callback for this if (holder == null) { @@ -1331,7 +1597,8 @@ public class CameraDeviceImpl extends CameraDevice { + frameNumber); } - mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, + isReprocess); return; } @@ -1343,11 +1610,11 @@ public class CameraDeviceImpl extends CameraDevice { + frameNumber); } - mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, + isReprocess); return; } - final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); Runnable resultDispatch = null; @@ -1398,7 +1665,7 @@ public class CameraDeviceImpl extends CameraDevice { holder.getHandler().post(resultDispatch); // Collect the partials for a total result; or mark the frame as totally completed - mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult); + mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, isReprocess); // Fire onCaptureSequenceCompleted if (!isPartialResult) { @@ -1407,6 +1674,31 @@ public class CameraDeviceImpl extends CameraDevice { } } + @Override + public void onPrepared(int streamId) { + final OutputConfiguration output; + final StateCallbackKK sessionCallback; + + if (DEBUG) { + Log.v(TAG, "Stream " + streamId + " is prepared"); + } + + synchronized(mInterfaceLock) { + output = mConfiguredOutputs.get(streamId); + sessionCallback = mSessionStateCallback; + } + + if (sessionCallback == null) return; + + if (output == null) { + Log.w(TAG, "onPrepared invoked for unknown output Surface"); + return; + } + final Surface surface = output.getSurface(); + + sessionCallback.onSurfacePrepared(surface); + } + /** * Called by onDeviceError for handling single-capture failures. */ @@ -1460,7 +1752,7 @@ public class CameraDeviceImpl extends CameraDevice { if (DEBUG) { Log.v(TAG, String.format("got error frame %d", frameNumber)); } - mFrameNumberTracker.updateTracker(frameNumber, /*error*/true); + mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.isReprocess()); checkAndFireSequenceComplete(); } diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 70f3463..abe26ea 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -252,6 +252,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override + public void onPrepared(int streamId) { + // TODO + } + + @Override public IBinder asBinder() { // This is solely intended to be used for in-process binding. return null; @@ -530,6 +535,18 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override + public int createInputStream(int width, int height, int format) { + Log.e(TAG, "creating input stream is not supported on legacy devices"); + return CameraBinderDecorator.INVALID_OPERATION; + } + + @Override + public int getInputSurface(/*out*/ Surface surface) { + Log.e(TAG, "getting input surface is not supported on legacy devices"); + return CameraBinderDecorator.INVALID_OPERATION; + } + + @Override public int createDefaultRequest(int templateId, /*out*/CameraMetadataNative request) { if (DEBUG) { Log.d(TAG, "createDefaultRequest called."); @@ -605,6 +622,19 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { return CameraBinderDecorator.NO_ERROR; } + public int prepare(int streamId) { + if (DEBUG) { + Log.d(TAG, "prepare called."); + } + if (mLegacyDevice.isClosed()) { + Log.e(TAG, "Cannot prepare stream, device has been closed."); + return CameraBinderDecorator.ENODEV; + } + + // TODO: Implement and fire callback + return CameraBinderDecorator.NO_ERROR; + } + @Override public IBinder asBinder() { // This is solely intended to be used for in-process binding. diff --git a/core/java/android/hardware/camera2/params/InputConfiguration.java b/core/java/android/hardware/camera2/params/InputConfiguration.java new file mode 100644 index 0000000..dea1c5c --- /dev/null +++ b/core/java/android/hardware/camera2/params/InputConfiguration.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.params; + +import android.hardware.camera2.utils.HashCodeHelpers; + +/** + * Immutable class to store an input configuration that is used to create a reprocessible capture + * session. + * + * @see CameraDevice#createReprocessibleCaptureSession + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ +public final class InputConfiguration { + + private final int mWidth; + private final int mHeight; + private final int mFormat; + + /** + * Create an input configration with the width, height, and user-defined format. + * + * <p>Images of an user-defined format are accessible by applications. Use + * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP} + * to query supported input formats</p> + * + * @param width Width of the input buffers. + * @param height Height of the input buffers. + * @param format Format of the input buffers. One of ImageFormat or PixelFormat constants. + * + * @see android.graphics.ImageFormat + * @see android.graphics.PixelFormat + * @see android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public InputConfiguration(int width, int height, int format) { + mWidth = width; + mHeight = height; + mFormat = format; + } + + /** + * Get the width of this input configration. + * + * @return width of this input configuration. + */ + public int getWidth() { + return mWidth; + } + + /** + * Get the height of this input configration. + * + * @return height of this input configuration. + */ + public int getHeight() { + return mHeight; + } + + /** + * Get the format of this input configration. + * + * @return format of this input configuration. + */ + public int getFormat() { + return mFormat; + } + + /** + * Check if this InputConfiguration is equal to another InputConfiguration. + * + * <p>Two input configurations are equal if and only if they have the same widths, heights, and + * formats.</p> + * + * @param obj the object to compare this instance with. + * + * @return {@code true} if the objects were equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof InputConfiguration)) { + return false; + } + + InputConfiguration otherInputConfig = (InputConfiguration) obj; + + if (otherInputConfig.getWidth() == mWidth && + otherInputConfig.getHeight() == mHeight && + otherInputConfig.getFormat() == mFormat) { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCode(mWidth, mHeight, mFormat); + } +} diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 6b4bc1e..b757a9a 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -56,7 +56,7 @@ public class FingerprintManager { private static final boolean DEBUG = true; private static final int MSG_ENROLL_RESULT = 100; private static final int MSG_ACQUIRED = 101; - private static final int MSG_PROCESSED = 102; + private static final int MSG_AUTHENTICATED = 102; private static final int MSG_ERROR = 103; private static final int MSG_REMOVED = 104; @@ -103,6 +103,11 @@ public class FingerprintManager { */ public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; + /** + * The operation was canceled because the API is locked out due to too many attempts. + */ + public static final int FINGERPRINT_ERROR_LOCKOUT = 7; + /** * Hardware vendors may extend this list if there are conditions that do not fall under one of * the above categories. Vendors are responsible for providing error strings for these errors. @@ -169,15 +174,9 @@ public class FingerprintManager { private Fingerprint mRemovalFingerprint; private class OnEnrollCancelListener implements OnCancelListener { - private long mChallenge; - - public OnEnrollCancelListener(long challenge) { - mChallenge = challenge; - } - @Override public void onCancel() { - cancelEnrollment(mChallenge); + cancelEnrollment(); } } @@ -380,6 +379,26 @@ public class FingerprintManager { */ public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, int flags) { + authenticate(crypto, cancel, callback, flags, UserHandle.myUserId()); + } + + /** + * Request authentication of a crypto object. This call warms up the fingerprint hardware + * and starts scanning for a fingerprint. It terminates when + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or + * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult) is called, at + * which point the object is no longer valid. The operation can be canceled by using the + * provided cancel object. + * + * @param crypto object associated with the call or null if none required. + * @param cancel an object that can be used to cancel authentication + * @param callback an object to receive authentication events + * @param flags optional flags; should be 0 + * @param userId the userId the fingerprint belongs to + * @hide + */ + public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, + @NonNull AuthenticationCallback callback, int flags, int userId) { if (callback == null) { throw new IllegalArgumentException("Must supply an authentication callback"); } @@ -397,7 +416,7 @@ public class FingerprintManager { mAuthenticationCallback = callback; mCryptoObject = crypto; long sessionId = crypto != null ? crypto.getOpId() : 0; - mService.authenticate(mToken, sessionId, getCurrentUserId(), mServiceReceiver, flags); + mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags); } catch (RemoteException e) { Log.w(TAG, "Remote exception while authenticating: ", e); if (callback != null) { @@ -417,14 +436,14 @@ public class FingerprintManager { * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at * which point the object is no longer valid. The operation can be canceled by using the * provided cancel object. - * @param challenge a unique id provided by a recent verification of device credentials - * (e.g. pin, pattern or password). + * @param token a unique token provided by a recent creation or verification of device + * credentials (e.g. pin, pattern or password). * @param cancel an object that can be used to cancel enrollment * @param callback an object to receive enrollment events * @param flags optional flags * @hide */ - public void enroll(long challenge, CancellationSignal cancel, EnrollmentCallback callback, + public void enroll(byte [] token, CancellationSignal cancel, EnrollmentCallback callback, int flags) { if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); @@ -435,13 +454,13 @@ public class FingerprintManager { Log.w(TAG, "enrollment already canceled"); return; } else { - cancel.setOnCancelListener(new OnEnrollCancelListener(challenge)); + cancel.setOnCancelListener(new OnEnrollCancelListener()); } } if (mService != null) try { mEnrollmentCallback = callback; - mService.enroll(mToken, challenge, getCurrentUserId(), mServiceReceiver, flags); + mService.enroll(mToken, token, getCurrentUserId(), mServiceReceiver, flags); } catch (RemoteException e) { Log.w(TAG, "Remote exception in enroll: ", e); if (callback != null) { @@ -554,8 +573,8 @@ public class FingerprintManager { case MSG_ACQUIRED: sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */); break; - case MSG_PROCESSED: - sendProcessedResult((Fingerprint) msg.obj); + case MSG_AUTHENTICATED: + sendAuthenticatedResult((Fingerprint) msg.obj); break; case MSG_ERROR: sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */); @@ -597,7 +616,7 @@ public class FingerprintManager { } } - private void sendProcessedResult(Fingerprint fp) { + private void sendAuthenticatedResult(Fingerprint fp) { if (mAuthenticationCallback != null) { if (fp.getFingerId() == 0 && fp.getGroupId() == 0) { // Fingerprint template valid but doesn't match one in database @@ -647,7 +666,7 @@ public class FingerprintManager { mRemovalCallback = null; } - private void cancelEnrollment(long challenge) { + private void cancelEnrollment() { if (mService != null) try { mService.cancelEnrollment(mToken); } catch (RemoteException e) { @@ -675,8 +694,11 @@ public class FingerprintManager { return mContext.getString( com.android.internal.R.string.fingerprint_error_no_space); case FINGERPRINT_ERROR_TIMEOUT: - return mContext.getString( - com.android.internal.R.string.fingerprint_error_timeout); + return mContext.getString(com.android.internal.R.string.fingerprint_error_timeout); + case FINGERPRINT_ERROR_CANCELED: + return mContext.getString(com.android.internal.R.string.fingerprint_error_canceled); + case FINGERPRINT_ERROR_LOCKOUT: + return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout); default: if (errMsg >= FINGERPRINT_ERROR_VENDOR_BASE) { int msgNumber = errMsg - FINGERPRINT_ERROR_VENDOR_BASE; @@ -733,8 +755,8 @@ public class FingerprintManager { mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, 0, deviceId).sendToTarget(); } - public void onProcessed(long deviceId, int fingerId, int groupId) { - mHandler.obtainMessage(MSG_PROCESSED, + public void onAuthenticated(long deviceId, int fingerId, int groupId) { + mHandler.obtainMessage(MSG_AUTHENTICATED, new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget(); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 2fcb20e..6fe72d5 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -33,7 +33,7 @@ interface IFingerprintService { void cancelAuthentication(IBinder token); // Start fingerprint enrollment - void enroll(IBinder token, long challenge, int groupId, IFingerprintServiceReceiver receiver, + void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, int flags); // Cancel enrollment in progress diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl index e82395f..a2d74b8 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl @@ -25,7 +25,7 @@ import android.os.UserHandle; oneway interface IFingerprintServiceReceiver { void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining); void onAcquired(long deviceId, int acquiredInfo); - void onProcessed(long deviceId, int fingerId, int groupId); + void onAuthenticated(long deviceId, int fingerId, int groupId); void onError(long deviceId, int error); void onRemoved(long deviceId, int fingerId, int groupId); } diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java index 5d40e94..ee2d43c 100644 --- a/core/java/android/hardware/location/GeofenceHardwareImpl.java +++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java @@ -41,6 +41,7 @@ import java.util.Iterator; public final class GeofenceHardwareImpl { private static final String TAG = "GeofenceHardwareImpl"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final int FIRST_VERSION_WITH_CAPABILITIES = 2; private final Context mContext; private static GeofenceHardwareImpl sInstance; @@ -54,6 +55,7 @@ public final class GeofenceHardwareImpl { private IFusedGeofenceHardware mFusedService; private IGpsGeofenceHardware mGpsService; private int mCapabilities; + private int mVersion = 1; private int[] mSupportedMonitorTypes = new int[GeofenceHardware.NUM_MONITORS]; @@ -145,8 +147,10 @@ public final class GeofenceHardwareImpl { private void updateFusedHardwareAvailability() { boolean fusedSupported; try { + final boolean hasGnnsCapabilities = (mVersion < FIRST_VERSION_WITH_CAPABILITIES) + || (mCapabilities & CAPABILITY_GNSS) != 0; fusedSupported = (mFusedService != null - ? mFusedService.isSupported() && (mCapabilities & CAPABILITY_GNSS) != 0 + ? mFusedService.isSupported() && hasGnnsCapabilities : false); } catch (RemoteException e) { Log.e(TAG, "RemoteException calling LocationManagerService"); @@ -177,6 +181,11 @@ public final class GeofenceHardwareImpl { updateFusedHardwareAvailability(); } + public void setVersion(int version) { + mVersion = version; + updateFusedHardwareAvailability(); + } + public void setFusedGeofenceHardware(IFusedGeofenceHardware service) { if(mFusedService == null) { mFusedService = service; @@ -230,7 +239,12 @@ public final class GeofenceHardwareImpl { case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE: return CAPABILITY_GNSS; case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE: - return mCapabilities; + if (mVersion >= FIRST_VERSION_WITH_CAPABILITIES) { + return mCapabilities; + } + // This was the implied capability on old FLP HAL versions that didn't + // have the capability callback. + return CAPABILITY_GNSS; } break; } diff --git a/core/java/android/hardware/location/IFusedLocationHardware.aidl b/core/java/android/hardware/location/IFusedLocationHardware.aidl index 3de766a..2ea4d23 100644 --- a/core/java/android/hardware/location/IFusedLocationHardware.aidl +++ b/core/java/android/hardware/location/IFusedLocationHardware.aidl @@ -121,4 +121,9 @@ interface IFusedLocationHardware { * of the locations returned in this call. */ void flushBatchedLocations() = 11; + + /** + * Returns the version of this FLP HAL implementation. + */ + int getVersion() = 12; } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index da2c5e0..55e39b1 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -286,6 +286,14 @@ public class ConnectivityManager { public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal"; /** + * Action used to display a dialog that asks the user whether to connect to a network that is + * not validated. This intent is used to start the dialog in settings via startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED"; + + /** * The absence of a connection type. * @hide */ @@ -2455,6 +2463,29 @@ public class ConnectivityManager { } /** + * Informs the system whether it should switch to {@code network} regardless of whether it is + * validated or not. If {@code accept} is true, and the network was explicitly selected by the + * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become + * the system default network regardless of any other network that's currently connected. If + * {@code always} is true, then the choice is remembered, so that the next time the user + * connects to this network, the system will switch to it. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL} + * + * @param network The network to accept. + * @param accept Whether to accept the network even if unvalidated. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { + try { + mService.setAcceptUnvalidated(network, accept, always); + } catch (RemoteException e) {} + } + + /** * Resets all connectivity manager settings back to factory defaults. * @hide */ diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 3c09978..1aa9c45 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -156,6 +156,8 @@ interface IConnectivityManager void releaseNetworkRequest(in NetworkRequest networkRequest); + void setAcceptUnvalidated(in Network network, boolean accept, boolean always); + int getRestoreDefaultNetworkDelay(int networkType); boolean addVpnAddress(String address, int prefixLength); diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 65d325a1..67ecb5d 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -340,6 +340,35 @@ public class Network implements Parcelable { } } + /** + * Returns a handle representing this {@code Network}, for use with the NDK API. + */ + public long getNetworkHandle() { + // The network handle is explicitly not the same as the netId. + // + // The netId is an implementation detail which might be changed in the + // future, or which alone (i.e. in the absence of some additional + // context) might not be sufficient to fully identify a Network. + // + // As such, the intention is to prevent accidental misuse of the API + // that might result if a developer assumed that handles and netIds + // were identical and passing a netId to a call expecting a handle + // "just worked". Such accidental misuse, if widely deployed, might + // prevent future changes to the semantics of the netId field or + // inhibit the expansion of state required for Network objects. + // + // This extra layer of indirection might be seen as paranoia, and might + // never end up being necessary, but the added complexity is trivial. + // At some future date it may be desirable to realign the handle with + // Multiple Provisioning Domains API recommendations, as made by the + // IETF mif working group. + // + // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // value in the native/android/net.c NDK implementation. + final long HANDLE_MAGIC = 0xfacade; + return (((long) netId) << 32) | HANDLE_MAGIC; + } + // implement the Parcelable interface public int describeContents() { return 0; diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 24aaf0d..95ceb2a 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -104,7 +104,7 @@ public abstract class NetworkAgent extends Handler { public static final int EVENT_UID_RANGES_REMOVED = BASE + 6; /** - * Sent by ConnectivitySerice to the NetworkAgent to inform the agent of the + * Sent by ConnectivityService to the NetworkAgent to inform the agent of the * networks status - whether we could use the network or could not, due to * either a bad network configuration (no internet link) or captive portal. * @@ -119,9 +119,21 @@ public abstract class NetworkAgent extends Handler { * Sent by the NetworkAgent to ConnectivityService to indicate this network was * explicitly selected. This should be sent before the NetworkInfo is marked * CONNECTED so it can be given special treatment at that time. + * + * obj = boolean indicating whether to use this network even if unvalidated */ public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8; + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent of + * whether the network should in the future be used even if not validated. + * This decision is made by the user, but it is the network transport's + * responsibility to remember it. + * + * arg1 = 1 if true, 0 if false + */ + public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9; + public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score) { this(looper, context, logTag, ni, nc, lp, score, null); @@ -191,6 +203,9 @@ public abstract class NetworkAgent extends Handler { networkStatus(msg.arg1); break; } + case CMD_SAVE_ACCEPT_UNVALIDATED: { + saveAcceptUnvalidated(msg.arg1 != 0); + } } } @@ -258,10 +273,16 @@ public abstract class NetworkAgent extends Handler { /** * Called by the bearer to indicate this network was manually selected by the user. * This should be called before the NetworkInfo is marked CONNECTED so that this - * Network can be given special treatment at that time. + * Network can be given special treatment at that time. If {@code acceptUnvalidated} is + * {@code true}, then the system will switch to this network. If it is {@code false} and the + * network cannot be validated, the system will ask the user whether to switch to this network. + * If the user confirms and selects "don't ask again", then the system will call + * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever + * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement + * {@link #saveAcceptUnvalidated} to respect the user's choice. */ - public void explicitlySelected() { - queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, 0); + public void explicitlySelected(boolean acceptUnvalidated) { + queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, acceptUnvalidated); } /** @@ -290,6 +311,16 @@ public abstract class NetworkAgent extends Handler { protected void networkStatus(int status) { } + /** + * Called when the user asks to remember the choice to use this network even if unvalidated. + * The transport is responsible for remembering the choice, and the next time the user connects + * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}. + * This method will only be called if {@link #explicitlySelected} was called with + * {@code acceptUnvalidated} set to {@code false}. + */ + protected void saveAcceptUnvalidated(boolean accept) { + } + protected void log(String s) { Log.d(LOG_TAG, "NetworkAgent: " + s); } diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java index b92c9e3..5511a24 100644 --- a/core/java/android/net/NetworkMisc.java +++ b/core/java/android/net/NetworkMisc.java @@ -45,6 +45,13 @@ public class NetworkMisc implements Parcelable { public boolean explicitlySelected; /** + * Set if the user desires to use this network even if it is unvalidated. This field has meaning + * only if {#link explicitlySelected} is true. If it is, this field must also be set to the + * appropriate value based on previous user choice. + */ + public boolean acceptUnvalidated; + + /** * For mobile networks, this is the subscriber ID (such as IMSI). */ public String subscriberId; @@ -56,6 +63,7 @@ public class NetworkMisc implements Parcelable { if (nm != null) { allowBypass = nm.allowBypass; explicitlySelected = nm.explicitlySelected; + acceptUnvalidated = nm.acceptUnvalidated; subscriberId = nm.subscriberId; } } @@ -69,6 +77,7 @@ public class NetworkMisc implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(allowBypass ? 1 : 0); out.writeInt(explicitlySelected ? 1 : 0); + out.writeInt(acceptUnvalidated ? 1 : 0); out.writeString(subscriberId); } @@ -78,6 +87,7 @@ public class NetworkMisc implements Parcelable { NetworkMisc networkMisc = new NetworkMisc(); networkMisc.allowBypass = in.readInt() != 0; networkMisc.explicitlySelected = in.readInt() != 0; + networkMisc.acceptUnvalidated = in.readInt() != 0; networkMisc.subscriberId = in.readString(); return networkMisc; } diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index 00b2ee3..f10e530 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -40,6 +40,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -262,7 +263,7 @@ public final class ApduServiceInfo implements Parcelable { * for that category. * @return List of AIDs registered by the service */ - public ArrayList<String> getAids() { + public List<String> getAids() { final ArrayList<String> aids = new ArrayList<String>(); for (AidGroup group : getAidGroups()) { aids.addAll(group.aids); @@ -270,6 +271,18 @@ public final class ApduServiceInfo implements Parcelable { return aids; } + public List<String> getPrefixAids() { + final ArrayList<String> prefixAids = new ArrayList<String>(); + for (AidGroup group : getAidGroups()) { + for (String aid : group.aids) { + if (aid.endsWith("*")) { + prefixAids.add(aid); + } + } + } + return prefixAids; + } + /** * Returns the registered AID group for this category. */ diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index c7edb1a..7c5ddee 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1135,8 +1135,10 @@ public abstract class BatteryStats implements Parcelable { public static final int EVENT_PACKAGE_INSTALLED = 0x000c; // Event for a package being uninstalled. public static final int EVENT_PACKAGE_UNINSTALLED = 0x000d; + // Event for a package being uninstalled. + public static final int EVENT_ALARM = 0x000e; // Number of event types. - public static final int EVENT_COUNT = 0x000e; + public static final int EVENT_COUNT = 0x000f; // Mask to extract out only the type part of the event. public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH); @@ -1158,6 +1160,8 @@ public abstract class BatteryStats implements Parcelable { EVENT_USER_FOREGROUND | EVENT_FLAG_START; public static final int EVENT_USER_FOREGROUND_FINISH = EVENT_USER_FOREGROUND | EVENT_FLAG_FINISH; + public static final int EVENT_ALARM_START = EVENT_ALARM | EVENT_FLAG_START; + public static final int EVENT_ALARM_FINISH = EVENT_ALARM | EVENT_FLAG_FINISH; // For CMD_EVENT. public int eventCode; @@ -1789,12 +1793,12 @@ public abstract class BatteryStats implements Parcelable { public static final String[] HISTORY_EVENT_NAMES = new String[] { "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn", - "motion", "active", "pkginst", "pkgunin" + "motion", "active", "pkginst", "pkgunin", "alarm" }; public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] { "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn", - "Esm", "Eac", "Epi", "Epu" + "Esm", "Eac", "Epi", "Epu", "Eal" }; /** diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index b4a4624..64562a4 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -187,6 +187,13 @@ public class Binder implements IBinder { } /** + * Call blocks until the number of executing binder threads is less + * than the maximum number of binder threads allowed for this process. + * @hide + */ + public static final native void blockUntilThreadAvailable(); + + /** * Default constructor initializes the object. */ public Binder() { diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 512e212..75b1101 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -34,6 +34,7 @@ import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Map; import org.apache.harmony.dalvik.ddmc.Chunk; import org.apache.harmony.dalvik.ddmc.ChunkHandler; @@ -1101,6 +1102,95 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo } /** + * Returns the value of a particular runtime statistic or {@code null} if no + * such runtime statistic exists. + * + * <p>The following table lists the runtime statistics that the runtime supports. + * Note runtime statistics may be added or removed in a future API level.</p> + * + * <table> + * <thead> + * <tr> + * <th>Runtime statistic name</th> + * <th>Meaning</th> + * <th>Example</th> + * <th>Supported (API Levels)</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>art.gc.gc-count</td> + * <td>The number of garbage collection runs.</td> + * <td>{@code 164}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>art.gc.gc-time</td> + * <td>The total duration of garbage collection runs in ms.</td> + * <td>{@code 62364}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>art.gc.bytes-allocated</td> + * <td>The total number of bytes that the application allocated.</td> + * <td>{@code 1463948408}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>art.gc.bytes-freed</td> + * <td>The total number of bytes that garbage collection reclaimed.</td> + * <td>{@code 1313493084}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>art.gc.blocking-gc-count</td> + * <td>The number of blocking garbage collection runs.</td> + * <td>{@code 2}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>art.gc.blocking-gc-time</td> + * <td>The total duration of blocking garbage collection runs in ms.</td> + * <td>{@code 804}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>art.gc.gc-count-rate-histogram</td> + * <td>The histogram of the number of garbage collection runs per 10 seconds.</td> + * <td>{@code 0:34503,1:45350,2:11281,3:8088,4:43,5:8}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>art.gc.blocking-gc-count-rate-histogram</td> + * <td>The histogram of the number of garbage collection runs per 10 seconds.</td> + * <td>{@code 0:99269,1:1,2:1}</td> + * <td>23</td> + * </tr> + * </tbody> + * </table> + * + * @param statName + * the name of the runtime statistic to look up. + * @return the value of the specified runtime statistic or {@code null} if the + * runtime statistic doesn't exist. + * @hide + */ + public static String getRuntimeStat(String statName) { + return VMDebug.getRuntimeStat(statName); + } + + /** + * Returns a map of the names/values of the runtime statistics + * that {@link #getRuntimeStat(String)} supports. + * + * @return a map of the names/values of the supported runtime statistics. + * @hide + */ + public static Map<String, String> getRuntimeStats() { + return VMDebug.getRuntimeStats(); + } + + /** * Returns the size of the native heap. * @return The size of the native heap in bytes. */ diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java index 4704b67..dc96640 100644 --- a/core/java/android/os/storage/DiskInfo.java +++ b/core/java/android/os/storage/DiskInfo.java @@ -16,6 +16,7 @@ package android.os.storage; +import android.annotation.NonNull; import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; @@ -33,6 +34,8 @@ import java.io.CharArrayWriter; * @hide */ public class DiskInfo implements Parcelable { + public static final String EXTRA_DISK_ID = "android.os.storage.extra.DISK_ID"; + public static final int FLAG_ADOPTABLE = 1 << 0; public static final int FLAG_DEFAULT_PRIMARY = 1 << 1; public static final int FLAG_SD = 1 << 2; @@ -42,7 +45,7 @@ public class DiskInfo implements Parcelable { public final int flags; public long size; public String label; - public String[] volumes; + public String[] volumeIds; public DiskInfo(String id, int flags) { this.id = Preconditions.checkNotNull(id); @@ -54,7 +57,11 @@ public class DiskInfo implements Parcelable { flags = parcel.readInt(); size = parcel.readLong(); label = parcel.readString(); - volumes = parcel.readStringArray(); + volumeIds = parcel.readStringArray(); + } + + public @NonNull String getId() { + return id; } public String getDescription() { @@ -68,6 +75,18 @@ public class DiskInfo implements Parcelable { } } + public boolean isSd() { + return (flags & FLAG_SD) != 0; + } + + public boolean isUsb() { + return (flags & FLAG_USB) != 0; + } + + public boolean isAdoptable() { + return (flags & FLAG_ADOPTABLE) != 0; + } + @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); @@ -82,7 +101,7 @@ public class DiskInfo implements Parcelable { pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags)); pw.printPair("size", size); pw.printPair("label", label); - pw.printPair("volumes", volumes); + pw.printPair("volumeIds", volumeIds); pw.decreaseIndent(); pw.println(); } @@ -122,6 +141,6 @@ public class DiskInfo implements Parcelable { parcel.writeInt(this.flags); parcel.writeLong(size); parcel.writeString(label); - parcel.writeStringArray(volumes); + parcel.writeStringArray(volumeIds); } } diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 10ffd48..0a8187e 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -923,12 +923,13 @@ public interface IMountService extends IInterface { } @Override - public VolumeInfo[] getVolumes() throws RemoteException { + public VolumeInfo[] getVolumes(int _flags) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); VolumeInfo[] _result; try { _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(_flags); mRemote.transact(Stub.TRANSACTION_getVolumes, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArray(VolumeInfo.CREATOR); @@ -1029,6 +1030,39 @@ public interface IMountService extends IInterface { _data.recycle(); } } + + @Override + public void setVolumeNickname(String volId, String nickname) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + _data.writeString(nickname); + mRemote.transact(Stub.TRANSACTION_setVolumeNickname, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + _data.writeInt(flags); + _data.writeInt(mask); + mRemote.transact(Stub.TRANSACTION_setVolumeUserFlags, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } } private static final String DESCRIPTOR = "IMountService"; @@ -1132,6 +1166,9 @@ public interface IMountService extends IInterface { static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 50; static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 51; + static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52; + static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1566,7 +1603,8 @@ public interface IMountService extends IInterface { } case TRANSACTION_getVolumes: { data.enforceInterface(DESCRIPTOR); - VolumeInfo[] volumes = getVolumes(); + int _flags = data.readInt(); + VolumeInfo[] volumes = getVolumes(_flags); reply.writeNoException(); reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; @@ -1614,6 +1652,23 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_setVolumeNickname: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + String nickname = data.readString(); + setVolumeNickname(volId, nickname); + reply.writeNoException(); + return true; + } + case TRANSACTION_setVolumeUserFlags: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + int _flags = data.readInt(); + int _mask = data.readInt(); + setVolumeUserFlags(volId, _flags, _mask); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1902,7 +1957,7 @@ public interface IMountService extends IInterface { public void waitForAsecScan() throws RemoteException; public DiskInfo[] getDisks() throws RemoteException; - public VolumeInfo[] getVolumes() throws RemoteException; + public VolumeInfo[] getVolumes(int flags) throws RemoteException; public void mount(String volId) throws RemoteException; public void unmount(String volId) throws RemoteException; @@ -1911,4 +1966,7 @@ public interface IMountService extends IInterface { public void partitionPublic(String diskId) throws RemoteException; public void partitionPrivate(String diskId) throws RemoteException; public void partitionMixed(String diskId, int ratio) throws RemoteException; + + public void setVolumeNickname(String volId, String nickname) throws RemoteException; + public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException; } diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java index 3965f9d..fd914bc 100644 --- a/core/java/android/os/storage/IMountServiceListener.java +++ b/core/java/android/os/storage/IMountServiceListener.java @@ -91,6 +91,13 @@ public interface IMountServiceListener extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_onVolumeMetadataChanged: { + data.enforceInterface(DESCRIPTOR); + final VolumeInfo vol = (VolumeInfo) data.readParcelable(null); + onVolumeMetadataChanged(vol); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -175,6 +182,22 @@ public interface IMountServiceListener extends IInterface { _data.recycle(); } } + + @Override + public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeParcelable(vol, 0); + mRemote.transact(Stub.TRANSACTION_onVolumeMetadataChanged, _data, _reply, + android.os.IBinder.FLAG_ONEWAY); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } } static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0); @@ -182,6 +205,7 @@ public interface IMountServiceListener extends IInterface { static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2); + static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3); } /** @@ -204,4 +228,6 @@ public interface IMountServiceListener extends IInterface { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) throws RemoteException; + + public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java index 29d5494..28a187d 100644 --- a/core/java/android/os/storage/StorageEventListener.java +++ b/core/java/android/os/storage/StorageEventListener.java @@ -40,4 +40,7 @@ public class StorageEventListener { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { } + + public void onVolumeMetadataChanged(VolumeInfo vol) { + } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index bd42f6a..0e977ff 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -35,7 +35,6 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.os.SomeArgs; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import java.io.File; @@ -71,6 +70,9 @@ public class StorageManager { /** {@hide} */ public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical"; + /** {@hide} */ + public static final int FLAG_ALL_METADATA = 1 << 0; + private final Context mContext; private final ContentResolver mResolver; @@ -84,6 +86,7 @@ public class StorageManager { Handler.Callback { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; + private static final int MSG_VOLUME_METADATA_CHANGED = 3; final StorageEventListener mCallback; final Handler mHandler; @@ -106,6 +109,10 @@ public class StorageManager { mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); args.recycle(); return true; + case MSG_VOLUME_METADATA_CHANGED: + mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + args.recycle(); + return true; } args.recycle(); return false; @@ -133,6 +140,13 @@ public class StorageManager { args.argi3 = newState; mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); } + + @Override + public void onVolumeMetadataChanged(VolumeInfo vol) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = vol; + mHandler.obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); + } } /** @@ -456,18 +470,6 @@ public class StorageManager { } /** {@hide} */ - public @Nullable DiskInfo findDiskByVolumeId(String volId) { - Preconditions.checkNotNull(volId); - // TODO; go directly to service to make this faster - for (DiskInfo disk : getDisks()) { - if (ArrayUtils.contains(disk.volumes, volId)) { - return disk; - } - } - return null; - } - - /** {@hide} */ public @Nullable VolumeInfo findVolumeById(String id) { Preconditions.checkNotNull(id); // TODO; go directly to service to make this faster @@ -493,25 +495,27 @@ public class StorageManager { /** {@hide} */ public @NonNull List<VolumeInfo> getVolumes() { + return getVolumes(0); + } + + /** {@hide} */ + public @NonNull List<VolumeInfo> getVolumes(int flags) { try { - return Arrays.asList(mMountService.getVolumes()); + return Arrays.asList(mMountService.getVolumes(flags)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } /** {@hide} */ - public @Nullable String getBestVolumeDescription(String volId) { - String descrip = null; - - final VolumeInfo vol = findVolumeById(volId); - if (vol != null) { - descrip = vol.getDescription(); - } + public @Nullable String getBestVolumeDescription(VolumeInfo vol) { + String descrip = vol.getDescription(); - final DiskInfo disk = findDiskByVolumeId(volId); - if (disk != null && TextUtils.isEmpty(descrip)) { - descrip = disk.getDescription(); + if (vol.diskId != null) { + final DiskInfo disk = findDiskById(vol.diskId); + if (disk != null && TextUtils.isEmpty(descrip)) { + descrip = disk.getDescription(); + } } return descrip; @@ -572,6 +576,35 @@ public class StorageManager { } /** {@hide} */ + public void setVolumeNickname(String volId, String nickname) { + try { + mMountService.setVolumeNickname(volId, nickname); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void setVolumeInited(String volId, boolean inited) { + try { + mMountService.setVolumeUserFlags(volId, inited ? VolumeInfo.USER_FLAG_INITED : 0, + VolumeInfo.USER_FLAG_INITED); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void setVolumeSnoozed(String volId, boolean snoozed) { + try { + mMountService.setVolumeUserFlags(volId, snoozed ? VolumeInfo.USER_FLAG_SNOOZED : 0, + VolumeInfo.USER_FLAG_SNOOZED); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ public @Nullable StorageVolume getStorageVolume(File file) { return getStorageVolume(getVolumeList(), file); } diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index beca8b8..f06fc8c 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -22,10 +22,12 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.mtp.MtpStorage; +import android.net.Uri; import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.provider.DocumentsContract; import android.text.TextUtils; import android.util.ArrayMap; import android.util.DebugUtils; @@ -45,6 +47,8 @@ import java.io.File; * @hide */ public class VolumeInfo implements Parcelable { + public static final String EXTRA_VOLUME_ID = "android.os.storage.extra.VOLUME_ID"; + /** Stub volume representing internal private storage */ public static final String ID_PRIVATE_INTERNAL = "private"; /** Real volume representing internal emulated storage */ @@ -67,6 +71,9 @@ public class VolumeInfo implements Parcelable { public static final int FLAG_PRIMARY = 1 << 0; public static final int FLAG_VISIBLE = 1 << 1; + public static final int USER_FLAG_INITED = 1 << 0; + public static final int USER_FLAG_SNOOZED = 1 << 1; + private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); @@ -100,7 +107,9 @@ public class VolumeInfo implements Parcelable { /** Framework state */ public final int mtpIndex; + public String diskId; public String nickname; + public int userFlags = 0; public VolumeInfo(String id, int type, int mtpIndex) { this.id = Preconditions.checkNotNull(id); @@ -119,7 +128,9 @@ public class VolumeInfo implements Parcelable { fsLabel = parcel.readString(); path = parcel.readString(); mtpIndex = parcel.readInt(); + diskId = parcel.readString(); nickname = parcel.readString(); + userFlags = parcel.readInt(); } public static @NonNull String getEnvironmentForState(int state) { @@ -139,6 +150,30 @@ public class VolumeInfo implements Parcelable { return getBroadcastForEnvironment(getEnvironmentForState(state)); } + public @NonNull String getId() { + return id; + } + + public @Nullable String getDiskId() { + return diskId; + } + + public int getType() { + return type; + } + + public int getState() { + return state; + } + + public @Nullable String getFsUuid() { + return fsUuid; + } + + public @Nullable String getNickname() { + return nickname; + } + public @Nullable String getDescription() { if (ID_PRIVATE_INTERNAL.equals(id)) { return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); @@ -159,6 +194,14 @@ public class VolumeInfo implements Parcelable { return (flags & FLAG_VISIBLE) != 0; } + public boolean isInited() { + return (userFlags & USER_FLAG_INITED) != 0; + } + + public boolean isSnoozed() { + return (userFlags & USER_FLAG_SNOOZED) != 0; + } + public boolean isVisibleToUser(int userId) { if (type == TYPE_PUBLIC && userId == this.userId) { return isVisible(); @@ -169,6 +212,10 @@ public class VolumeInfo implements Parcelable { } } + public File getPath() { + return new File(path); + } + public File getPathForUser(int userId) { if (path == null) { return null; @@ -228,6 +275,34 @@ public class VolumeInfo implements Parcelable { fsUuid, envState); } + // TODO: avoid this layering violation + private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; + private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary"; + + /** + * Build an intent to browse the contents of this volume. Only valid for + * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}. + */ + public Intent buildBrowseIntent() { + final Uri uri; + if (type == VolumeInfo.TYPE_PUBLIC) { + uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid); + } else if (VolumeInfo.ID_EMULATED_INTERNAL.equals(id)) { + uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, + DOCUMENT_ROOT_PRIMARY_EMULATED); + } else if (type == VolumeInfo.TYPE_EMULATED) { + // TODO: build intent once supported + uri = null; + } else { + throw new IllegalArgumentException(); + } + + final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setData(uri); + return intent; + } + @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); @@ -250,6 +325,9 @@ public class VolumeInfo implements Parcelable { pw.println(); pw.printPair("path", path); pw.printPair("mtpIndex", mtpIndex); + pw.printPair("diskId", diskId); + pw.printPair("nickname", nickname); + pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags)); pw.decreaseIndent(); pw.println(); } @@ -295,6 +373,8 @@ public class VolumeInfo implements Parcelable { parcel.writeString(fsLabel); parcel.writeString(path); parcel.writeInt(mtpIndex); + parcel.writeString(diskId); parcel.writeString(nickname); + parcel.writeInt(userFlags); } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9edf6ad..f640f0d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2766,8 +2766,6 @@ public final class Settings { * It was about AudioManager's setting and thus affected all the applications which * relied on the setting, while this is purely about the vibration setting for incoming * calls. - * - * @hide */ public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; @@ -2788,7 +2786,6 @@ public final class Settings { * DTMF tone type played by the dialer when dialing. * 0 = Normal * 1 = Long - * @hide */ public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type"; @@ -7057,7 +7054,6 @@ public final class Settings { /** * Setting to 1 will hide carrier network settings. * Default is 0. - * @hide */ public static final String HIDE_CARRIER_NETWORK_SETTINGS = "hide_carrier_network_settings"; @@ -7717,6 +7713,13 @@ public final class Settings { */ public static final String[] MULTI_SIM_USER_PREFERRED_SUBS = {"user_preferred_sub1", "user_preferred_sub2","user_preferred_sub3"}; + + /** + * Whether to enable new contacts aggregator or not. + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String NEW_CONTACT_AGGREGATOR = "new_contact_aggregator"; } /** diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl new file mode 100644 index 0000000..2f3e296 --- /dev/null +++ b/core/java/android/service/gatekeeper/IGateKeeperService.aidl @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.gatekeeper; + +/** + * Interface for communication with GateKeeper, the + * secure password storage daemon. + * + * This must be kept manually in sync with system/core/gatekeeperd + * until AIDL can generate both C++ and Java bindings. + * + * @hide + */ +interface IGateKeeperService { + /** + * Enrolls a password, returning the handle to the enrollment to be stored locally. + * @param uid The Android user ID associated to this enrollment + * @param currentPasswordHandle The previously enrolled handle, or null if none + * @param currentPassword The previously enrolled plaintext password, or null if none. + * If provided, must verify against the currentPasswordHandle. + * @param desiredPassword The new desired password, for which a handle will be returned + * upon success. + * @return the handle corresponding to desiredPassword, or null + */ + byte[] enroll(int uid, in byte[] currentPasswordHandle, in byte[] currentPassword, + in byte[] desiredPassword); + + /** + * Verifies an enrolled handle against a provided, plaintext blob. + * @param uid The Android user ID associated to this enrollment + * @param enrolledPasswordHandle The handle against which the provided password will be + * verified. + * @param The plaintext blob to verify against enrolledPassword. + * @return True if the authentication was successful + */ + boolean verify(int uid, in byte[] enrolledPasswordHandle, + in byte[] providedPassword); + /** + * Verifies an enrolled handle against a provided, plaintext blob. + * @param uid The Android user ID associated to this enrollment + * @param challenge a challenge to authenticate agaisnt the device credential. If successful + * authentication occurs, this value will be written to the returned + * authentication attestation. + * @param enrolledPasswordHandle The handle against which the provided password will be + * verified. + * @param The plaintext blob to verify against enrolledPassword. + * @return an opaque attestation of authentication on success, or null. + */ + byte[] verifyChallenge(int uid, long challenge, in byte[] enrolledPasswordHandle, + in byte[] providedPassword); +} diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 20d7079..71b7f76 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -20,8 +20,10 @@ import android.app.AssistStructure; import android.app.Dialog; import android.app.Instrumentation; import android.app.VoiceInteractor; +import android.content.ComponentCallbacks2; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Rect; @@ -65,7 +67,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; * when done. It can also initiate voice interactions with applications by calling * {@link #startVoiceActivity}</p>. */ -public abstract class VoiceInteractionSession implements KeyEvent.Callback { +public abstract class VoiceInteractionSession implements KeyEvent.Callback, + ComponentCallbacks2 { static final String TAG = "VoiceInteractionSession"; static final boolean DEBUG = true; @@ -855,6 +858,18 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { hide(); } + @Override + public void onConfigurationChanged(Configuration newConfig) { + } + + @Override + public void onLowMemory() { + } + + @Override + public void onTrimMemory(int level) { + } + /** * Compute the interesting insets into your UI. The default implementation * sets {@link Insets#contentInsets outInsets.contentInsets.top} to the height diff --git a/core/java/android/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java index 008d55f..8f988f3 100644 --- a/core/java/android/service/voice/VoiceInteractionSessionService.java +++ b/core/java/android/service/voice/VoiceInteractionSessionService.java @@ -19,6 +19,7 @@ package android.service.voice; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.os.Looper; @@ -76,6 +77,30 @@ public abstract class VoiceInteractionSessionService extends Service { return mInterface.asBinder(); } + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (mSession != null) { + mSession.onConfigurationChanged(newConfig); + } + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + if (mSession != null) { + mSession.onLowMemory(); + } + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + if (mSession != null) { + mSession.onTrimMemory(level); + } + } + void doNewSession(IBinder token, Bundle args, int startFlags) { if (mSession != null) { mSession.doDestroy(); diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 2bcb352..7828851 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -811,7 +811,7 @@ public class StaticLayout extends Layout { float sum = 0; int i; - for (i = len; i >= 0; i--) { + for (i = len; i > 0; i--) { float w = widths[i - 1 + lineStart - widthStart]; if (w + sum + ellipsisWidth > avail) { diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java index 5af2832..794c8e7 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/android/view/PhoneWindow.java @@ -4257,7 +4257,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (deviceId != 0) { searchEvent = new SearchEvent(InputDevice.getDevice(deviceId)); } - result = cb.onSearchRequested(searchEvent); + try { + result = cb.onSearchRequested(searchEvent); + } catch (AbstractMethodError e) { + Log.e(TAG, "WindowCallback " + cb.getClass().getName() + " does not implement" + + " method onSearchRequested(SearchEvent); fa", e); + result = cb.onSearchRequested(); + } } if (!result && (getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) { diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 87d5d9a..5017a38 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -360,7 +360,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override boolean copyLayerInto(final HardwareLayer layer, final Bitmap bitmap) { return nCopyLayerInto(mNativeProxy, - layer.getDeferredLayerUpdater(), bitmap.getSkBitmap()); + layer.getDeferredLayerUpdater(), bitmap); } @Override @@ -531,7 +531,7 @@ public class ThreadedRenderer extends HardwareRenderer { private static native long nCreateTextureLayer(long nativeProxy); private static native void nBuildLayer(long nativeProxy, long node); - private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap); + private static native boolean nCopyLayerInto(long nativeProxy, long layer, Bitmap bitmap); private static native void nPushLayerUpdate(long nativeProxy, long layer); private static native void nCancelLayerUpdate(long nativeProxy, long layer); private static native void nDetachSurfaceTexture(long nativeProxy, long layer); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3fa8c81..25fa349 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1795,6 +1795,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 11 PFLAG2_TEXT_DIRECTION_FLAGS[3] * 1 PFLAG2_TEXT_DIRECTION_FLAGS[4] * 1 1 PFLAG2_TEXT_DIRECTION_FLAGS[5] + * 11 PFLAG2_TEXT_DIRECTION_FLAGS[6] + * 111 PFLAG2_TEXT_DIRECTION_FLAGS[7] * 111 PFLAG2_TEXT_DIRECTION_MASK * 1 PFLAG2_TEXT_DIRECTION_RESOLVED * 1 PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT @@ -1968,6 +1970,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int TEXT_DIRECTION_LOCALE = 5; /** + * Text direction is using "first strong algorithm". The first strong directional character + * determines the paragraph direction. If there is no strong directional character, the + * paragraph direction is LTR. + */ + public static final int TEXT_DIRECTION_FIRST_STRONG_LTR = 6; + + /** + * Text direction is using "first strong algorithm". The first strong directional character + * determines the paragraph direction. If there is no strong directional character, the + * paragraph direction is RTL. + */ + public static final int TEXT_DIRECTION_FIRST_STRONG_RTL = 7; + + /** * Default text direction is inherited */ private static final int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; @@ -2002,7 +2018,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, TEXT_DIRECTION_ANY_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, TEXT_DIRECTION_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, TEXT_DIRECTION_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, - TEXT_DIRECTION_LOCALE << PFLAG2_TEXT_DIRECTION_MASK_SHIFT + TEXT_DIRECTION_LOCALE << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_FIRST_STRONG_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_FIRST_STRONG_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT }; /** @@ -15187,27 +15205,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * This method is called by ViewGroup.drawChild() to have each child view draw itself. - * This draw() method is an implementation detail and is not intended to be overridden or - * to be called from anywhere else other than ViewGroup.drawChild(). + * + * This is where the View specializes rendering behavior based on layer type, + * and hardware acceleration. */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { - boolean usingRenderNodeProperties = mAttachInfo != null && mAttachInfo.mHardwareAccelerated; + final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated(); + /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList. + * + * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't + * HW accelerated, it can't handle drawing RenderNodes. + */ + boolean drawingWithRenderNode = mAttachInfo != null + && mAttachInfo.mHardwareAccelerated + && hardwareAcceleratedCanvas; + boolean more = false; final boolean childHasIdentityMatrix = hasIdentityMatrix(); - final int flags = parent.mGroupFlags; + final int parentFlags = parent.mGroupFlags; - if ((flags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) == ViewGroup.FLAG_CLEAR_TRANSFORMATION) { + if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) { parent.getChildTransformation().clear(); parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION; } Transformation transformToApply = null; boolean concatMatrix = false; - - boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired; - int layerType = getLayerType(); - final boolean hardwareAccelerated = canvas.isHardwareAccelerated(); - + final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired; final Animation a = getAnimation(); if (a != null) { more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired); @@ -15222,8 +15246,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mRenderNode.setAnimationMatrix(null); mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } - if (!usingRenderNodeProperties && - (flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { + if (!drawingWithRenderNode + && (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { final Transformation t = parent.getChildTransformation(); final boolean hasTransform = parent.getChildStaticTransformation(this, t); if (hasTransform) { @@ -15241,7 +15265,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_DRAWN; if (!concatMatrix && - (flags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS | + (parentFlags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS | ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN && canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) && (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) { @@ -15250,81 +15274,61 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED; - if (hardwareAccelerated) { + if (hardwareAcceleratedCanvas) { // Clear INVALIDATED flag to allow invalidation to occur during rendering, but // retain the flag's value temporarily in the mRecreateDisplayList flag - mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) == PFLAG_INVALIDATED; + mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0; mPrivateFlags &= ~PFLAG_INVALIDATED; } RenderNode renderNode = null; Bitmap cache = null; - boolean hasDisplayList = false; - if (!hardwareAccelerated) { - if (layerType != LAYER_TYPE_NONE) { - layerType = LAYER_TYPE_SOFTWARE; - buildDrawingCache(true); - } + int layerType = getLayerType(); + if (layerType == LAYER_TYPE_SOFTWARE + || (!drawingWithRenderNode && layerType != LAYER_TYPE_NONE)) { + layerType = LAYER_TYPE_SOFTWARE; + buildDrawingCache(true); cache = getDrawingCache(true); - } else { - switch (layerType) { - case LAYER_TYPE_SOFTWARE: - if (usingRenderNodeProperties) { - hasDisplayList = canHaveDisplayList(); - } else { - buildDrawingCache(true); - cache = getDrawingCache(true); - } - break; - case LAYER_TYPE_HARDWARE: - if (usingRenderNodeProperties) { - hasDisplayList = canHaveDisplayList(); - } - break; - case LAYER_TYPE_NONE: - // Delay getting the display list until animation-driven alpha values are - // set up and possibly passed on to the view - hasDisplayList = canHaveDisplayList(); - break; - } } - usingRenderNodeProperties &= hasDisplayList; - if (usingRenderNodeProperties) { + + if (drawingWithRenderNode) { + // Delay getting the display list until animation-driven alpha values are + // set up and possibly passed on to the view renderNode = getDisplayList(); if (!renderNode.isValid()) { // Uncommon, but possible. If a view is removed from the hierarchy during the call // to getDisplayList(), the display list will be marked invalid and we should not // try to use it again. renderNode = null; - hasDisplayList = false; - usingRenderNodeProperties = false; + drawingWithRenderNode = false; } } int sx = 0; int sy = 0; - if (!hasDisplayList) { + if (!drawingWithRenderNode) { computeScroll(); sx = mScrollX; sy = mScrollY; } - final boolean hasNoCache = cache == null || hasDisplayList; - final boolean offsetForScroll = cache == null && !hasDisplayList && - layerType != LAYER_TYPE_HARDWARE; + final boolean hasNoCache = cache == null || drawingWithRenderNode; + final boolean offsetForScroll = cache == null + && !drawingWithRenderNode + && layerType != LAYER_TYPE_HARDWARE; int restoreTo = -1; - if (!usingRenderNodeProperties || transformToApply != null) { + if (!drawingWithRenderNode || transformToApply != null) { restoreTo = canvas.save(); } if (offsetForScroll) { canvas.translate(mLeft - sx, mTop - sy); } else { - if (!usingRenderNodeProperties) { + if (!drawingWithRenderNode) { canvas.translate(mLeft, mTop); } if (scalingRequired) { - if (usingRenderNodeProperties) { + if (drawingWithRenderNode) { // TODO: Might not need this if we put everything inside the DL restoreTo = canvas.save(); } @@ -15334,9 +15338,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - float alpha = usingRenderNodeProperties ? 1 : (getAlpha() * getTransitionAlpha()); - if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() || - (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) { + float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha()); + if (transformToApply != null + || alpha < 1 + || !hasIdentityMatrix() + || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) { if (transformToApply != null || !childHasIdentityMatrix) { int transX = 0; int transY = 0; @@ -15348,7 +15354,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (transformToApply != null) { if (concatMatrix) { - if (usingRenderNodeProperties) { + if (drawingWithRenderNode) { renderNode.setAnimationMatrix(transformToApply.getMatrix()); } else { // Undo the scroll translation, apply the transformation matrix, @@ -15367,7 +15373,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - if (!childHasIdentityMatrix && !usingRenderNodeProperties) { + if (!childHasIdentityMatrix && !drawingWithRenderNode) { canvas.translate(-transX, -transY); canvas.concat(getMatrix()); canvas.translate(transX, transY); @@ -15375,8 +15381,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } // Deal with alpha if it is or used to be <1 - if (alpha < 1 || - (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) { + if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) { if (alpha < 1) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA; } else { @@ -15387,17 +15392,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final int multipliedAlpha = (int) (255 * alpha); if (!onSetAlpha(multipliedAlpha)) { int layerFlags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; - if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 || - layerType != LAYER_TYPE_NONE) { + if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 + || layerType != LAYER_TYPE_NONE) { layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG; } - if (usingRenderNodeProperties) { + if (drawingWithRenderNode) { renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha()); - } else if (layerType == LAYER_TYPE_NONE) { - final int scrollX = hasDisplayList ? 0 : sx; - final int scrollY = hasDisplayList ? 0 : sy; - canvas.saveLayerAlpha(scrollX, scrollY, - scrollX + (mRight - mLeft), scrollY + (mBottom - mTop), + } else if (layerType == LAYER_TYPE_NONE) { + canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(), multipliedAlpha, layerFlags); } } else { @@ -15411,15 +15413,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags &= ~PFLAG_ALPHA_SET; } - if (!usingRenderNodeProperties) { + if (!drawingWithRenderNode) { // apply clips directly, since RenderNode won't do it for this draw - if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN - && cache == null) { + if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) { if (offsetForScroll) { - canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop)); + canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight()); } else { if (!scalingRequired || cache == null) { - canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop); + canvas.clipRect(0, 0, getWidth(), getHeight()); } else { canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight()); } @@ -15432,22 +15433,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - - - if (!usingRenderNodeProperties && hasDisplayList) { - renderNode = getDisplayList(); - if (!renderNode.isValid()) { - // Uncommon, but possible. If a view is removed from the hierarchy during the call - // to getDisplayList(), the display list will be marked invalid and we should not - // try to use it again. - renderNode = null; - hasDisplayList = false; - } - } - if (hasNoCache) { boolean layerRendered = false; - if (layerType == LAYER_TYPE_HARDWARE && !usingRenderNodeProperties) { + if (layerType == LAYER_TYPE_HARDWARE && !drawingWithRenderNode) { final HardwareLayer layer = getHardwareLayer(); if (layer != null && layer.isValid()) { int restoreAlpha = mLayerPaint.getAlpha(); @@ -15456,16 +15444,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mLayerPaint.setAlpha(restoreAlpha); layerRendered = true; } else { - final int scrollX = hasDisplayList ? 0 : sx; - final int scrollY = hasDisplayList ? 0 : sy; - canvas.saveLayer(scrollX, scrollY, - scrollX + mRight - mLeft, scrollY + mBottom - mTop, mLayerPaint, - Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); + canvas.saveLayer(sx, sy, sx + getWidth(), sy + getHeight(), mLayerPaint); } } if (!layerRendered) { - if (!hasDisplayList) { + if (!drawingWithRenderNode) { // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; @@ -15475,7 +15459,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } else { mPrivateFlags &= ~PFLAG_DIRTY_MASK; - ((DisplayListCanvas) canvas).drawRenderNode(renderNode, flags); + ((DisplayListCanvas) canvas).drawRenderNode(renderNode, parentFlags); } } } else if (cache != null) { @@ -15504,13 +15488,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (a != null && !more) { - if (!hardwareAccelerated && !a.getFillAfter()) { + if (!hardwareAcceleratedCanvas && !a.getFillAfter()) { onSetAlpha(255); } parent.finishAnimatingView(this, a); } - if (more && hardwareAccelerated) { + if (more && hardwareAcceleratedCanvas) { if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) { // alpha animations should cause the child to recreate its display list invalidate(true); @@ -19636,11 +19620,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return the defined text direction. It can be one of: * * {@link #TEXT_DIRECTION_INHERIT}, - * {@link #TEXT_DIRECTION_FIRST_STRONG} + * {@link #TEXT_DIRECTION_FIRST_STRONG}, * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE} + * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL} * * @attr ref android.R.styleable#View_textDirection * @@ -19652,7 +19638,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE") + @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_LTR, to = "FIRST_STRONG_LTR"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_RTL, to = "FIRST_STRONG_RTL") }) public int getRawTextDirection() { return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_MASK) >> PFLAG2_TEXT_DIRECTION_MASK_SHIFT; @@ -19664,11 +19652,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param textDirection the direction to set. Should be one of: * * {@link #TEXT_DIRECTION_INHERIT}, - * {@link #TEXT_DIRECTION_FIRST_STRONG} + * {@link #TEXT_DIRECTION_FIRST_STRONG}, * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * {@link #TEXT_DIRECTION_LOCALE} + * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL}, * * Resolution will be done if the value is set to TEXT_DIRECTION_INHERIT. The resolution * proceeds up the parent chain of the view to get the value. If there is no parent, then it will @@ -19698,11 +19688,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @return the resolved text direction. Returns one of: * - * {@link #TEXT_DIRECTION_FIRST_STRONG} + * {@link #TEXT_DIRECTION_FIRST_STRONG}, * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE} + * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR}, + * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL} * * @attr ref android.R.styleable#View_textDirection */ @@ -19712,7 +19704,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE") + @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_LTR, to = "FIRST_STRONG_LTR"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_RTL, to = "FIRST_STRONG_RTL") }) public int getTextDirection() { return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT; @@ -19771,6 +19765,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case TEXT_DIRECTION_LTR: case TEXT_DIRECTION_RTL: case TEXT_DIRECTION_LOCALE: + case TEXT_DIRECTION_FIRST_STRONG_LTR: + case TEXT_DIRECTION_FIRST_STRONG_RTL: mPrivateFlags2 |= (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT); break; @@ -19784,6 +19780,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case TEXT_DIRECTION_LTR: case TEXT_DIRECTION_RTL: case TEXT_DIRECTION_LOCALE: + case TEXT_DIRECTION_FIRST_STRONG_LTR: + case TEXT_DIRECTION_FIRST_STRONG_RTL: // Resolved direction is the same as text direction mPrivateFlags2 |= (textDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT); break; diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 9d0d5ff..49a72ce 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -595,6 +595,8 @@ public abstract class Window { title="Panel"; } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) { title="SubPanel"; + } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) { + title="AboveSubPanel"; } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) { title="AtchDlg"; } else { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 54d78f3..e983910 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -164,13 +164,14 @@ public interface WindowManager extends ViewManager { * be used by applications, and a special permission is required * to use them. * </ul> - * + * * @see #TYPE_BASE_APPLICATION * @see #TYPE_APPLICATION * @see #TYPE_APPLICATION_STARTING * @see #TYPE_APPLICATION_PANEL * @see #TYPE_APPLICATION_MEDIA * @see #TYPE_APPLICATION_SUB_PANEL + * @see #TYPE_APPLICATION_ABOVE_SUB_PANEL * @see #TYPE_APPLICATION_ATTACHED_DIALOG * @see #TYPE_STATUS_BAR * @see #TYPE_SEARCH_BAR @@ -193,6 +194,7 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL, to = "TYPE_APPLICATION_PANEL"), @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA, to = "TYPE_APPLICATION_MEDIA"), @ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL, to = "TYPE_APPLICATION_SUB_PANEL"), + @ViewDebug.IntToString(from = TYPE_APPLICATION_ABOVE_SUB_PANEL, to = "TYPE_APPLICATION_ABOVE_SUB_PANEL"), @ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG, to = "TYPE_APPLICATION_ATTACHED_DIALOG"), @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY, to = "TYPE_APPLICATION_MEDIA_OVERLAY"), @ViewDebug.IntToString(from = TYPE_STATUS_BAR, to = "TYPE_STATUS_BAR"), @@ -260,40 +262,40 @@ public interface WindowManager extends ViewManager { * End of types of application windows. */ public static final int LAST_APPLICATION_WINDOW = 99; - + /** * Start of types of sub-windows. The {@link #token} of these windows * must be set to the window they are attached to. These types of * windows are kept next to their attached window in Z-order, and their * coordinate space is relative to their attached window. */ - public static final int FIRST_SUB_WINDOW = 1000; - + public static final int FIRST_SUB_WINDOW = 1000; + /** * Window type: a panel on top of an application window. These windows * appear on top of their attached window. */ - public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; - + public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; + /** * Window type: window for showing media (such as video). These windows * are displayed behind their attached window. */ - public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1; - + public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1; + /** * Window type: a sub-panel on top of an application window. These * windows are displayed on top their attached window and any * {@link #TYPE_APPLICATION_PANEL} panels. */ - public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2; + public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2; /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout * of the window happens as that of a top-level window, <em>not</em> * as a child of its container. */ - public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3; - + public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3; + /** * Window type: window for showing overlays on top of media windows. * These windows are displayed between TYPE_APPLICATION_MEDIA and the @@ -301,19 +303,26 @@ public interface WindowManager extends ViewManager { * is a big ugly hack so: * @hide */ - public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4; - + public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4; + + /** + * Window type: a above sub-panel on top of an application window and it's + * sub-panel windows. These windows are displayed on top of their attached window + * and any {@link #TYPE_APPLICATION_SUB_PANEL} panels. + */ + public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5; + /** * End of types of sub-windows. */ - public static final int LAST_SUB_WINDOW = 1999; - + public static final int LAST_SUB_WINDOW = 1999; + /** * Start of system-specific window types. These are not normally * created by applications. */ public static final int FIRST_SYSTEM_WINDOW = 2000; - + /** * Window type: the status bar. There can be only one status bar * window; it is placed at the top of the screen, and all other diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 29073be..1be05f3 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3370,7 +3370,7 @@ public class Editor { // Parent's (TextView) previous position in window private int mLastParentX, mLastParentY; // Previous text character offset - private int mPreviousOffset = -1; + protected int mPreviousOffset = -1; // Previous text character offset private boolean mPositionHasChanged = true; // Minimum touch target size for handles @@ -3830,8 +3830,6 @@ public class Editor { } private class SelectionStartHandleView extends HandleView { - // The previous offset this handle was at. - private int mPrevOffset; // Indicates whether the cursor is making adjustments within a word. private boolean mInWord = false; // Offset to track difference between touch and word boundary. @@ -3879,7 +3877,7 @@ public class Editor { int end = getWordEnd(offset, true); int start = getWordStart(offset); - if (offset < mPrevOffset) { + if (offset < mPreviousOffset) { // User is increasing the selection. if (!mInWord || currLine < mPrevLine) { // We're not in a word, or we're on a different line so we'll expand by @@ -3888,21 +3886,19 @@ public class Editor { if (offset <= end - offsetToWord || currLine < mPrevLine) { offset = start; } else { - offset = mPrevOffset; + offset = mPreviousOffset; } } - mPrevOffset = offset; mTouchWordOffset = Math.max(trueOffset - offset, 0); mInWord = !isStartBoundary(offset); positionCursor = true; - } else if (offset - mTouchWordOffset > mPrevOffset) { + } else if (offset - mTouchWordOffset > mPreviousOffset) { // User is shrinking the selection. if (currLine > mPrevLine) { // We're on a different line, so we'll snap to word boundaries. offset = end; } offset -= mTouchWordOffset; - mPrevOffset = offset; mInWord = !isEndBoundary(offset); positionCursor = true; } @@ -3936,8 +3932,6 @@ public class Editor { } private class SelectionEndHandleView extends HandleView { - // The previous offset this handle was at. - private int mPrevOffset; // Indicates whether the cursor is making adjustments within a word. private boolean mInWord = false; // Offset to track difference between touch and word boundary. @@ -3986,7 +3980,7 @@ public class Editor { int end = getWordEnd(offset, true); int start = getWordStart(offset); - if (offset > mPrevOffset) { + if (offset > mPreviousOffset) { // User is increasing the selection. if (!mInWord || currLine > mPrevLine) { // We're not in a word, or we're on a different line so we'll expand by @@ -3995,21 +3989,19 @@ public class Editor { if (offset >= start + midPoint || currLine > mPrevLine) { offset = end; } else { - offset = mPrevOffset; + offset = mPreviousOffset; } } - mPrevOffset = offset; mTouchWordOffset = Math.max(offset - trueOffset, 0); mInWord = !isEndBoundary(offset); positionCursor = true; - } else if (offset + mTouchWordOffset < mPrevOffset) { + } else if (offset + mTouchWordOffset < mPreviousOffset) { // User is shrinking the selection. if (currLine > mPrevLine) { // We're on a different line, so we'll snap to word boundaries. offset = getWordStart(offset); } offset += mTouchWordOffset; - mPrevOffset = offset; positionCursor = true; mInWord = !isStartBoundary(offset); } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 8792323..c5b5c84 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -860,19 +860,22 @@ public class PopupWindow { } /** - * Set the layout type for this window. Should be one of the TYPE constants defined in - * {@link WindowManager.LayoutParams}. + * Set the layout type for this window. This value will be passed through to + * {@link WindowManager.LayoutParams#type} therefore the value should match any value + * {@link WindowManager.LayoutParams#type} accepts. * * @param layoutType Layout type for this window. - * @hide + * + * @see WindowManager.LayoutParams#type */ public void setWindowLayoutType(int layoutType) { mWindowLayoutType = layoutType; } /** - * @return The layout type for this window. - * @hide + * Returns the layout type for this window. + * + * @see #setWindowLayoutType(int) */ public int getWindowLayoutType() { return mWindowLayoutType; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9bbf375..b44a886 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9275,6 +9275,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return TextDirectionHeuristics.RTL; case TEXT_DIRECTION_LOCALE: return TextDirectionHeuristics.LOCALE; + case TEXT_DIRECTION_FIRST_STRONG_LTR: + return TextDirectionHeuristics.FIRSTSTRONG_LTR; + case TEXT_DIRECTION_FIRST_STRONG_RTL: + return TextDirectionHeuristics.FIRSTSTRONG_RTL; } } |
