diff options
Diffstat (limited to 'core/java/android')
36 files changed, 1227 insertions, 1215 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index e8d08b8..0423478 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -466,8 +466,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM String resultData = data.readString(); Bundle resultExtras = data.readBundle(); boolean resultAbort = data.readInt() != 0; + int intentFlags = data.readInt(); if (who != null) { - finishReceiver(who, resultCode, resultData, resultExtras, resultAbort); + finishReceiver(who, resultCode, resultData, resultExtras, resultAbort, intentFlags); } reply.writeNoException(); return true; @@ -2848,7 +2849,8 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException + public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, + boolean abortBroadcast, int flags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2858,6 +2860,7 @@ class ActivityManagerProxy implements IActivityManager data.writeString(resultData); data.writeBundle(map); data.writeInt(abortBroadcast ? 1 : 0); + data.writeInt(flags); mRemote.transact(FINISH_RECEIVER_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); reply.readException(); data.recycle(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 404876f..6f0bc3a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -367,7 +367,7 @@ public final class ActivityThread { public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras, boolean ordered, boolean sticky, IBinder token, int sendingUser) { super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, - token, sendingUser); + token, sendingUser, intent.getFlags()); this.intent = intent; } @@ -1079,8 +1079,7 @@ public final class ActivityThread { WindowManagerGlobal.getInstance().dumpGfxInfo(fd); } - @Override - public void dumpDbInfo(FileDescriptor fd, String[] args) { + private void dumpDatabaseInfo(FileDescriptor fd, String[] args) { PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd)); PrintWriterPrinter printer = new PrintWriterPrinter(pw); SQLiteDebug.dump(printer, args); @@ -1088,6 +1087,22 @@ public final class ActivityThread { } @Override + public void dumpDbInfo(final FileDescriptor fd, final String[] args) { + if (mSystemThread) { + // Ensure this invocation is asynchronous to prevent + // writer waiting due to buffer cannot be consumed. + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + dumpDatabaseInfo(fd, args); + } + }); + } else { + dumpDatabaseInfo(fd, args); + } + } + + @Override public void unstableProviderDied(IBinder provider) { sendMessage(H.UNSTABLE_PROVIDER_DIED, provider); } diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java index f79d32b..3fbbdff 100644 --- a/core/java/android/app/DatePickerDialog.java +++ b/core/java/android/app/DatePickerDialog.java @@ -131,6 +131,9 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, switch (which) { case BUTTON_POSITIVE: if (mDateSetListener != null) { + // Clearing focus forces the dialog to commit any pending + // changes, e.g. typed text in a NumberPicker. + mDatePicker.clearFocus(); mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(), mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index e505d69..d9fc2cf 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -106,7 +106,8 @@ public interface IActivityManager extends IInterface { String resultData, Bundle map, String requiredPermission, int appOp, boolean serialized, boolean sticky, int userId) throws RemoteException; public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) throws RemoteException; - public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException; + public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, + boolean abortBroadcast, int flags) throws RemoteException; public void attachApplication(IApplicationThread app) throws RemoteException; public void activityResumed(IBinder token) throws RemoteException; public void activityIdle(IBinder token, Configuration config, diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index ece2a33..b01f87e 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -801,7 +801,7 @@ public final class LoadedApk { if (extras != null) { extras.setAllowFds(false); } - mgr.finishReceiver(this, resultCode, data, extras, false); + mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags()); } catch (RemoteException e) { Slog.w(ActivityThread.TAG, "Couldn't finish broadcast to unregistered receiver"); } @@ -826,8 +826,8 @@ public final class LoadedApk { public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras, boolean ordered, boolean sticky, int sendingUser) { super(resultCode, resultData, resultExtras, - mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, - ordered, sticky, mIIntentReceiver.asBinder(), sendingUser); + mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered, + sticky, mIIntentReceiver.asBinder(), sendingUser, intent.getFlags()); mCurIntent = intent; mOrdered = ordered; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c5c6d06..69872ff 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -469,20 +469,20 @@ public class DevicePolicyManager { = "android.app.action.SET_NEW_PASSWORD"; /** - * Flag used by {@link #addCrossProfileIntentFilter} to allow access - * <em>from</em> a managed profile <em>to</em> its parent. That is, any - * matching activities in the parent profile are included in the - * disambiguation list shown when an app in the managed profile calls - * {@link Activity#startActivity(Intent)}. + * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in + * the parent profile to access intents sent from the managed profile. + * That is, when an app in the managed profile calls + * {@link Activity#startActivity(Intent)}, the intent can be resolved by a + * matching activity in the parent profile. */ public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001; /** - * Flag used by {@link #addCrossProfileIntentFilter} to allow access - * <em>from</em> a parent <em>to</em> its managed profile. That is, any - * matching activities in the managed profile are included in the - * disambiguation list shown when an app in the parent profile calls - * {@link Activity#startActivity(Intent)}. + * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in + * the managed profile to access intents sent from the parent profile. + * That is, when an app in the parent profile calls + * {@link Activity#startActivity(Intent)}, the intent can be resolved by a + * matching activity in the managed profile. */ public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002; diff --git a/core/java/android/app/backup/BackupHelperDispatcher.java b/core/java/android/app/backup/BackupHelperDispatcher.java index 5466db5..6811532 100644 --- a/core/java/android/app/backup/BackupHelperDispatcher.java +++ b/core/java/android/app/backup/BackupHelperDispatcher.java @@ -50,7 +50,6 @@ public class BackupHelperDispatcher { Header header = new Header(); TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone(); FileDescriptor oldStateFD = null; - FileDescriptor newStateFD = newState.getFileDescriptor(); if (oldState != null) { oldStateFD = oldState.getFileDescriptor(); diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 9a32fdf..af74e73 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -238,6 +238,7 @@ public abstract class BroadcastReceiver { final boolean mInitialStickyHint; final IBinder mToken; final int mSendingUser; + final int mFlags; int mResultCode; String mResultData; @@ -246,8 +247,8 @@ public abstract class BroadcastReceiver { boolean mFinished; /** @hide */ - public PendingResult(int resultCode, String resultData, Bundle resultExtras, - int type, boolean ordered, boolean sticky, IBinder token, int userId) { + public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type, + boolean ordered, boolean sticky, IBinder token, int userId, int flags) { mResultCode = resultCode; mResultData = resultData; mResultExtras = resultExtras; @@ -256,6 +257,7 @@ public abstract class BroadcastReceiver { mInitialStickyHint = sticky; mToken = token; mSendingUser = userId; + mFlags = flags; } /** @@ -417,11 +419,11 @@ public abstract class BroadcastReceiver { } if (mOrderedHint) { am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras, - mAbortBroadcast); + mAbortBroadcast, mFlags); } else { // This broadcast was sent to a component; it is not ordered, // but we still need to tell the activity manager we are done. - am.finishReceiver(mToken, 0, null, null, false); + am.finishReceiver(mToken, 0, null, null, false, mFlags); } } catch (RemoteException ex) { } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 5ebbf16..af7ec5e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3949,10 +3949,12 @@ public class Intent implements Parcelable, Cloneable { * is: * * <pre class="prettyprint"> - * android-app://{package_id}/{scheme}/{host}/{path}{#Intent;...}</pre> + * android-app://{package_id}[/{scheme}[/{host}[/{path}]]][#Intent;{...}]</pre> * - * <p>In this scheme, only the <code>pacakge_id</code> is required, and all - * other components can be included as desired. Note that this can not be + * <p>In this scheme, only the <code>package_id</code> is required. If you include a host, + * you must also include a scheme; including a path also requires both a host and a scheme. + * The final #Intent; fragment can be used without a scheme, host, or path. + * Note that this can not be * used with intents that have a {@link #setSelector}, since the base intent * will always have an explicit package name.</p> * diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index c5c8d75..c931c01 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2696,8 +2696,8 @@ public class Resources { private ColorStateList loadColorStateListForCookie(TypedValue value, int id, Theme theme) { if (value.string == null) { - throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" - + Integer.toHexString(id) + ") is not a ColorStateList: " + value); + throw new UnsupportedOperationException( + "Can't convert to color state list: type=0x" + value.type); } final String file = value.string.toString(); diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 06c701a..f15b6b9 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -18,6 +18,7 @@ package android.content.res; import android.annotation.Nullable; import android.graphics.drawable.Drawable; +import android.os.StrictMode; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -73,7 +74,9 @@ public class TypedArray { /*package*/ TypedValue mValue = new TypedValue(); /** - * Return the number of values in this array. + * Returns the number of values in this array. + * + * @throws RuntimeException if the TypedArray has already been recycled. */ public int length() { if (mRecycled) { @@ -85,6 +88,8 @@ public class TypedArray { /** * Return the number of indices in the array that actually have data. + * + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getIndexCount() { if (mRecycled) { @@ -95,13 +100,14 @@ public class TypedArray { } /** - * Return an index in the array that has data. + * Returns an index in the array that has data. * * @param at The index you would like to returned, ranging from 0 to - * {@link #getIndexCount()}. + * {@link #getIndexCount()}. * * @return The index at the given offset, which can be used with - * {@link #getValue} and related APIs. + * {@link #getValue} and related APIs. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getIndex(int at) { if (mRecycled) { @@ -112,7 +118,9 @@ public class TypedArray { } /** - * Return the Resources object this array was loaded from. + * Returns the Resources object this array was loaded from. + * + * @throws RuntimeException if the TypedArray has already been recycled. */ public Resources getResources() { if (mRecycled) { @@ -123,12 +131,17 @@ public class TypedArray { } /** - * Retrieve the styled string value for the attribute at <var>index</var>. + * Retrieves the styled string value for the attribute at <var>index</var>. + * <p> + * If the attribute is not a string, this method will attempt to coerce + * it to a string. * * @param index Index of attribute to retrieve. * - * @return CharSequence holding string data. May be styled. Returns - * null if the attribute is not defined. + * @return CharSequence holding string data. May be styled. Returns + * {@code null} if the attribute is not defined or could not be + * coerced to a string. + * @throws RuntimeException if the TypedArray has already been recycled. */ public CharSequence getText(int index) { if (mRecycled) { @@ -144,23 +157,28 @@ public class TypedArray { return loadStringValueAt(index); } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to string: " + v); + StrictMode.noteResourceMismatch(v); return v.coerceToString(); } - Log.w(Resources.TAG, "getString of bad type: 0x" - + Integer.toHexString(type)); - return null; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type)); } /** - * Retrieve the string value for the attribute at <var>index</var>. + * Retrieves the string value for the attribute at <var>index</var>. + * <p> + * If the attribute is not a string, this method will attempt to coerce + * it to a string. * * @param index Index of attribute to retrieve. * - * @return String holding string data. Any styling information is - * removed. Returns null if the attribute is not defined. + * @return String holding string data. Any styling information is removed. + * Returns {@code null} if the attribute is not defined or could + * not be coerced to a string. + * @throws RuntimeException if the TypedArray has already been recycled. */ public String getString(int index) { if (mRecycled) { @@ -176,19 +194,19 @@ public class TypedArray { return loadStringValueAt(index).toString(); } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to string: " + v); - CharSequence cs = v.coerceToString(); + StrictMode.noteResourceMismatch(v); + final CharSequence cs = v.coerceToString(); return cs != null ? cs.toString() : null; } - Log.w(Resources.TAG, "getString of bad type: 0x" - + Integer.toHexString(type)); - return null; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type)); } /** - * Retrieve the string value for the attribute at <var>index</var>, but + * Retrieves the string value for the attribute at <var>index</var>, but * only if that string comes from an immediate value in an XML file. That * is, this does not allow references to string resources, string * attributes, or conversions from other types. As such, this method @@ -197,9 +215,10 @@ public class TypedArray { * * @param index Index of attribute to retrieve. * - * @return String holding string data. Any styling information is - * removed. Returns null if the attribute is not defined or is not - * an immediate string value. + * @return String holding string data. Any styling information is removed. + * Returns {@code null} if the attribute is not defined or is not + * an immediate string value. + * @throws RuntimeException if the TypedArray has already been recycled. */ public String getNonResourceString(int index) { if (mRecycled) { @@ -220,16 +239,17 @@ public class TypedArray { } /** - * @hide - * Retrieve the string value for the attribute at <var>index</var> that is + * Retrieves the string value for the attribute at <var>index</var> that is * not allowed to change with the given configurations. * * @param index Index of attribute to retrieve. * @param allowedChangingConfigs Bit mask of configurations from - * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change. + * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change. * - * @return String holding string data. Any styling information is - * removed. Returns null if the attribute is not defined. + * @return String holding string data. Any styling information is removed. + * Returns {@code null} if the attribute is not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @hide */ public String getNonConfigurationString(int index, int allowedChangingConfigs) { if (mRecycled) { @@ -248,24 +268,33 @@ public class TypedArray { return loadStringValueAt(index).toString(); } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to string: " + v); - CharSequence cs = v.coerceToString(); + StrictMode.noteResourceMismatch(v); + final CharSequence cs = v.coerceToString(); return cs != null ? cs.toString() : null; } - Log.w(Resources.TAG, "getString of bad type: 0x" - + Integer.toHexString(type)); - return null; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getNonConfigurationString of bad type: 0x" + + Integer.toHexString(type)); } /** * Retrieve the boolean value for the attribute at <var>index</var>. + * <p> + * If the attribute is an integer value, this method will return whether + * it is equal to zero. If the attribute is not a boolean or integer value, + * this method will attempt to coerce it to an integer using + * {@link Integer#decode(String)} and return whether it is equal to zero. * * @param index Index of attribute to retrieve. - * @param defValue Value to return if the attribute is not defined. + * @param defValue Value to return if the attribute is not defined or + * cannot be coerced to an integer. * - * @return Attribute boolean value, or defValue if not defined. + * @return Boolean value of the attribute, or defValue if the attribute was + * not defined or could not be coerced to an integer. + * @throws RuntimeException if the TypedArray has already been recycled. */ public boolean getBoolean(int index, boolean defValue) { if (mRecycled) { @@ -278,28 +307,33 @@ public class TypedArray { if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA] != 0; } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to boolean: " + v); - return XmlUtils.convertValueToBoolean( - v.coerceToString(), defValue); + StrictMode.noteResourceMismatch(v); + return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue); } - Log.w(Resources.TAG, "getBoolean of bad type: 0x" - + Integer.toHexString(type)); - return defValue; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type)); } /** * Retrieve the integer value for the attribute at <var>index</var>. + * <p> + * If the attribute is not an integer, this method will attempt to coerce + * it to an integer using {@link Integer#decode(String)}. * * @param index Index of attribute to retrieve. - * @param defValue Value to return if the attribute is not defined. + * @param defValue Value to return if the attribute is not defined or + * cannot be coerced to an integer. * - * @return Attribute int value, or defValue if not defined. + * @return Integer value of the attribute, or defValue if the attribute was + * not defined or could not be coerced to an integer. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getInt(int index, int defValue) { if (mRecycled) { @@ -312,27 +346,31 @@ public class TypedArray { if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA]; } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to int: " + v); - return XmlUtils.convertValueToInt( - v.coerceToString(), defValue); + StrictMode.noteResourceMismatch(v); + return XmlUtils.convertValueToInt(v.coerceToString(), defValue); } - Log.w(Resources.TAG, "getInt of bad type: 0x" - + Integer.toHexString(type)); - return defValue; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getInt of bad type: 0x" + Integer.toHexString(type)); } /** * Retrieve the float value for the attribute at <var>index</var>. + * <p> + * If the attribute is not a float or an integer, this method will attempt + * to coerce it to a float using {@link Float#parseFloat(String)}. * * @param index Index of attribute to retrieve. * - * @return Attribute float value, or defValue if not defined.. + * @return Attribute float value, or defValue if the attribute was + * not defined or could not be coerced to a float. + * @throws RuntimeException if the TypedArray has already been recycled. */ public float getFloat(int index, float defValue) { if (mRecycled) { @@ -347,21 +385,21 @@ public class TypedArray { } else if (type == TypedValue.TYPE_FLOAT) { return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]); } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA]; } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to float: " + v); - CharSequence str = v.coerceToString(); + final CharSequence str = v.coerceToString(); if (str != null) { + StrictMode.noteResourceMismatch(v); return Float.parseFloat(str.toString()); } } - Log.w(Resources.TAG, "getFloat of bad type: 0x" - + Integer.toHexString(type)); - return defValue; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type)); } /** @@ -369,12 +407,18 @@ public class TypedArray { * the attribute references a color resource holding a complex * {@link android.content.res.ColorStateList}, then the default color from * the set is returned. + * <p> + * This method will throw an exception if the attribute is defined but is + * not an integer color or color state list. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute color value, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer color or color state list. */ public int getColor(int index, int defValue) { if (mRecycled) { @@ -387,7 +431,7 @@ public class TypedArray { if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA]; } else if (type == TypedValue.TYPE_STRING) { final TypedValue value = mValue; @@ -398,7 +442,8 @@ public class TypedArray { } return defValue; } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to color: type=0x" @@ -408,12 +453,22 @@ public class TypedArray { /** * Retrieve the ColorStateList for the attribute at <var>index</var>. * The value may be either a single solid color or a reference to - * a color or complex {@link android.content.res.ColorStateList} description. + * a color or complex {@link android.content.res.ColorStateList} + * description. + * <p> + * This method will return {@code null} if the attribute is not defined or + * is not an integer color or color state list. * * @param index Index of attribute to retrieve. * - * @return ColorStateList for the attribute, or null if not defined. + * @return ColorStateList for the attribute, or {@code null} if not + * defined. + * @throws RuntimeException if the attribute if the TypedArray has already + * been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer color or color state list. */ + @Nullable public ColorStateList getColorStateList(int index) { if (mRecycled) { throw new RuntimeException("Cannot make calls to a recycled instance!"); @@ -422,7 +477,8 @@ public class TypedArray { final TypedValue value = mValue; if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } return mResources.loadColorStateList(value, value.resourceId, mTheme); } @@ -431,12 +487,18 @@ public class TypedArray { /** * Retrieve the integer value for the attribute at <var>index</var>. + * <p> + * Unlike {@link #getInt(int, int)}, this method will throw an exception if + * the attribute is defined but is not an integer. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute integer value, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer. */ public int getInteger(int index, int defValue) { if (mRecycled) { @@ -449,10 +511,11 @@ public class TypedArray { if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA]; } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to integer: type=0x" @@ -460,17 +523,23 @@ public class TypedArray { } /** - * Retrieve a dimensional unit attribute at <var>index</var>. Unit + * Retrieve a dimensional unit attribute at <var>index</var>. Unit * conversions are based on the current {@link DisplayMetrics} * associated with the resources this {@link TypedArray} object * came from. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a dimension. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute dimension value multiplied by the appropriate - * metric, or defValue if not defined. + * metric, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer. * * @see #getDimensionPixelOffset * @see #getDimensionPixelSize @@ -487,9 +556,10 @@ public class TypedArray { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimension( - data[index+AssetManager.STYLE_DATA], mMetrics); + data[index + AssetManager.STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -502,13 +572,19 @@ public class TypedArray { * {@link #getDimension}, except the returned value is converted to * integer pixels for you. An offset conversion involves simply * truncating the base value to an integer. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a dimension. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute dimension value multiplied by the appropriate - * metric and truncated to integer pixels, or defValue if not defined. + * metric and truncated to integer pixels, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer. * * @see #getDimension * @see #getDimensionPixelSize @@ -525,9 +601,10 @@ public class TypedArray { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimensionPixelOffset( - data[index+AssetManager.STYLE_DATA], mMetrics); + data[index + AssetManager.STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -541,13 +618,19 @@ public class TypedArray { * integer pixels for use as a size. A size conversion involves * rounding the base value, and ensuring that a non-zero base value * is at least one pixel in size. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a dimension. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute dimension value multiplied by the appropriate - * metric and truncated to integer pixels, or defValue if not defined. + * metric and truncated to integer pixels, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not a dimension. * * @see #getDimension * @see #getDimensionPixelOffset @@ -566,7 +649,8 @@ public class TypedArray { return TypedValue.complexToDimensionPixelSize( data[index+AssetManager.STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -578,12 +662,18 @@ public class TypedArray { * {@link android.view.ViewGroup}'s layout_width and layout_height * attributes. This is only here for performance reasons; applications * should use {@link #getDimensionPixelSize}. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a dimension or integer (enum). * * @param index Index of the attribute to retrieve. * @param name Textual name of attribute for error reporting. * * @return Attribute dimension value multiplied by the appropriate - * metric and truncated to integer pixels. + * metric and truncated to integer pixels. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not a dimension or integer (enum). */ public int getLayoutDimension(int index, String name) { if (mRecycled) { @@ -600,10 +690,11 @@ public class TypedArray { return TypedValue.complexToDimensionPixelSize( data[index+AssetManager.STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } - throw new RuntimeException(getPositionDescription() + throw new UnsupportedOperationException(getPositionDescription() + ": You must supply a " + name + " attribute."); } @@ -615,10 +706,11 @@ public class TypedArray { * * @param index Index of the attribute to retrieve. * @param defValue The default value to return if this attribute is not - * default or contains the wrong type of data. + * default or contains the wrong type of data. * * @return Attribute dimension value multiplied by the appropriate - * metric and truncated to integer pixels. + * metric and truncated to integer pixels. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getLayoutDimension(int index, int defValue) { if (mRecycled) { @@ -633,14 +725,14 @@ public class TypedArray { return data[index+AssetManager.STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + data[index + AssetManager.STYLE_DATA], mMetrics); } return defValue; } /** - * Retrieve a fractional unit attribute at <var>index</var>. + * Retrieves a fractional unit attribute at <var>index</var>. * * @param index Index of attribute to retrieve. * @param base The base value of this fraction. In other words, a @@ -652,7 +744,10 @@ public class TypedArray { * not a resource. * * @return Attribute fractional value multiplied by the appropriate - * base value, or defValue if not defined. + * base value, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not a fraction. */ public float getFraction(int index, int base, int pbase, float defValue) { if (mRecycled) { @@ -668,7 +763,8 @@ public class TypedArray { return TypedValue.complexToFraction( data[index+AssetManager.STYLE_DATA], base, pbase); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to fraction: type=0x" @@ -676,7 +772,7 @@ public class TypedArray { } /** - * Retrieve the resource identifier for the attribute at + * Retrieves the resource identifier for the attribute at * <var>index</var>. Note that attribute resource as resolved when * the overall {@link TypedArray} object is retrieved. As a * result, this function will return the resource identifier of the @@ -688,6 +784,7 @@ public class TypedArray { * not a resource. * * @return Attribute resource identifier, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getResourceId(int index, int defValue) { if (mRecycled) { @@ -706,13 +803,15 @@ public class TypedArray { } /** - * Retrieve the theme attribute resource identifier for the attribute at + * Retrieves the theme attribute resource identifier for the attribute at * <var>index</var>. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or not a - * resource. + * resource. + * * @return Theme attribute resource identifier, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. * @hide */ public int getThemeAttributeId(int index, int defValue) { @@ -730,10 +829,16 @@ public class TypedArray { /** * Retrieve the Drawable for the attribute at <var>index</var>. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a color or drawable resource. * * @param index Index of attribute to retrieve. * - * @return Drawable for the attribute, or null if not defined. + * @return Drawable for the attribute, or {@code null} if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not a color or drawable resource. */ @Nullable public Drawable getDrawable(int index) { @@ -744,7 +849,8 @@ public class TypedArray { final TypedValue value = mValue; if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } return mResources.loadDrawable(value, value.resourceId, mTheme); } @@ -756,10 +862,15 @@ public class TypedArray { * This gets the resource ID of the selected attribute, and uses * {@link Resources#getTextArray Resources.getTextArray} of the owning * Resources object to retrieve its String[]. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a text array resource. * * @param index Index of attribute to retrieve. * - * @return CharSequence[] for the attribute, or null if not defined. + * @return CharSequence[] for the attribute, or {@code null} if not + * defined. + * @throws RuntimeException if the TypedArray has already been recycled. */ public CharSequence[] getTextArray(int index) { if (mRecycled) { @@ -780,7 +891,8 @@ public class TypedArray { * @param outValue TypedValue object in which to place the attribute's * data. * - * @return Returns true if the value was retrieved, else false. + * @return {@code true} if the value was retrieved, false otherwise. + * @throws RuntimeException if the TypedArray has already been recycled. */ public boolean getValue(int index, TypedValue outValue) { if (mRecycled) { @@ -794,7 +906,9 @@ public class TypedArray { * Returns the type of attribute at the specified index. * * @param index Index of attribute whose type to retrieve. + * * @return Attribute type. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getType(int index) { if (mRecycled) { @@ -814,6 +928,7 @@ public class TypedArray { * @param index Index of attribute to retrieve. * * @return True if the attribute has a value, false otherwise. + * @throws RuntimeException if the TypedArray has already been recycled. */ public boolean hasValue(int index) { if (mRecycled) { @@ -834,6 +949,7 @@ public class TypedArray { * @param index Index of attribute to retrieve. * * @return True if the attribute has a value or is empty, false otherwise. + * @throws RuntimeException if the TypedArray has already been recycled. */ public boolean hasValueOrEmpty(int index) { if (mRecycled) { @@ -857,6 +973,7 @@ public class TypedArray { * @return Returns a TypedValue object if the attribute is defined, * containing its data; otherwise returns null. (You will not * receive a TypedValue whose type is TYPE_NULL.) + * @throws RuntimeException if the TypedArray has already been recycled. */ public TypedValue peekValue(int index) { if (mRecycled) { @@ -872,6 +989,9 @@ public class TypedArray { /** * Returns a message about the parser state suitable for printing error messages. + * + * @return Human-readable description of current parser state. + * @throws RuntimeException if the TypedArray has already been recycled. */ public String getPositionDescription() { if (mRecycled) { @@ -882,8 +1002,10 @@ public class TypedArray { } /** - * Recycle the TypedArray, to be re-used by a later caller. After calling + * Recycles the TypedArray, to be re-used by a later caller. After calling * this function you must not ever touch the typed array again. + * + * @throws RuntimeException if the TypedArray has already been recycled. */ public void recycle() { if (mRecycled) { @@ -905,9 +1027,10 @@ public class TypedArray { * Removes the entries from the typed array so that subsequent calls to typed * getters will return the default value without crashing. * - * @return An array of length {@link #getIndexCount()} populated with theme - * attributes, or {@code null} if there are no theme attributes in - * the typed array. + * @return an array of length {@link #getIndexCount()} populated with theme + * attributes, or null if there are no theme attributes in the typed + * array + * @throws RuntimeException if the TypedArray has already been recycled. * @hide */ @Nullable @@ -966,9 +1089,14 @@ public class TypedArray { * * @return Returns a mask of the changing configuration parameters, as * defined by {@link android.content.pm.ActivityInfo}. + * @throws RuntimeException if the TypedArray has already been recycled. * @see android.content.pm.ActivityInfo */ public int getChangingConfigurations() { + if (mRecycled) { + throw new RuntimeException("Cannot make calls to a recycled instance!"); + } + int changingConfig = 0; final int[] data = mData; diff --git a/core/java/android/midi/IMidiDeviceServer.aidl b/core/java/android/midi/IMidiDeviceServer.aidl new file mode 100644 index 0000000..31fdbbb --- /dev/null +++ b/core/java/android/midi/IMidiDeviceServer.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.midi; + +import android.os.ParcelFileDescriptor; + +/** @hide */ +interface IMidiDeviceServer +{ + ParcelFileDescriptor openInputPort(int portNumber); + ParcelFileDescriptor openOutputPort(int portNumber); +} diff --git a/core/java/android/midi/IMidiManager.aidl b/core/java/android/midi/IMidiManager.aidl index 7a9f887..575b525 100644 --- a/core/java/android/midi/IMidiManager.aidl +++ b/core/java/android/midi/IMidiManager.aidl @@ -16,13 +16,11 @@ package android.midi; -import android.hardware.usb.UsbDevice; +import android.midi.IMidiDeviceServer; import android.midi.IMidiListener; -import android.midi.MidiDevice; import android.midi.MidiDeviceInfo; import android.os.Bundle; import android.os.IBinder; -import android.os.ParcelFileDescriptor; /** @hide */ interface IMidiManager @@ -34,14 +32,10 @@ interface IMidiManager void unregisterListener(IBinder token, in IMidiListener listener); // for communicating with MIDI devices - ParcelFileDescriptor openDevice(IBinder token, in MidiDeviceInfo device); + IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device); // for implementing virtual MIDI devices - MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts, - in Bundle properties); - void unregisterVirtualDevice(IBinder token, in MidiDeviceInfo device); - - // for use by UsbAudioManager - void alsaDeviceAdded(int card, int device, in UsbDevice usbDevice); - void alsaDeviceRemoved(in UsbDevice usbDevice); + MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts, + int numOutputPorts, in Bundle properties, boolean isPrivate, int type); + void unregisterDeviceServer(in IMidiDeviceServer server); } diff --git a/core/java/android/midi/MidiDevice.java b/core/java/android/midi/MidiDevice.java index 7f83896..8aaa86d 100644 --- a/core/java/android/midi/MidiDevice.java +++ b/core/java/android/midi/MidiDevice.java @@ -16,9 +16,8 @@ package android.midi; -import android.os.Parcel; -import android.os.Parcelable; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.util.Log; import java.io.FileDescriptor; @@ -30,114 +29,31 @@ import java.util.ArrayList; /** * This class is used for sending and receiving data to and from an MIDI device * Instances of this class are created by {@link MidiManager#openDevice}. - * This class can also be used to provide the implementation for a virtual device. - * - * This class implements Parcelable so it can be returned from MidiService when creating - * virtual MIDI devices. * * @hide */ -public final class MidiDevice implements Parcelable { +public final class MidiDevice { private static final String TAG = "MidiDevice"; private final MidiDeviceInfo mDeviceInfo; - private ParcelFileDescriptor mParcelFileDescriptor; - private FileInputStream mInputStream; - private FileOutputStream mOutputStream; - - // lazily populated lists of ports - private final MidiInputPort[] mInputPorts; - private final MidiOutputPort[] mOutputPorts; - - // array of receiver lists, indexed by port number - private final ArrayList<MidiReceiver>[] mReceivers; - - private int mReceiverCount; // total number of receivers for all ports + private final IMidiDeviceServer mServer; - /** - * Minimum size of packed message as sent through our ParcelFileDescriptor - * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message + /** + * MidiDevice should only be instantiated by MidiManager * @hide */ - public static final int MIN_PACKED_MESSAGE_SIZE = 10; + public MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) { + mDeviceInfo = deviceInfo; + mServer = server; + } /** - * Maximum size of packed message as sent through our ParcelFileDescriptor - * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message - * @hide - */ - public static final int MAX_PACKED_MESSAGE_SIZE = 12; - - // This thread reads MIDI events from a socket and distributes them to the list of - // MidiReceivers attached to this device. - private final Thread mThread = new Thread() { - @Override - public void run() { - byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE]; - ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>(); - - try { - while (true) { - // read next event - int count = mInputStream.read(buffer); - if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) { - Log.e(TAG, "Number of bytes read out of range: " + count); - break; - } - - int offset = getMessageOffset(buffer, count); - int size = getMessageSize(buffer, count); - long timestamp = getMessageTimeStamp(buffer, count); - int port = getMessagePortNumber(buffer, count); - - synchronized (mReceivers) { - ArrayList<MidiReceiver> receivers = mReceivers[port]; - if (receivers != null) { - for (int i = 0; i < receivers.size(); i++) { - MidiReceiver receiver = receivers.get(i); - try { - receivers.get(i).onPost(buffer, offset, size, timestamp); - } catch (IOException e) { - Log.e(TAG, "post failed"); - deadReceivers.add(receiver); - } - } - // remove any receivers that failed - if (deadReceivers.size() > 0) { - for (MidiReceiver receiver: deadReceivers) { - receivers.remove(receiver); - mReceiverCount--; - } - deadReceivers.clear(); - } - if (receivers.size() == 0) { - mReceivers[port] = null; - } - // exit if we have no receivers left - if (mReceiverCount == 0) { - break; - } - } - } - } - } catch (IOException e) { - Log.e(TAG, "read failed"); - } - } - }; - - /** - * MidiDevice should only be instantiated by MidiManager or MidiService - * @hide + * Returns a {@link MidiDeviceInfo} object, which describes this device. + * + * @return the {@link MidiDeviceInfo} object */ - public MidiDevice(MidiDeviceInfo deviceInfo, ParcelFileDescriptor pfd) { - mDeviceInfo = deviceInfo; - mParcelFileDescriptor = pfd; - int inputPorts = deviceInfo.getInputPortCount(); - int outputPorts = deviceInfo.getOutputPortCount(); - mInputPorts = new MidiInputPort[inputPorts]; - mOutputPorts = new MidiOutputPort[outputPorts]; - mReceivers = new ArrayList[outputPorts]; + public MidiDeviceInfo getInfo() { + return mDeviceInfo; } /** @@ -147,14 +63,15 @@ public final class MidiDevice implements Parcelable { * @return the {@link MidiInputPort} */ public MidiInputPort openInputPort(int portNumber) { - if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) { - throw new IllegalArgumentException("input port number out of range"); - } - synchronized (mInputPorts) { - if (mInputPorts[portNumber] == null) { - mInputPorts[portNumber] = new MidiInputPort(mOutputStream, portNumber); + try { + ParcelFileDescriptor pfd = mServer.openInputPort(portNumber); + if (pfd == null) { + return null; } - return mInputPorts[portNumber]; + return new MidiInputPort(pfd, portNumber); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openInputPort"); + return null; } } @@ -165,185 +82,20 @@ public final class MidiDevice implements Parcelable { * @return the {@link MidiOutputPort} */ public MidiOutputPort openOutputPort(int portNumber) { - if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) { - throw new IllegalArgumentException("output port number out of range"); - } - synchronized (mOutputPorts) { - if (mOutputPorts[portNumber] == null) { - mOutputPorts[portNumber] = new MidiOutputPort(this, portNumber); - } - return mOutputPorts[portNumber]; - } - } - - /* package */ void connect(MidiReceiver receiver, int portNumber) { - synchronized (mReceivers) { - if (mReceivers[portNumber] == null) { - mReceivers[portNumber] = new ArrayList<MidiReceiver>(); - } - mReceivers[portNumber].add(receiver); - if (mReceiverCount++ == 0) { - mThread.start(); - } - } - } - - /* package */ void disconnect(MidiReceiver receiver, int portNumber) { - synchronized (mReceivers) { - ArrayList<MidiReceiver> receivers = mReceivers[portNumber]; - if (receivers != null && receivers.remove(receiver)) { - mReceiverCount--; - } - } - } - - /* package */ boolean open() { - FileDescriptor fd = mParcelFileDescriptor.getFileDescriptor(); - try { - mInputStream = new FileInputStream(fd); - } catch (Exception e) { - Log.e(TAG, "could not create mInputStream", e); - return false; - } - try { - mOutputStream = new FileOutputStream(fd); - } catch (Exception e) { - Log.e(TAG, "could not create mOutputStream", e); - return false; - } - return true; - } - - /* package */ void close() { - try { - if (mInputStream != null) { - mInputStream.close(); - } - if (mOutputStream != null) { - mOutputStream.close(); + ParcelFileDescriptor pfd = mServer.openOutputPort(portNumber); + if (pfd == null) { + return null; } - mParcelFileDescriptor.close(); - } catch (IOException e) { + return new MidiOutputPort(pfd, portNumber); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openOutputPort"); + return null; } } - /** - * Returns a {@link MidiDeviceInfo} object, which describes this device. - * - * @return the {@link MidiDeviceInfo} object - */ - public MidiDeviceInfo getInfo() { - return mDeviceInfo; - } - @Override public String toString() { - return ("MidiDevice: " + mDeviceInfo.toString() + " fd: " + mParcelFileDescriptor); - } - - public static final Parcelable.Creator<MidiDevice> CREATOR = - new Parcelable.Creator<MidiDevice>() { - public MidiDevice createFromParcel(Parcel in) { - MidiDeviceInfo deviceInfo = (MidiDeviceInfo)in.readParcelable(null); - ParcelFileDescriptor pfd = (ParcelFileDescriptor)in.readParcelable(null); - return new MidiDevice(deviceInfo, pfd); - } - - public MidiDevice[] newArray(int size) { - return new MidiDevice[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeParcelable(mDeviceInfo, flags); - parcel.writeParcelable(mParcelFileDescriptor, flags); - } - - /** - * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor - * - * message byte array contains variable length MIDI message. - * messageSize is size of variable length MIDI message - * timestamp is message timestamp to pack - * dest is buffer to pack into - * returns size of packed message - * - * @hide - */ - public static int packMessage(byte[] message, int offset, int size, long timestamp, - int portNumber, byte[] dest) { - // pack variable length message first - System.arraycopy(message, offset, dest, 0, size); - int destOffset = size; - // timestamp takes 8 bytes - for (int i = 0; i < 8; i++) { - dest[destOffset++] = (byte)timestamp; - timestamp >>= 8; - } - // portNumber is last - dest[destOffset++] = (byte)portNumber; - - return destOffset; - } - - /** - * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor - * returns the offet of of MIDI message in packed buffer - * - * @hide - */ - public static int getMessageOffset(byte[] buffer, int bufferLength) { - // message is at start of buffer - return 0; - } - - /** - * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor - * returns size of MIDI message in packed buffer - * - * @hide - */ - public static int getMessageSize(byte[] buffer, int bufferLength) { - // message length is total buffer length minus size of the timestamp and port number - return bufferLength - 9 /* (sizeof(timestamp) + sizeof(portNumber)) */; + return ("MidiDevice: " + mDeviceInfo.toString()); } - - /** - * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor - * unpacks timestamp from packed buffer - * - * @hide - */ - public static long getMessageTimeStamp(byte[] buffer, int bufferLength) { - long timestamp = 0; - - // timestamp follows variable length message data - int dataLength = getMessageSize(buffer, bufferLength); - for (int i = dataLength + 7; i >= dataLength; i--) { - // why can't Java deal with unsigned ints? - int b = buffer[i]; - if (b < 0) b += 256; - timestamp = (timestamp << 8) | b; - } - return timestamp; - } - - /** - * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor - * unpacks port number from packed buffer - * - * @hide - */ - public static int getMessagePortNumber(byte[] buffer, int bufferLength) { - // timestamp follows variable length message data and timestamp - int dataLength = getMessageSize(buffer, bufferLength); - return buffer[dataLength + 8 /* sizeof(timestamp) */]; - } } diff --git a/core/java/android/midi/MidiDeviceInfo.java b/core/java/android/midi/MidiDeviceInfo.java index 5b57696..5cf62b5 100644 --- a/core/java/android/midi/MidiDeviceInfo.java +++ b/core/java/android/midi/MidiDeviceInfo.java @@ -50,10 +50,6 @@ public class MidiDeviceInfo implements Parcelable { private final int mOutputPortCount; private final Bundle mProperties; - // used for USB devices only - private final int mAlsaCard; - private final int mAlsaDevice; - /** * Bundle key for the device's manufacturer name property. * Used with the {@link android.os.Bundle} returned by {@link #getProperties}. @@ -83,33 +79,30 @@ public class MidiDeviceInfo implements Parcelable { public static final String PROPERTY_USB_DEVICE = "usb_device"; /** - * MidiDeviceInfo should only be instantiated by MidiService implementation - * @hide + * Bundle key for the device's ALSA card number. + * Only set for USB MIDI devices. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} */ - public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts, - Bundle properties) { - mType = type; - mId = id; - mInputPortCount = numInputPorts; - mOutputPortCount = numOutputPorts; - mProperties = properties; - mAlsaCard = -1; - mAlsaDevice = -1; - } + public static final String PROPERTY_ALSA_CARD = "alsa_card"; + + /** + * Bundle key for the device's ALSA device number. + * Only set for USB MIDI devices. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + */ + public static final String PROPERTY_ALSA_DEVICE = "alsa_device"; /** * MidiDeviceInfo should only be instantiated by MidiService implementation * @hide */ public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts, - Bundle properties, int alsaCard, int alsaDevice) { + Bundle properties) { mType = type; mId = id; mInputPortCount = numInputPorts; mOutputPortCount = numOutputPorts; mProperties = properties; - mAlsaCard = alsaCard; - mAlsaDevice = alsaDevice; } /** @@ -158,20 +151,6 @@ public class MidiDeviceInfo implements Parcelable { return mProperties; } - /** - * @hide - */ - public int getAlsaCard() { - return mAlsaCard; - } - - /** - * @hide - */ - public int getAlsaDevice() { - return mAlsaDevice; - } - @Override public boolean equals(Object o) { if (o instanceof MidiDeviceInfo) { @@ -191,9 +170,7 @@ public class MidiDeviceInfo implements Parcelable { return ("MidiDeviceInfo[mType=" + mType + ",mInputPortCount=" + mInputPortCount + ",mOutputPortCount=" + mOutputPortCount + - ",mProperties=" + mProperties + - ",mAlsaCard=" + mAlsaCard + - ",mAlsaDevice=" + mAlsaDevice); + ",mProperties=" + mProperties); } public static final Parcelable.Creator<MidiDeviceInfo> CREATOR = @@ -204,9 +181,7 @@ public class MidiDeviceInfo implements Parcelable { int inputPorts = in.readInt(); int outputPorts = in.readInt(); Bundle properties = in.readBundle(); - int card = in.readInt(); - int device = in.readInt(); - return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, card, device); + return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties); } public MidiDeviceInfo[] newArray(int size) { @@ -224,7 +199,5 @@ public class MidiDeviceInfo implements Parcelable { parcel.writeInt(mInputPortCount); parcel.writeInt(mOutputPortCount); parcel.writeBundle(mProperties); - parcel.writeInt(mAlsaCard); - parcel.writeInt(mAlsaDevice); } } diff --git a/core/java/android/midi/MidiDeviceServer.java b/core/java/android/midi/MidiDeviceServer.java new file mode 100644 index 0000000..6ce4bf7 --- /dev/null +++ b/core/java/android/midi/MidiDeviceServer.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.midi; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.system.OsConstants; +import android.util.Log; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; + +/** @hide */ +public final class MidiDeviceServer implements Closeable { + private static final String TAG = "MidiDeviceServer"; + + private final IMidiManager mMidiManager; + + // MidiDeviceInfo for the device implemented by this server + private MidiDeviceInfo mDeviceInfo; + private int mInputPortCount; + private int mOutputPortCount; + + // output ports for receiving messages from our clients + // we can have only one per port number + private MidiOutputPort[] mInputPortSenders; + + // receivers attached to our input ports + private ArrayList<MidiReceiver>[] mInputPortReceivers; + + // input ports for sending messages to our clients + // we can have multiple outputs per port number + private ArrayList<MidiInputPort>[] mOutputPortReceivers; + + // subclass of MidiInputPort for passing to clients + // that notifies us when the connection has failed + private class ServerInputPort extends MidiInputPort { + ServerInputPort(ParcelFileDescriptor pfd, int portNumber) { + super(pfd, portNumber); + } + + @Override + public void onIOException() { + synchronized (mOutputPortReceivers) { + mOutputPortReceivers[getPortNumber()] = null; + } + } + } + + // subclass of MidiOutputPort for passing to clients + // that notifies us when the connection has failed + private class ServerOutputPort extends MidiOutputPort { + ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) { + super(pfd, portNumber); + } + + @Override + public void onIOException() { + synchronized (mInputPortSenders) { + mInputPortSenders[getPortNumber()] = null; + } + } + } + + // Binder interface stub for receiving connection requests from clients + private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() { + + @Override + public ParcelFileDescriptor openInputPort(int portNumber) { + if (portNumber < 0 || portNumber >= mInputPortCount) { + Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber); + return null; + } + + ParcelFileDescriptor result = null; + MidiOutputPort newOutputPort = null; + + synchronized (mInputPortSenders) { + if (mInputPortSenders[portNumber] != null) { + Log.d(TAG, "port " + portNumber + " already open"); + return null; + } + + try { + ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( + OsConstants.SOCK_SEQPACKET); + newOutputPort = new ServerOutputPort(pair[0], portNumber); + mInputPortSenders[portNumber] = newOutputPort; + result = pair[1]; + } catch (IOException e) { + Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort"); + return null; + } + + if (newOutputPort != null) { + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber]; + synchronized (receivers) { + for (int i = 0; i < receivers.size(); i++) { + newOutputPort.connect(receivers.get(i)); + } + } + } + } + + return result; + } + + @Override + public ParcelFileDescriptor openOutputPort(int portNumber) { + if (portNumber < 0 || portNumber >= mOutputPortCount) { + Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber); + return null; + } + synchronized (mOutputPortReceivers) { + try { + ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( + OsConstants.SOCK_SEQPACKET); + mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber)); + return pair[1]; + } catch (IOException e) { + Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort"); + return null; + } + } + } + }; + + /* package */ MidiDeviceServer(IMidiManager midiManager) { + mMidiManager = midiManager; + } + + /* package */ IMidiDeviceServer getBinderInterface() { + return mServer; + } + + /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) { + if (mDeviceInfo != null) { + throw new IllegalStateException("setDeviceInfo should only be called once"); + } + mDeviceInfo = deviceInfo; + mInputPortCount = deviceInfo.getInputPortCount(); + mOutputPortCount = deviceInfo.getOutputPortCount(); + mInputPortSenders = new MidiOutputPort[mInputPortCount]; + + mInputPortReceivers = new ArrayList[mInputPortCount]; + for (int i = 0; i < mInputPortCount; i++) { + mInputPortReceivers[i] = new ArrayList<MidiReceiver>(); + } + + mOutputPortReceivers = new ArrayList[mOutputPortCount]; + for (int i = 0; i < mOutputPortCount; i++) { + mOutputPortReceivers[i] = new ArrayList<MidiInputPort>(); + } + } + + @Override + public void close() throws IOException { + try { + // FIXME - close input and output ports too? + mMidiManager.unregisterDeviceServer(mServer); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in unregisterDeviceServer"); + } + } + + /** + * Returns a {@link MidiDeviceInfo} object, which describes this device. + * + * @return the {@link MidiDeviceInfo} object + */ + public MidiDeviceInfo getInfo() { + return mDeviceInfo; + } + + /** + * Called to open a {@link MidiSender} to allow receiving MIDI messages + * on the device's input port for the specified port number. + * + * @param portNumber the number of the input port + * @return the {@link MidiSender} + */ + public MidiSender openInputPortSender(int portNumber) { + if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) { + throw new IllegalArgumentException("portNumber " + portNumber + " out of range"); + } + final int portNumberF = portNumber; + return new MidiSender() { + + @Override + public void connect(MidiReceiver receiver) { + // We always synchronize on mInputPortSenders before receivers if we need to + // synchronize on both. + synchronized (mInputPortSenders) { + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF]; + synchronized (receivers) { + receivers.add(receiver); + MidiOutputPort outputPort = mInputPortSenders[portNumberF]; + if (outputPort != null) { + outputPort.connect(receiver); + } + } + } + } + + @Override + public void disconnect(MidiReceiver receiver) { + // We always synchronize on mInputPortSenders before receivers if we need to + // synchronize on both. + synchronized (mInputPortSenders) { + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF]; + synchronized (receivers) { + receivers.remove(receiver); + MidiOutputPort outputPort = mInputPortSenders[portNumberF]; + if (outputPort != null) { + outputPort.disconnect(receiver); + } + } + } + } + }; + } + + /** + * Called to open a {@link MidiReceiver} to allow sending MIDI messages + * on the virtual device's output port for the specified port number. + * + * @param portNumber the number of the output port + * @return the {@link MidiReceiver} + */ + public MidiReceiver openOutputPortReceiver(int portNumber) { + if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) { + throw new IllegalArgumentException("portNumber " + portNumber + " out of range"); + } + final int portNumberF = portNumber; + return new MidiReceiver() { + + @Override + public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException { + ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF]; + synchronized (receivers) { + for (int i = 0; i < receivers.size(); i++) { + // FIXME catch errors and remove dead ones + receivers.get(i).onPost(msg, offset, count, timestamp); + } + } + } + }; + } +} diff --git a/core/java/android/midi/MidiInputPort.java b/core/java/android/midi/MidiInputPort.java index 31449a5..5d806cf 100644 --- a/core/java/android/midi/MidiInputPort.java +++ b/core/java/android/midi/MidiInputPort.java @@ -16,6 +16,10 @@ package android.midi; +import android.os.ParcelFileDescriptor; + +import libcore.io.IoUtils; + import java.io.FileOutputStream; import java.io.IOException; @@ -24,15 +28,16 @@ import java.io.IOException; * * @hide */ -public final class MidiInputPort extends MidiPort implements MidiReceiver { +public class MidiInputPort extends MidiPort implements MidiReceiver { private final FileOutputStream mOutputStream; + // buffer to use for sending messages out our output stream - private final byte[] mBuffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE]; + private final byte[] mBuffer = new byte[MAX_PACKED_MESSAGE_SIZE]; - /* package */ MidiInputPort(FileOutputStream outputStream, int portNumber) { + /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) { super(portNumber); - mOutputStream = outputStream; + mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd); } /** @@ -46,9 +51,20 @@ public final class MidiInputPort extends MidiPort implements MidiReceiver { */ public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException { synchronized (mBuffer) { - int length = MidiDevice.packMessage(msg, offset, count, timestamp, getPortNumber(), - mBuffer); - mOutputStream.write(mBuffer, 0, length); + int length = packMessage(msg, offset, count, timestamp, mBuffer); + try { + mOutputStream.write(mBuffer, 0, length); + } catch (IOException e) { + IoUtils.closeQuietly(mOutputStream); + // report I/O failure + onIOException(); + throw e; + } } } + + @Override + public void close() throws IOException { + mOutputStream.close(); + } } diff --git a/core/java/android/midi/MidiManager.java b/core/java/android/midi/MidiManager.java index 64cd4fe..2c1c7bf 100644 --- a/core/java/android/midi/MidiManager.java +++ b/core/java/android/midi/MidiManager.java @@ -20,7 +20,6 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.Bundle; -import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; @@ -146,40 +145,31 @@ public class MidiManager { */ public MidiDevice openDevice(MidiDeviceInfo deviceInfo) { try { - ParcelFileDescriptor pfd = mService.openDevice(mToken, deviceInfo); - if (pfd == null) { + IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo); + if (server == null) { Log.e(TAG, "could not open device " + deviceInfo); return null; } - MidiDevice device = new MidiDevice(deviceInfo, pfd); - if (device.open()) { - Log.d(TAG, "openDevice returning " + device); - return device; - } + return new MidiDevice(deviceInfo, server); } catch (RemoteException e) { Log.e(TAG, "RemoteException in openDevice"); } return null; } - /** - * Creates a new MIDI virtual device. - * NOTE: The method for creating virtual devices is likely to change before release. - * - * @param numInputPorts number of input ports for the virtual device - * @param numOutputPorts number of output ports for the virtual device - * @param properties a {@link android.os.Bundle} containing properties describing the device - * @return a {@link MidiDevice} object to locally represent the device - */ - public MidiDevice createVirtualDevice(int numInputPorts, int numOutputPorts, - Bundle properties) { + /** @hide */ + public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts, + Bundle properties, boolean isPrivate, int type) { try { - MidiDevice device = mService.registerVirtualDevice(mToken, - numInputPorts, numOutputPorts, properties); - if (device != null && !device.open()) { - device = null; + MidiDeviceServer server = new MidiDeviceServer(mService); + MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), + numInputPorts, numOutputPorts, properties, isPrivate, type); + if (deviceInfo == null) { + Log.e(TAG, "registerVirtualDevice failed"); + return null; } - return device; + server.setDeviceInfo(deviceInfo); + return server; } catch (RemoteException e) { Log.e(TAG, "RemoteException in createVirtualDevice"); return null; @@ -187,16 +177,19 @@ public class MidiManager { } /** - * Removes a MIDI virtual device. + * Creates a new MIDI virtual device. * - * @param device the {@link MidiDevice} for the virtual device to remove + * @param numInputPorts number of input ports for the virtual device + * @param numOutputPorts number of output ports for the virtual device + * @param properties a {@link android.os.Bundle} containing properties describing the device + * @param isPrivate true if this device should only be visible and accessible to apps + * with the same UID as the caller + * @return a {@link MidiVirtualDevice} object to locally represent the device */ - public void closeVirtualDevice(MidiDevice device) { - try { - device.close(); - mService.unregisterVirtualDevice(mToken, device.getInfo()); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in unregisterVirtualDevice"); - } + public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts, + Bundle properties, boolean isPrivate) { + return createDeviceServer(numInputPorts, numOutputPorts, properties, + isPrivate, MidiDeviceInfo.TYPE_VIRTUAL); } + } diff --git a/core/java/android/midi/MidiOutputPort.java b/core/java/android/midi/MidiOutputPort.java index 7ce286b..8b2d65d 100644 --- a/core/java/android/midi/MidiOutputPort.java +++ b/core/java/android/midi/MidiOutputPort.java @@ -16,20 +16,88 @@ package android.midi; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import libcore.io.IoUtils; + import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; /** * This class is used for receiving data to a port on a MIDI device * * @hide */ -public final class MidiOutputPort extends MidiPort implements MidiSender { +public class MidiOutputPort extends MidiPort implements MidiSender { + private static final String TAG = "MidiOutputPort"; + + private final FileInputStream mInputStream; + + // array of receiver lists, indexed by port number + private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>(); + + private int mReceiverCount; // total number of receivers for all ports + + // This thread reads MIDI events from a socket and distributes them to the list of + // MidiReceivers attached to this device. + private final Thread mThread = new Thread() { + @Override + public void run() { + byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE]; + ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>(); - private final MidiDevice mDevice; + try { + while (true) { + // read next event + int count = mInputStream.read(buffer); + if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) { + Log.e(TAG, "Number of bytes read out of range: " + count); + break; + } - /* package */ MidiOutputPort(MidiDevice device, int portNumber) { + int offset = getMessageOffset(buffer, count); + int size = getMessageSize(buffer, count); + long timestamp = getMessageTimeStamp(buffer, count); + + synchronized (mReceivers) { + for (int i = 0; i < mReceivers.size(); i++) { + MidiReceiver receiver = mReceivers.get(i); + try { + receiver.onPost(buffer, offset, size, timestamp); + } catch (IOException e) { + Log.e(TAG, "post failed"); + deadReceivers.add(receiver); + } + } + // remove any receivers that failed + if (deadReceivers.size() > 0) { + for (MidiReceiver receiver: deadReceivers) { + mReceivers.remove(receiver); + mReceiverCount--; + } + deadReceivers.clear(); + } + // exit if we have no receivers left + if (mReceiverCount == 0) { + break; + } + } + } + } catch (IOException e) { + Log.e(TAG, "read failed"); + // report I/O failure + IoUtils.closeQuietly(mInputStream); + onIOException(); + } + } + }; + + + /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) { super(portNumber); - mDevice = device; + mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); } /** @@ -39,7 +107,12 @@ public final class MidiOutputPort extends MidiPort implements MidiSender { * @param receiver the receiver to connect */ public void connect(MidiReceiver receiver) { - mDevice.connect(receiver, getPortNumber()); + synchronized (mReceivers) { + mReceivers.add(receiver); + if (mReceiverCount++ == 0) { + mThread.start(); + } + } } /** @@ -48,6 +121,15 @@ public final class MidiOutputPort extends MidiPort implements MidiSender { * @param receiver the receiver to connect */ public void disconnect(MidiReceiver receiver) { - mDevice.disconnect(receiver, getPortNumber()); + synchronized (mReceivers) { + if (mReceivers.remove(receiver)) { + mReceiverCount--; + } + } + } + + @Override + public void close() throws IOException { + mInputStream.close(); } } diff --git a/core/java/android/midi/MidiPort.java b/core/java/android/midi/MidiPort.java index fdd0233..53cba21 100644 --- a/core/java/android/midi/MidiPort.java +++ b/core/java/android/midi/MidiPort.java @@ -16,8 +16,9 @@ package android.midi; -import java.io.FileOutputStream; -import java.io.IOException; +import android.util.Log; + +import java.io.Closeable; /** * This class represents a MIDI input or output port. @@ -25,10 +26,24 @@ import java.io.IOException; * * @hide */ -public class MidiPort { +abstract public class MidiPort implements Closeable { + private static final String TAG = "MidiPort"; private final int mPortNumber; + /** + * Minimum size of packed message as sent through our ParcelFileDescriptor + * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message + */ + protected static final int MIN_PACKED_MESSAGE_SIZE = 10; + + /** + * Maximum size of packed message as sent through our ParcelFileDescriptor + * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message + */ + protected static final int MAX_PACKED_MESSAGE_SIZE = 12; + + /* package */ MidiPort(int portNumber) { mPortNumber = portNumber; } @@ -41,4 +56,69 @@ public class MidiPort { public final int getPortNumber() { return mPortNumber; } + + /** + * Called when an IOExeption occurs while sending or receiving data. + * Subclasses can override to be notified of such errors + * + */ + public void onIOException() { + } + + /** + * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor + * + * message byte array contains variable length MIDI message. + * messageSize is size of variable length MIDI message + * timestamp is message timestamp to pack + * dest is buffer to pack into + * returns size of packed message + */ + protected static int packMessage(byte[] message, int offset, int size, long timestamp, + byte[] dest) { + // pack variable length message first + System.arraycopy(message, offset, dest, 0, size); + int destOffset = size; + // timestamp takes 8 bytes + for (int i = 0; i < 8; i++) { + dest[destOffset++] = (byte)timestamp; + timestamp >>= 8; + } + + return destOffset; + } + + /** + * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor + * returns the offet of of MIDI message in packed buffer + */ + protected static int getMessageOffset(byte[] buffer, int bufferLength) { + // message is at start of buffer + return 0; + } + + /** + * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor + * returns size of MIDI message in packed buffer + */ + protected static int getMessageSize(byte[] buffer, int bufferLength) { + // message length is total buffer length minus size of the timestamp and port number + return bufferLength - 8 /* sizeof(timestamp) */; + } + + /** + * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor + * unpacks timestamp from packed buffer + */ + protected static long getMessageTimeStamp(byte[] buffer, int bufferLength) { + long timestamp = 0; + + // timestamp follows variable length message data + int dataLength = getMessageSize(buffer, bufferLength); + for (int i = dataLength + 7; i >= dataLength; i--) { + int b = (int)buffer[i] & 0xFF; + timestamp = (timestamp << 8) | b; + } + return timestamp; + } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index c0dca0e..cd45cfb 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -4083,7 +4083,7 @@ public abstract class BatteryStats implements Parcelable { prepareForDumpLocked(); dumpLine(pw, 0 /* uid */, "i" /* category */, VERSION_DATA, - "11", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion()); + "12", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion()); long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index b209690..23ddd03 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -91,7 +91,7 @@ public class Build { /** The name of the hardware (from the kernel command line or /proc). */ public static final String HARDWARE = getString("ro.hardware"); - /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */ + /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */ public static final String SERIAL = getString("ro.serialno"); /** @@ -159,7 +159,7 @@ public class Build { /** * The user-visible SDK version of the framework in its raw String * representation; use {@link #SDK_INT} instead. - * + * * @deprecated Use {@link #SDK_INT} to easily get this as an integer. */ @Deprecated @@ -207,25 +207,25 @@ public class Build { * not yet turned into an official release. */ public static final int CUR_DEVELOPMENT = 10000; - + /** * October 2008: The original, first, version of Android. Yay! */ public static final int BASE = 1; - + /** * February 2009: First Android update, officially called 1.1. */ public static final int BASE_1_1 = 2; - + /** * May 2009: Android 1.5. */ public static final int CUPCAKE = 3; - + /** * September 2009: Android 1.6. - * + * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> * <ul> @@ -247,10 +247,10 @@ public class Build { * </ul> */ public static final int DONUT = 4; - + /** * November 2009: Android 2.0 - * + * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> * <ul> @@ -267,22 +267,22 @@ public class Build { * </ul> */ public static final int ECLAIR = 5; - + /** * December 2009: Android 2.0.1 */ public static final int ECLAIR_0_1 = 6; - + /** * January 2010: Android 2.1 */ public static final int ECLAIR_MR1 = 7; - + /** * June 2010: Android 2.2 */ public static final int FROYO = 8; - + /** * November 2010: Android 2.3 * @@ -294,7 +294,7 @@ public class Build { * </ul> */ public static final int GINGERBREAD = 9; - + /** * February 2011: Android 2.3.3. */ @@ -339,12 +339,12 @@ public class Build { * </ul> */ public static final int HONEYCOMB = 11; - + /** * May 2011: Android 3.1. */ public static final int HONEYCOMB_MR1 = 12; - + /** * June 2011: Android 3.2. * @@ -598,7 +598,7 @@ public class Build { */ public static final int LOLLIPOP_MR1 = 22; } - + /** The type of build, like "user" or "eng". */ public static final String TYPE = getString("ro.build.type"); @@ -653,6 +653,7 @@ public class Build { public static boolean isFingerprintConsistent() { final String system = SystemProperties.get("ro.build.fingerprint"); final String vendor = SystemProperties.get("ro.vendor.build.fingerprint"); + final String bootimage = SystemProperties.get("ro.bootimage.build.fingerprint"); if (TextUtils.isEmpty(system)) { Slog.e(TAG, "Required ro.build.fingerprint is empty!"); @@ -667,6 +668,14 @@ public class Build { } } + if (!TextUtils.isEmpty(bootimage)) { + if (!Objects.equals(vendor, bootimage)) { + Slog.e(TAG, "Mismatched fingerprints; system and vendor reported " + system + + " but bootimage reported " + bootimage); + return false; + } + } + return true; } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 6db5f67..63ccf64 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -174,8 +174,16 @@ public final class StrictMode { */ public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy + /** + * For StrictMode.noteResourceMismatch() + * + * @hide + */ + public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy + private static final int ALL_THREAD_DETECT_BITS = - DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM; + DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM | + DETECT_RESOURCE_MISMATCH; // Process-policy: @@ -430,6 +438,22 @@ public final class StrictMode { } /** + * Disable detection of mismatches between defined resource types + * and getter calls. + */ + public Builder permitResourceMismatches() { + return disable(DETECT_RESOURCE_MISMATCH); + } + + /** + * Enable detection of mismatches between defined resource types + * and getter calls. + */ + public Builder detectResourceMismatches() { + return enable(DETECT_RESOURCE_MISMATCH); + } + + /** * Enable detection of disk writes. */ public Builder detectDiskWrites() { @@ -851,6 +875,15 @@ public final class StrictMode { } /** + * @hide + */ + private static class StrictModeResourceMismatchViolation extends StrictModeViolation { + public StrictModeResourceMismatchViolation(int policyMask, Object tag) { + super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null); + } + } + + /** * Returns the bitmask of the current thread's policy. * * @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled @@ -1125,6 +1158,20 @@ public final class StrictMode { startHandlingViolationException(e); } + // Not part of BlockGuard.Policy; just part of StrictMode: + void onResourceMismatch(Object tag) { + if ((mPolicyMask & DETECT_RESOURCE_MISMATCH) == 0) { + return; + } + if (tooManyViolationsThisLoop()) { + return; + } + BlockGuard.BlockGuardPolicyException e = + new StrictModeResourceMismatchViolation(mPolicyMask, tag); + e.fillInStackTrace(); + startHandlingViolationException(e); + } + // Part of BlockGuard.Policy interface: public void onReadFromDisk() { if ((mPolicyMask & DETECT_DISK_READ) == 0) { @@ -1943,6 +1990,26 @@ public final class StrictMode { } /** + * For code to note that a resource was obtained using a type other than + * its defined type. This is a no-op unless the current thread's + * {@link android.os.StrictMode.ThreadPolicy} has + * {@link android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()} + * enabled. + * + * @param tag an object for the exception stack trace that's + * built if when this fires. + * @hide + */ + public static void noteResourceMismatch(Object tag) { + BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); + if (!(policy instanceof AndroidBlockGuardPolicy)) { + // StrictMode not enabled. + return; + } + ((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag); + } + + /** * @hide */ public static void noteDiskRead() { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index ffbed94..d124a49 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -56,6 +56,7 @@ public class UserManager { /** * Specifies if a user is disallowed from changing Wi-Fi * access points. The default value is <code>false</code>. + * <p/>This restriction has no effect in a managed profile. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -87,8 +88,10 @@ public class UserManager { public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps"; /** - * Specifies if a user is disallowed from toggling location sharing. + * Specifies if a user is disallowed from turning on location sharing. * The default value is <code>false</code>. + * <p/>In a managed profile, location sharing always reflects the primary user's setting, but + * can be overridden and forced off by setting this restriction to true in the managed profile. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -112,6 +115,7 @@ public class UserManager { /** * Specifies if a user is disallowed from configuring bluetooth. * The default value is <code>false</code>. + * <p/>This restriction has no effect in a managed profile. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -144,8 +148,10 @@ public class UserManager { public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials"; /** - * Specifies if a user is disallowed from removing itself and other - * users. The default value is <code>false</code>. + * When set on the primary user this specifies if the user can remove other users. + * When set on a secondary user, this specifies if the user can remove itself. + * This restriction has no effect on managed profiles. + * The default value is <code>false</code>. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -168,6 +174,7 @@ public class UserManager { /** * Specifies if a user is disallowed from configuring VPN. * The default value is <code>false</code>. + * This restriction has no effect in a managed profile. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -192,6 +199,8 @@ public class UserManager { * Specifies if a user is disallowed from factory resetting * from Settings. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. + * <p/>This restriction has no effect on secondary users and managed profiles since only the + * primary user can factory reset the device. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -204,6 +213,8 @@ public class UserManager { * Specifies if a user is disallowed from adding new users and * profiles. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. + * <p/>This restriction has no effect on secondary users and managed profiles since only the + * primary user can add other users. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -227,6 +238,8 @@ public class UserManager { * Specifies if a user is disallowed from configuring cell * broadcasts. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. + * <p/>This restriction has no effect on secondary users and managed profiles since only the + * primary user can configure cell broadcasts. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -239,6 +252,8 @@ public class UserManager { * Specifies if a user is disallowed from configuring mobile * networks. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. + * <p/>This restriction has no effect on secondary users and managed profiles since only the + * primary user can configure mobile networks. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -307,6 +322,8 @@ public class UserManager { * Specifies that the user is not allowed to make outgoing * phone calls. Emergency calls are still permitted. * The default value is <code>false</code>. + * <p/>This restriction has no effect on managed profiles since call intents are normally + * forwarded to the primary user. * * <p/>Key for user restrictions. * <p/>Type: Boolean @@ -383,8 +400,10 @@ public class UserManager { * * <p/>Key for application restrictions. * <p/>Type: Boolean - * @see android.app.admin.DevicePolicyManager#setApplicationRestrictions() - * @see android.app.admin.DevicePolicyManager#getApplicationRestrictions() + * @see android.app.admin.DevicePolicyManager#setApplicationRestrictions( + * android.content.ComponentName, String, Bundle) + * @see android.app.admin.DevicePolicyManager#getApplicationRestrictions( + * android.content.ComponentName, String) */ public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 69c81f8..687db7c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3657,6 +3657,7 @@ public final class Settings { * A flag containing settings used for biometric weak * @hide */ + @Deprecated public static final String LOCK_BIOMETRIC_WEAK_FLAGS = "lock_biometric_weak_flags"; @@ -3668,7 +3669,11 @@ public final class Settings { /** * Whether autolock is enabled (0 = false, 1 = true) + * + * @deprecated Use {@link android.app.KeyguardManager} to determine the state and security + * level of the keyguard. */ + @Deprecated public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock"; /** @@ -3707,6 +3712,7 @@ public final class Settings { * Ids of the user-selected appwidgets on the lockscreen (comma-delimited). * @hide */ + @Deprecated public static final String LOCK_SCREEN_APPWIDGET_IDS = "lock_screen_appwidget_ids"; @@ -3720,6 +3726,7 @@ public final class Settings { * Id of the appwidget shown on the lock screen when appwidgets are disabled. * @hide */ + @Deprecated public static final String LOCK_SCREEN_FALLBACK_APPWIDGET_ID = "lock_screen_fallback_appwidget_id"; @@ -3727,6 +3734,7 @@ public final class Settings { * Index of the lockscreen appwidget to restore, -1 if none. * @hide */ + @Deprecated public static final String LOCK_SCREEN_STICKY_APPWIDGET = "lock_screen_sticky_appwidget"; diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl new file mode 100644 index 0000000..bf51ed1 --- /dev/null +++ b/core/java/android/security/IKeystoreService.aidl @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.security.KeystoreArguments; + +/** + * This must be kept manually in sync with system/security/keystore until AIDL + * can generate both Java and C++ bindings. + * + * @hide + */ +interface IKeystoreService { + int test(); + byte[] get(String name); + int insert(String name, in byte[] item, int uid, int flags); + int del(String name, int uid); + int exist(String name, int uid); + String[] saw(String namePrefix, int uid); + int reset(); + int password(String password); + int lock(); + int unlock(String password); + int zero(); + int generate(String name, int uid, int keyType, int keySize, int flags, + in KeystoreArguments args); + int import_key(String name, in byte[] data, int uid, int flags); + byte[] sign(String name, in byte[] data); + int verify(String name, in byte[] data, in byte[] signature); + byte[] get_pubkey(String name); + int del_key(String name, int uid); + int grant(String name, int granteeUid); + int ungrant(String name, int granteeUid); + long getmtime(String name); + int duplicate(String srcKey, int srcUid, String destKey, int destUid); + int is_hardware_backed(String string); + int clear_uid(long uid); + int reset_uid(int uid); + int sync_uid(int sourceUid, int targetUid); + int password_uid(String password, int uid); +} diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java deleted file mode 100644 index 7e9aba0..0000000 --- a/core/java/android/security/IKeystoreService.java +++ /dev/null @@ -1,662 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security; - -import android.os.Binder; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Parcel; -import android.os.RemoteException; - -/** - * This must be kept manually in sync with system/security/keystore until AIDL - * can generate both Java and C++ bindings. - * - * @hide - */ -public interface IKeystoreService extends IInterface { - public static abstract class Stub extends Binder implements IKeystoreService { - private static class Proxy implements IKeystoreService { - private final IBinder mRemote; - - Proxy(IBinder remote) { - mRemote = remote; - } - - public IBinder asBinder() { - return mRemote; - } - - public String getInterfaceDescriptor() { - return DESCRIPTOR; - } - - public int test() throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - mRemote.transact(Stub.TRANSACTION_test, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public byte[] get(String name) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - byte[] _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - mRemote.transact(Stub.TRANSACTION_get, _data, _reply, 0); - _reply.readException(); - _result = _reply.createByteArray(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int insert(String name, byte[] item, int uid, int flags) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeByteArray(item); - _data.writeInt(uid); - _data.writeInt(flags); - mRemote.transact(Stub.TRANSACTION_insert, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int del(String name, int uid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeInt(uid); - mRemote.transact(Stub.TRANSACTION_del, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int exist(String name, int uid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeInt(uid); - mRemote.transact(Stub.TRANSACTION_exist, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public String[] saw(String name, int uid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - String[] _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeInt(uid); - mRemote.transact(Stub.TRANSACTION_saw, _data, _reply, 0); - _reply.readException(); - int size = _reply.readInt(); - _result = new String[size]; - for (int i = 0; i < size; i++) { - _result[i] = _reply.readString(); - } - int _ret = _reply.readInt(); - if (_ret != 1) { - return null; - } - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - @Override - public int reset() throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - mRemote.transact(Stub.TRANSACTION_reset, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int password(String password) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(password); - mRemote.transact(Stub.TRANSACTION_password, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int lock() throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - mRemote.transact(Stub.TRANSACTION_lock, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int unlock(String password) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(password); - mRemote.transact(Stub.TRANSACTION_unlock, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - @Override - public int zero() throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - mRemote.transact(Stub.TRANSACTION_zero, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int generate(String name, int uid, int keyType, int keySize, int flags, - byte[][] args) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeInt(uid); - _data.writeInt(keyType); - _data.writeInt(keySize); - _data.writeInt(flags); - if (args == null) { - _data.writeInt(0); - } else { - _data.writeInt(args.length); - for (int i = 0; i < args.length; i++) { - _data.writeByteArray(args[i]); - } - } - mRemote.transact(Stub.TRANSACTION_generate, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int import_key(String name, byte[] data, int uid, int flags) - throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeByteArray(data); - _data.writeInt(uid); - _data.writeInt(flags); - mRemote.transact(Stub.TRANSACTION_import, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public byte[] sign(String name, byte[] data) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - byte[] _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeByteArray(data); - mRemote.transact(Stub.TRANSACTION_sign, _data, _reply, 0); - _reply.readException(); - _result = _reply.createByteArray(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int verify(String name, byte[] data, byte[] signature) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeByteArray(data); - _data.writeByteArray(signature); - mRemote.transact(Stub.TRANSACTION_verify, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public byte[] get_pubkey(String name) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - byte[] _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - mRemote.transact(Stub.TRANSACTION_get_pubkey, _data, _reply, 0); - _reply.readException(); - _result = _reply.createByteArray(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int del_key(String name, int uid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeInt(uid); - mRemote.transact(Stub.TRANSACTION_del_key, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int grant(String name, int granteeUid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeInt(granteeUid); - mRemote.transact(Stub.TRANSACTION_grant, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int ungrant(String name, int granteeUid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeInt(granteeUid); - mRemote.transact(Stub.TRANSACTION_ungrant, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - @Override - public long getmtime(String name) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - long _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - mRemote.transact(Stub.TRANSACTION_getmtime, _data, _reply, 0); - _reply.readException(); - _result = _reply.readLong(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - @Override - public int duplicate(String srcKey, int srcUid, String destKey, int destUid) - throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(srcKey); - _data.writeInt(srcUid); - _data.writeString(destKey); - _data.writeInt(destUid); - mRemote.transact(Stub.TRANSACTION_duplicate, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - @Override - public int is_hardware_backed(String keyType) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(keyType); - mRemote.transact(Stub.TRANSACTION_is_hardware_backed, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - @Override - public int clear_uid(long uid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeLong(uid); - mRemote.transact(Stub.TRANSACTION_clear_uid, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int reset_uid(int uid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(uid); - mRemote.transact(Stub.TRANSACTION_reset_uid, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int sync_uid(int srcUid, int dstUid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(srcUid); - _data.writeInt(dstUid); - mRemote.transact(Stub.TRANSACTION_sync_uid, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - public int password_uid(String password, int uid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(password); - _data.writeInt(uid); - mRemote.transact(Stub.TRANSACTION_password_uid, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - } - - private static final String DESCRIPTOR = "android.security.keystore"; - - static final int TRANSACTION_test = IBinder.FIRST_CALL_TRANSACTION + 0; - static final int TRANSACTION_get = IBinder.FIRST_CALL_TRANSACTION + 1; - static final int TRANSACTION_insert = IBinder.FIRST_CALL_TRANSACTION + 2; - static final int TRANSACTION_del = IBinder.FIRST_CALL_TRANSACTION + 3; - static final int TRANSACTION_exist = IBinder.FIRST_CALL_TRANSACTION + 4; - static final int TRANSACTION_saw = IBinder.FIRST_CALL_TRANSACTION + 5; - static final int TRANSACTION_reset = IBinder.FIRST_CALL_TRANSACTION + 6; - static final int TRANSACTION_password = IBinder.FIRST_CALL_TRANSACTION + 7; - static final int TRANSACTION_lock = IBinder.FIRST_CALL_TRANSACTION + 8; - static final int TRANSACTION_unlock = IBinder.FIRST_CALL_TRANSACTION + 9; - static final int TRANSACTION_zero = IBinder.FIRST_CALL_TRANSACTION + 10; - static final int TRANSACTION_generate = IBinder.FIRST_CALL_TRANSACTION + 11; - static final int TRANSACTION_import = IBinder.FIRST_CALL_TRANSACTION + 12; - static final int TRANSACTION_sign = IBinder.FIRST_CALL_TRANSACTION + 13; - static final int TRANSACTION_verify = IBinder.FIRST_CALL_TRANSACTION + 14; - static final int TRANSACTION_get_pubkey = IBinder.FIRST_CALL_TRANSACTION + 15; - static final int TRANSACTION_del_key = IBinder.FIRST_CALL_TRANSACTION + 16; - static final int TRANSACTION_grant = IBinder.FIRST_CALL_TRANSACTION + 17; - static final int TRANSACTION_ungrant = IBinder.FIRST_CALL_TRANSACTION + 18; - static final int TRANSACTION_getmtime = IBinder.FIRST_CALL_TRANSACTION + 19; - static final int TRANSACTION_duplicate = IBinder.FIRST_CALL_TRANSACTION + 20; - static final int TRANSACTION_is_hardware_backed = IBinder.FIRST_CALL_TRANSACTION + 21; - static final int TRANSACTION_clear_uid = IBinder.FIRST_CALL_TRANSACTION + 22; - static final int TRANSACTION_reset_uid = IBinder.FIRST_CALL_TRANSACTION + 23; - static final int TRANSACTION_sync_uid = IBinder.FIRST_CALL_TRANSACTION + 24; - static final int TRANSACTION_password_uid = IBinder.FIRST_CALL_TRANSACTION + 25; - - /** - * Cast an IBinder object into an IKeystoreService interface, generating - * a proxy if needed. - */ - public static IKeystoreService asInterface(IBinder obj) { - if (obj == null) { - return null; - } - IInterface iin = obj.queryLocalInterface(DESCRIPTOR); - if (iin != null && iin instanceof IKeystoreService) { - return (IKeystoreService) iin; - } - return new IKeystoreService.Stub.Proxy(obj); - } - - /** Construct the stub at attach it to the interface. */ - public Stub() { - attachInterface(this, DESCRIPTOR); - } - - public IBinder asBinder() { - return this; - } - - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - switch (code) { - case INTERFACE_TRANSACTION: { - reply.writeString(DESCRIPTOR); - return true; - } - case TRANSACTION_test: { - data.enforceInterface(DESCRIPTOR); - int resultCode = test(); - reply.writeNoException(); - reply.writeInt(resultCode); - return true; - } - } - return super.onTransact(code, data, reply, flags); - } - } - - public int test() throws RemoteException; - - public byte[] get(String name) throws RemoteException; - - public int insert(String name, byte[] item, int uid, int flags) throws RemoteException; - - public int del(String name, int uid) throws RemoteException; - - public int exist(String name, int uid) throws RemoteException; - - public String[] saw(String name, int uid) throws RemoteException; - - public int reset() throws RemoteException; - - public int password(String password) throws RemoteException; - - public int lock() throws RemoteException; - - public int unlock(String password) throws RemoteException; - - public int zero() throws RemoteException; - - public int generate(String name, int uid, int keyType, int keySize, int flags, byte[][] args) - throws RemoteException; - - public int import_key(String name, byte[] data, int uid, int flags) throws RemoteException; - - public byte[] sign(String name, byte[] data) throws RemoteException; - - public int verify(String name, byte[] data, byte[] signature) throws RemoteException; - - public byte[] get_pubkey(String name) throws RemoteException; - - public int del_key(String name, int uid) throws RemoteException; - - public int grant(String name, int granteeUid) throws RemoteException; - - public int ungrant(String name, int granteeUid) throws RemoteException; - - public long getmtime(String name) throws RemoteException; - - public int duplicate(String srcKey, int srcUid, String destKey, int destUid) - throws RemoteException; - - public int is_hardware_backed(String string) throws RemoteException; - - public int clear_uid(long uid) throws RemoteException; - - public int reset_uid(int uid) throws RemoteException; - - public int sync_uid(int sourceUid, int targetUid) throws RemoteException; - - public int password_uid(String password, int uid) throws RemoteException; -} diff --git a/core/java/android/midi/MidiDevice.aidl b/core/java/android/security/KeystoreArguments.aidl index 11bb497..d636414 100644 --- a/core/java/android/midi/MidiDevice.aidl +++ b/core/java/android/security/KeystoreArguments.aidl @@ -1,5 +1,5 @@ -/* - * Copyright (C) 2014, The Android Open Source Project +/** + * Copyright (c) 2015, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ * limitations under the License. */ -package android.midi; +package android.security; -parcelable MidiDevice; +/* @hide */ +parcelable KeystoreArguments; diff --git a/core/java/android/security/KeystoreArguments.java b/core/java/android/security/KeystoreArguments.java new file mode 100644 index 0000000..16054e5 --- /dev/null +++ b/core/java/android/security/KeystoreArguments.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Class for handling the additional arguments to some keystore binder calls. + * This must be kept in sync with the deserialization code in system/security/keystore. + * @hide + */ +public class KeystoreArguments implements Parcelable { + public byte[][] args; + + public static final Parcelable.Creator<KeystoreArguments> CREATOR = new + Parcelable.Creator<KeystoreArguments>() { + public KeystoreArguments createFromParcel(Parcel in) { + return new KeystoreArguments(in); + } + public KeystoreArguments[] newArray(int size) { + return new KeystoreArguments[size]; + } + }; + + public KeystoreArguments() { + args = null; + } + + public KeystoreArguments(byte[][] args) { + this.args = args; + } + + private KeystoreArguments(Parcel in) { + readFromParcel(in); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + if (args == null) { + out.writeInt(0); + } else { + out.writeInt(args.length); + for (byte[] arg : args) { + out.writeByteArray(arg); + } + } + } + + private void readFromParcel(Parcel in) { + int length = in.readInt(); + args = new byte[length][]; + for (int i = 0; i < length; i++) { + args[i] = in.createByteArray(); + } + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d867adf..f486e0e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8121,6 +8121,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Report an accessibility action to this view's parents for delegated processing. + * + * <p>Implementations of {@link #performAccessibilityAction(int, Bundle)} may internally + * call this method to delegate an accessibility action to a supporting parent. If the parent + * returns true from its + * {@link ViewParent#onNestedPrePerformAccessibilityAction(View, int, android.os.Bundle)} + * method this method will return true to signify that the action was consumed.</p> + * + * <p>This method is useful for implementing nested scrolling child views. If + * {@link #isNestedScrollingEnabled()} returns true and the action is a scrolling action + * a custom view implementation may invoke this method to allow a parent to consume the + * scroll first. If this method returns true the custom view should skip its own scrolling + * behavior.</p> + * + * @param action Accessibility action to delegate + * @param arguments Optional action arguments + * @return true if the action was consumed by a parent + */ + public boolean dispatchNestedPrePerformAccessibilityAction(int action, Bundle arguments) { + for (ViewParent p = getParent(); p != null; p = p.getParent()) { + if (p.onNestedPrePerformAccessibilityAction(this, action, arguments)) { + return true; + } + } + return false; + } + + /** * Performs the specified accessibility action on the view. For * possible accessibility actions look at {@link AccessibilityNodeInfo}. * <p> @@ -8130,6 +8158,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * is responsible for handling this call. * </p> * + * <p>The default implementation will delegate + * {@link AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD} and + * {@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD} to nested scrolling parents if + * {@link #isNestedScrollingEnabled() nested scrolling is enabled} on this view.</p> + * * @param action The action to perform. * @param arguments Optional action arguments. * @return Whether the action was performed. @@ -8150,6 +8183,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (isNestedScrollingEnabled() + && (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD + || action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)) { + if (dispatchNestedPrePerformAccessibilityAction(action, arguments)) { + return true; + } + } + switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { if (isClickable()) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 71c79fd..d03e098 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -32,6 +32,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.os.Build; +import android.os.Bundle; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; @@ -2928,6 +2929,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * {@inheritDoc} + * + * <p>Subclasses should always call <code>super.onNestedPrePerformAccessibilityAction</code></p> + * + * @param target The target view dispatching this action + * @param action Action being performed; see + * {@link android.view.accessibility.AccessibilityNodeInfo} + * @param args Optional action arguments + * @return false by default. Subclasses should return true if they handle the event. + */ + @Override + public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) { + return false; + } + + /** + * {@inheritDoc} */ @Override void dispatchDetachedFromWindow() { diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 87a37f4..035871d 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -17,6 +17,7 @@ package android.view; import android.graphics.Rect; +import android.os.Bundle; import android.view.accessibility.AccessibilityEvent; /** @@ -551,4 +552,23 @@ public interface ViewParent { * @return true if this parent consumed the fling ahead of the target view */ public boolean onNestedPreFling(View target, float velocityX, float velocityY); + + /** + * React to an accessibility action delegated by a target descendant view before the target + * processes it. + * + * <p>This method may be called by a target descendant view if the target wishes to give + * a view in its parent chain a chance to react to the event before normal processing occurs. + * Most commonly this will be a scroll event such as + * {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_SCROLL_FORWARD}. + * A ViewParent that supports acting as a nested scrolling parent should override this + * method and act accordingly to implement scrolling via accesibility systems.</p> + * + * @param target The target view dispatching this action + * @param action Action being performed; see + * {@link android.view.accessibility.AccessibilityNodeInfo} + * @param arguments Optional action arguments + * @return true if the action was consumed by this ViewParent + */ + public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 34c27d7..87d9a58 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -560,39 +560,43 @@ public final class ViewRootImpl implements ViewParent, case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( - "Unable to add window -- token " + attrs.token - + " is not valid; is your activity running?"); + "Unable to add window -- token " + attrs.token + + " is not valid; is your activity running?"); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( - "Unable to add window -- token " + attrs.token - + " is not for an application"); + "Unable to add window -- token " + attrs.token + + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( - "Unable to add window -- app for token " + attrs.token - + " is exiting"); + "Unable to add window -- app for token " + attrs.token + + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( - "Unable to add window -- window " + mWindow - + " has already been added"); + "Unable to add window -- window " + mWindow + + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException( - "Unable to add window " + mWindow + - " -- another window of this type already exists"); + "Unable to add window " + mWindow + + " -- another window of this type already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException( - "Unable to add window " + mWindow + - " -- permission denied for this window type"); + "Unable to add window " + mWindow + + " -- permission denied for this window type"); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException( - "Unable to add window " + mWindow + - " -- the specified display can not be found"); + "Unable to add window " + mWindow + + " -- the specified display can not be found"); + case WindowManagerGlobal.ADD_INVALID_TYPE: + throw new WindowManager.InvalidDisplayException( + "Unable to add window " + mWindow + + " -- the specified window type is not valid"); } throw new RuntimeException( - "Unable to add window -- unknown error code " + res); + "Unable to add window -- unknown error code " + res); } if (view instanceof RootViewSurfaceTaker) { @@ -6413,6 +6417,11 @@ public final class ViewRootImpl implements ViewParent, return false; } + @Override + public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) { + return false; + } + void changeCanvasOpacity(boolean opaque) { Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque); if (mAttachInfo.mHardwareRenderer != null) { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 55c6cb89..3f2f3a5 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -156,7 +156,10 @@ public abstract class Window { public static final String NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME = "android:navigation:background"; - /** The default features enabled */ + /** + * The default features enabled. + * @deprecated use {@link #getDefaultFeatures(android.content.Context)} instead. + */ @Deprecated @SuppressWarnings({"PointlessBitwiseExpression"}) protected static final int DEFAULT_FEATURES = (1 << FEATURE_OPTIONS_PANEL) | @@ -1087,6 +1090,10 @@ public abstract class Window { /** * Sets the window elevation. + * <p> + * Changes to this property take effect immediately and will cause the + * window surface to be recreated. This is an expensive operation and as a + * result, this property should not be animated. * * @param elevation The window elevation. * @see View#setElevation(float) diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 0d3727b..ed17e3f 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -103,6 +103,7 @@ public final class WindowManagerGlobal { public static final int ADD_MULTIPLE_SINGLETON = -7; public static final int ADD_PERMISSION_DENIED = -8; public static final int ADD_INVALID_DISPLAY = -9; + public static final int ADD_INVALID_TYPE = -10; private static WindowManagerGlobal sDefaultWindowManager; private static IWindowManager sWindowManagerService; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 92ca427..6d3b063 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.UndoManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -526,6 +527,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final TextPaint mTextPaint; private boolean mUserSetTextScaleX; private Layout mLayout; + private boolean mLocaleChanged = false; private int mGravity = Gravity.TOP | Gravity.START; private boolean mHorizontallyScrolling; @@ -2740,9 +2742,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see Paint#setTextLocale */ public void setTextLocale(Locale locale) { + mLocaleChanged = true; mTextPaint.setTextLocale(locale); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (!mLocaleChanged) { + mTextPaint.setTextLocale(Locale.getDefault()); + } + } + /** * @return the size (in pixels) of the default text size in this TextView. */ |