diff options
Diffstat (limited to 'core/java/android')
58 files changed, 1716 insertions, 779 deletions
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index aa7692b..dd3d3a8 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -43,6 +43,7 @@ import com.google.android.collect.Maps; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; @@ -2259,6 +2260,9 @@ public class AccountManager { } /** + * Deprecated in favor of {@link #newChooseAccountIntent(Account, List, String[], String, + * String, String[], Bundle)}. + * * Returns an intent to an {@link Activity} that prompts the user to choose from a list of * accounts. * The caller will then typically start the activity by calling @@ -2273,14 +2277,13 @@ public class AccountManager { * null, null, null);</pre> * @param selectedAccount if specified, indicates that the {@link Account} is the currently * selected one, according to the caller's definition of selected. - * @param allowableAccounts an optional {@link ArrayList} of accounts that are allowed to be + * @param allowableAccounts an optional {@link List} of accounts that are allowed to be * shown. If not specified then this field will not limit the displayed accounts. * @param allowableAccountTypes an optional string array of account types. These are used * both to filter the shown accounts and to filter the list of account types that are shown * when adding an account. If not specified then this field will not limit the displayed * account types when adding an account. - * @param alwaysPromptForAccount if set the account chooser screen is always shown, otherwise - * it is only shown when there is more than one account from which to choose + * @param alwaysPromptForAccount boolean that is ignored. * @param descriptionOverrideText if non-null this string is used as the description in the * accounts chooser screen rather than the default * @param addAccountAuthTokenType this string is passed as the {@link #addAccount} @@ -2291,7 +2294,9 @@ public class AccountManager { * parameter * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow. */ - static public Intent newChooseAccountIntent(Account selectedAccount, + @Deprecated + static public Intent newChooseAccountIntent( + Account selectedAccount, ArrayList<Account> allowableAccounts, String[] allowableAccountTypes, boolean alwaysPromptForAccount, @@ -2299,20 +2304,67 @@ public class AccountManager { String addAccountAuthTokenType, String[] addAccountRequiredFeatures, Bundle addAccountOptions) { + return newChooseAccountIntent( + selectedAccount, + allowableAccounts, + allowableAccountTypes, + descriptionOverrideText, + addAccountAuthTokenType, + addAccountRequiredFeatures, + addAccountOptions); + } + + /** + * Returns an intent to an {@link Activity} that prompts the user to choose from a list of + * accounts. + * The caller will then typically start the activity by calling + * <code>startActivityForResult(intent, ...);</code>. + * <p> + * On success the activity returns a Bundle with the account name and type specified using + * keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}. + * <p> + * The most common case is to call this with one account type, e.g.: + * <p> + * <pre> newChooseAccountIntent(null, null, new String[]{"com.google"}, null, null, null, + * null);</pre> + * @param selectedAccount if specified, indicates that the {@link Account} is the currently + * selected one, according to the caller's definition of selected. + * @param allowableAccounts an optional {@link List} of accounts that are allowed to be + * shown. If not specified then this field will not limit the displayed accounts. + * @param allowableAccountTypes an optional string array of account types. These are used + * both to filter the shown accounts and to filter the list of account types that are shown + * when adding an account. If not specified then this field will not limit the displayed + * account types when adding an account. + * @param descriptionOverrideText if non-null this string is used as the description in the + * accounts chooser screen rather than the default + * @param addAccountAuthTokenType this string is passed as the {@link #addAccount} + * authTokenType parameter + * @param addAccountRequiredFeatures this string array is passed as the {@link #addAccount} + * requiredFeatures parameter + * @param addAccountOptions This {@link Bundle} is passed as the {@link #addAccount} options + * parameter + * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow. + */ + static public Intent newChooseAccountIntent( + Account selectedAccount, + List<Account> allowableAccounts, + String[] allowableAccountTypes, + String descriptionOverrideText, + String addAccountAuthTokenType, + String[] addAccountRequiredFeatures, + Bundle addAccountOptions) { Intent intent = new Intent(); ComponentName componentName = ComponentName.unflattenFromString( Resources.getSystem().getString(R.string.config_chooseTypeAndAccountActivity)); intent.setClassName(componentName.getPackageName(), componentName.getClassName()); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST, - allowableAccounts); + allowableAccounts == null ? null : new ArrayList<Account>(allowableAccounts)); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, allowableAccountTypes); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE, addAccountOptions); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_SELECTED_ACCOUNT, selectedAccount); - intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, - alwaysPromptForAccount); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_DESCRIPTION_TEXT_OVERRIDE, descriptionOverrideText); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING, diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index c06b462..133df2b 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -88,9 +88,10 @@ public class ChooseTypeAndAccountActivity extends Activity public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount"; /** - * If true then display the account selection list even if there is just - * one account to choose from. boolean. + * Deprecated. Providing this extra to {@link ChooseTypeAndAccountActivity} + * will have no effect. */ + @Deprecated public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT = "alwaysPromptForAccount"; @@ -117,7 +118,6 @@ public class ChooseTypeAndAccountActivity extends Activity private Set<String> mSetOfRelevantAccountTypes; private String mSelectedAccountName = null; private boolean mSelectedAddNewAccount = false; - private boolean mAlwaysPromptForAccount = false; private String mDescriptionOverride; private ArrayList<Account> mAccounts; @@ -188,7 +188,6 @@ public class ChooseTypeAndAccountActivity extends Activity mSetOfAllowableAccounts = getAllowableAccountSet(intent); mSetOfRelevantAccountTypes = getReleventAccountTypes(intent); - mAlwaysPromptForAccount = intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false); mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); mAccounts = getAcceptableAccountChoices(AccountManager.get(this)); @@ -218,15 +217,6 @@ public class ChooseTypeAndAccountActivity extends Activity } else { startChooseAccountTypeActivity(); } - return; - } - - // if there is only one allowable account return it - if (!mAlwaysPromptForAccount && mAccounts.size() == 1) { - Account account = mAccounts.get(0); - super.onCreate(savedInstanceState); - setResultAndFinish(account.name, account.type); - return; } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 828dc0a..2b4d03b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -180,15 +180,14 @@ public final class ActivityThread { final ApplicationThread mAppThread = new ApplicationThread(); final Looper mLooper = Looper.myLooper(); final H mH = new H(); - final ArrayMap<IBinder, ActivityClientRecord> mActivities - = new ArrayMap<IBinder, ActivityClientRecord>(); + final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. ActivityClientRecord mNewActivities = null; // Number of activities that are currently visible on-screen. int mNumVisibleActivities = 0; - final ArrayMap<IBinder, Service> mServices - = new ArrayMap<IBinder, Service>(); + WeakReference<AssistStructure> mLastAssistStructure; + final ArrayMap<IBinder, Service> mServices = new ArrayMap<>(); AppBindData mBoundApplication; Profiler mProfiler; int mCurDefaultDisplayDpi; @@ -2568,6 +2567,12 @@ public final class ActivityThread { } public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) { + if (mLastAssistStructure != null) { + AssistStructure structure = mLastAssistStructure.get(); + if (structure != null) { + structure.clearSendChannel(); + } + } Bundle data = new Bundle(); AssistStructure structure = null; AssistContent content = new AssistContent(); @@ -2597,6 +2602,7 @@ public final class ActivityThread { if (structure == null) { structure = new AssistStructure(); } + mLastAssistStructure = new WeakReference<>(structure); IActivityManager mgr = ActivityManagerNative.getDefault(); try { mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content, referrer); diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 9ea1606..dc83a01 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -570,12 +570,15 @@ public class AlarmManager { * even when the system is in low-power idle modes. This type of alarm must <b>only</b> * be used for situations where it is actually required that the alarm go off while in * idle -- a reasonable example would be for a calendar notification that should make a - * sound so the user is aware of it. These alarms can significantly impact the power use - * of the device when idle (and thus cause significant battery blame to the app scheduling - * them), so they should be used with care. + * sound so the user is aware of it. When the alarm is dispatched, the app will also be + * added to the system's temporary whitelist for approximately 10 seconds to allow that + * application to acquire further wake locks in which to complete its work.</p> * - * <p>To reduce abuse, there are restrictions on how frequently these alarms will go off - * for a particular application. Under normal system operation, it will not dispatch these + * <p>These alarms can significantly impact the power use + * of the device when idle (and thus cause significant battery blame to the app scheduling + * them), so they should be used with care. To reduce abuse, there are restrictions on how + * frequently these alarms will go off for a particular application. + * Under normal system operation, it will not dispatch these * alarms more than about every minute (at which point every such pending alarm is * dispatched); when in low-power idle modes this duration may be significantly longer, * such as 15 minutes.</p> @@ -619,12 +622,15 @@ public class AlarmManager { * {@link #setAndAllowWhileIdle}. This type of alarm must <b>only</b> * be used for situations where it is actually required that the alarm go off while in * idle -- a reasonable example would be for a calendar notification that should make a - * sound so the user is aware of it. These alarms can significantly impact the power use - * of the device when idle (and thus cause significant battery blame to the app scheduling - * them), so they should be used with care. + * sound so the user is aware of it. When the alarm is dispatched, the app will also be + * added to the system's temporary whitelist for approximately 10 seconds to allow that + * application to acquire further wake locks in which to complete its work.</p> * - * <p>To reduce abuse, there are restrictions on how frequently these alarms will go off - * for a particular application. Under normal system operation, it will not dispatch these + * <p>These alarms can significantly impact the power use + * of the device when idle (and thus cause significant battery blame to the app scheduling + * them), so they should be used with care. To reduce abuse, there are restrictions on how + * frequently these alarms will go off for a particular application. + * Under normal system operation, it will not dispatch these * alarms more than about every minute (at which point every such pending alarm is * dispatched); when in low-power idle modes this duration may be significantly longer, * such as 15 minutes.</p> diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index cb1e7aa..1fb0b2a 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -421,6 +421,15 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public boolean isPermissionRevokedByPolicy(String permName, String pkgName) { + try { + return mPM.isPermissionRevokedByPolicy(permName, pkgName, mContext.getUserId()); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override public boolean addPermission(PermissionInfo info) { try { return mPM.addPermission(info); diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index 6094012..0d09e4a 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -30,6 +30,6 @@ interface ISearchManager { List<ResolveInfo> getGlobalSearchActivities(); ComponentName getGlobalSearchActivity(); ComponentName getWebSearchActivity(); - ComponentName getAssistIntent(int userHandle); - boolean launchAssistAction(String hint, int userHandle, in Bundle args); + void launchAssist(in Bundle args); + boolean launchLegacyAssist(String hint, int userHandle, in Bundle args); } diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 45799a1..9e32164 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -946,27 +946,9 @@ public class SearchManager * * @hide */ - public Intent getAssistIntent(Context context, boolean inclContext) { - return getAssistIntent(context, inclContext, UserHandle.myUserId()); - } - - /** - * Gets an intent for launching installed assistant activity, or null if not available. - * @return The assist intent. - * - * @hide - */ - public Intent getAssistIntent(Context context, boolean inclContext, int userHandle) { + public Intent getAssistIntent(boolean inclContext) { try { - if (mService == null) { - return null; - } - ComponentName comp = mService.getAssistIntent(userHandle); - if (comp == null) { - return null; - } Intent intent = new Intent(Intent.ACTION_ASSIST); - intent.setComponent(comp); if (inclContext) { IActivityManager am = ActivityManagerNative.getDefault(); Bundle extras = am.getAssistContextExtras(ActivityManager.ASSIST_CONTEXT_BASIC); @@ -982,17 +964,38 @@ public class SearchManager } /** - * Launch an assist action for the current top activity. + * Starts the assistant. + * + * @param args the args to pass to the assistant + * + * @hide + */ + public void launchAssist(Bundle args) { + try { + if (mService == null) { + return; + } + mService.launchAssist(args); + } catch (RemoteException re) { + Log.e(TAG, "launchAssist() failed: " + re); + } + } + + /** + * Starts the legacy assistant (i.e. the {@link Intent#ACTION_ASSIST}). + * + * @param args the args to pass to the assistant + * * @hide */ - public boolean launchAssistAction(String hint, int userHandle, Bundle args) { + public boolean launchLegacyAssist(String hint, int userHandle, Bundle args) { try { if (mService == null) { return false; } - return mService.launchAssistAction(hint, userHandle, args); + return mService.launchLegacyAssist(hint, userHandle, args); } catch (RemoteException re) { - Log.e(TAG, "launchAssistAction() failed: " + re); + Log.e(TAG, "launchAssist() failed: " + re); return false; } } diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 73c551f..9673c98 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -30,6 +30,9 @@ import java.util.ArrayList; public class AssistStructure implements Parcelable { static final String TAG = "AssistStructure"; + static final boolean DEBUG_PARCEL = false; + static final boolean DEBUG_PARCEL_TREE = false; + boolean mHaveData; ComponentName mActivityComponent; @@ -46,12 +49,40 @@ public class AssistStructure implements Parcelable { static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1; static final String DESCRIPTOR = "android.app.AssistStructure"; - final class SendChannel extends Binder { + final static class SendChannel extends Binder { + volatile AssistStructure mAssistStructure; + + SendChannel(AssistStructure as) { + mAssistStructure = as; + } + @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (code == TRANSACTION_XFER) { + AssistStructure as = mAssistStructure; + if (as == null) { + return true; + } + data.enforceInterface(DESCRIPTOR); - writeContentToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + IBinder token = data.readStrongBinder(); + if (DEBUG_PARCEL) Log.d(TAG, "Request for data on " + as + + " using token " + token); + if (token != null) { + if (DEBUG_PARCEL) Log.d(TAG, "Resuming partial write of " + token); + if (token instanceof ParcelTransferWriter) { + ParcelTransferWriter xfer = (ParcelTransferWriter)token; + xfer.writeToParcel(as, reply); + return true; + } + Log.w(TAG, "Caller supplied bad token type: " + token); + // Don't write anything; this is the end of the data. + return true; + } + //long start = SystemClock.uptimeMillis(); + ParcelTransferWriter xfer = new ParcelTransferWriter(as, reply); + xfer.writeToParcel(as, reply); + //Log.i(TAG, "Time to parcel: " + (SystemClock.uptimeMillis()-start) + "ms"); return true; } else { return super.onTransact(code, data, reply, flags); @@ -59,6 +90,235 @@ public class AssistStructure implements Parcelable { } } + final static class ViewStackEntry { + ViewNode node; + int curChild; + int numChildren; + } + + final static class ParcelTransferWriter extends Binder { + final boolean mWriteStructure; + int mCurWindow; + int mNumWindows; + final ArrayList<ViewStackEntry> mViewStack = new ArrayList<>(); + ViewStackEntry mCurViewStackEntry; + int mCurViewStackPos; + int mNumWrittenWindows; + int mNumWrittenViews; + final float[] mTmpMatrix = new float[9]; + + ParcelTransferWriter(AssistStructure as, Parcel out) { + mWriteStructure = as.waitForReady(); + ComponentName.writeToParcel(as.mActivityComponent, out); + mNumWindows = as.mWindowNodes.size(); + if (mWriteStructure && mNumWindows > 0) { + out.writeInt(mNumWindows); + } else { + out.writeInt(0); + } + } + + void writeToParcel(AssistStructure as, Parcel out) { + int start = out.dataPosition(); + mNumWrittenWindows = 0; + mNumWrittenViews = 0; + boolean more = writeToParcelInner(as, out); + Log.i(TAG, "Flattened " + (more ? "partial" : "final") + " assist data: " + + (out.dataPosition() - start) + + " bytes, containing " + mNumWrittenWindows + " windows, " + + mNumWrittenViews + " views"); + } + + boolean writeToParcelInner(AssistStructure as, Parcel out) { + if (mNumWindows == 0) { + return false; + } + if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringWriter @ " + out.dataPosition()); + PooledStringWriter pwriter = new PooledStringWriter(out); + while (writeNextEntryToParcel(as, out, pwriter)) { + // If the parcel contains more than 100K of data, then we are getting too + // large for a single IPC so stop here and let the caller come back when it + // is ready for more. + if (out.dataSize() > 1024*1024) { + if (DEBUG_PARCEL) Log.d(TAG, "Assist data size is " + out.dataSize() + + " @ pos " + out.dataPosition() + "; returning partial result"); + out.writeInt(0); + out.writeStrongBinder(this); + if (DEBUG_PARCEL) Log.d(TAG, "Finishing PooledStringWriter @ " + + out.dataPosition() + ", size " + pwriter.getStringCount()); + pwriter.finish(); + return true; + } + } + if (DEBUG_PARCEL) Log.d(TAG, "Finishing PooledStringWriter @ " + + out.dataPosition() + ", size " + pwriter.getStringCount()); + pwriter.finish(); + mViewStack.clear(); + return false; + } + + void pushViewStackEntry(ViewNode node, int pos) { + ViewStackEntry entry; + if (pos >= mViewStack.size()) { + entry = new ViewStackEntry(); + mViewStack.add(entry); + if (DEBUG_PARCEL_TREE) Log.d(TAG, "New stack entry at " + pos + ": " + entry); + } else { + entry = mViewStack.get(pos); + if (DEBUG_PARCEL_TREE) Log.d(TAG, "Existing stack entry at " + pos + ": " + entry); + } + entry.node = node; + entry.numChildren = node.getChildCount(); + entry.curChild = 0; + mCurViewStackEntry = entry; + } + + boolean writeNextEntryToParcel(AssistStructure as, Parcel out, PooledStringWriter pwriter) { + // Write next view node if appropriate. + if (mCurViewStackEntry != null) { + if (mCurViewStackEntry.curChild < mCurViewStackEntry.numChildren) { + // Write the next child in the current view. + if (DEBUG_PARCEL_TREE) Log.d(TAG, "Writing child #" + + mCurViewStackEntry.curChild + " in " + mCurViewStackEntry.node); + ViewNode child = mCurViewStackEntry.node.mChildren[mCurViewStackEntry.curChild]; + mCurViewStackEntry.curChild++; + if (DEBUG_PARCEL) Log.d(TAG, "write view: at " + out.dataPosition() + + ", windows=" + mNumWrittenWindows + + ", views=" + mNumWrittenViews); + out.writeInt(1); + int flags = child.writeSelfToParcel(out, pwriter, mTmpMatrix); + mNumWrittenViews++; + // If the child has children, push it on the stack to write them next. + if ((flags&ViewNode.FLAGS_HAS_CHILDREN) != 0) { + if (DEBUG_PARCEL_TREE) Log.d(TAG, "Preparing to write " + + child.mChildren.length + " children under " + child); + out.writeInt(child.mChildren.length); + int pos = ++mCurViewStackPos; + pushViewStackEntry(child, pos); + } + return true; + } + + // We are done writing children of the current view; pop off the stack. + do { + int pos = --mCurViewStackPos; + if (DEBUG_PARCEL_TREE) Log.d(TAG, "Done with " + mCurViewStackEntry.node + + "; popping up to " + pos); + if (pos < 0) { + // Reached the last view; step to next window. + if (DEBUG_PARCEL_TREE) Log.d(TAG, "Done with view hierarchy!"); + mCurViewStackEntry = null; + break; + } + mCurViewStackEntry = mViewStack.get(pos); + } while (mCurViewStackEntry.curChild >= mCurViewStackEntry.numChildren); + return true; + } + + // Write the next window if appropriate. + int pos = mCurWindow; + if (pos < mNumWindows) { + WindowNode win = as.mWindowNodes.get(pos); + mCurWindow++; + if (DEBUG_PARCEL) Log.d(TAG, "write window #" + pos + ": at " + out.dataPosition() + + ", windows=" + mNumWrittenWindows + + ", views=" + mNumWrittenViews); + out.writeInt(1); + win.writeSelfToParcel(out, pwriter, mTmpMatrix); + mNumWrittenWindows++; + ViewNode root = win.mRoot; + mCurViewStackPos = 0; + if (DEBUG_PARCEL_TREE) Log.d(TAG, "Pushing initial root view " + root); + pushViewStackEntry(root, 0); + return true; + } + + return false; + } + } + + final class ParcelTransferReader { + final float[] mTmpMatrix = new float[9]; + PooledStringReader mStringReader; + + int mNumReadWindows; + int mNumReadViews; + + private final IBinder mChannel; + private IBinder mTransferToken; + private Parcel mCurParcel; + + ParcelTransferReader(IBinder channel) { + mChannel = channel; + } + + void go() { + fetchData(); + mActivityComponent = ComponentName.readFromParcel(mCurParcel); + final int N = mCurParcel.readInt(); + if (N > 0) { + if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringReader @ " + + mCurParcel.dataPosition()); + mStringReader = new PooledStringReader(mCurParcel); + if (DEBUG_PARCEL) Log.d(TAG, "PooledStringReader size = " + + mStringReader.getStringCount()); + for (int i=0; i<N; i++) { + mWindowNodes.add(new WindowNode(this)); + } + } + if (DEBUG_PARCEL) Log.d(TAG, "Finished reading: at " + mCurParcel.dataPosition() + + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows + + ", views=" + mNumReadViews); + } + + Parcel readParcel() { + if (DEBUG_PARCEL) Log.d(TAG, "readParcel: at " + mCurParcel.dataPosition() + + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows + + ", views=" + mNumReadViews); + if (mCurParcel.readInt() != 0) { + return mCurParcel; + } + // We have run out of partial data, need to read another batch. + mTransferToken = mCurParcel.readStrongBinder(); + if (mTransferToken == null) { + throw new IllegalStateException( + "Reached end of partial data without transfer token"); + } + if (DEBUG_PARCEL) Log.d(TAG, "Ran out of partial data at " + + mCurParcel.dataPosition() + ", token " + mTransferToken); + fetchData(); + if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringReader @ " + + mCurParcel.dataPosition()); + mStringReader = new PooledStringReader(mCurParcel); + if (DEBUG_PARCEL) Log.d(TAG, "PooledStringReader size = " + + mStringReader.getStringCount()); + if (DEBUG_PARCEL) Log.d(TAG, "readParcel: at " + mCurParcel.dataPosition() + + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows + + ", views=" + mNumReadViews); + mCurParcel.readInt(); + return mCurParcel; + } + + private void fetchData() { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(DESCRIPTOR); + data.writeStrongBinder(mTransferToken); + if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken); + if (mCurParcel != null) { + mCurParcel.recycle(); + } + mCurParcel = Parcel.obtain(); + try { + mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0); + } catch (RemoteException e) { + Log.w(TAG, "Failure reading AssistStructure data", e); + throw new IllegalStateException("Failure reading AssistStructure data: " + e); + } + data.recycle(); + mNumReadWindows = mNumReadViews = 0; + } + } + final static class ViewNodeText { CharSequence mText; float mTextSize; @@ -145,24 +405,25 @@ public class AssistStructure implements Parcelable { view.dispatchProvideStructure(builder); } - WindowNode(Parcel in, PooledStringReader preader, float[] tmpMatrix) { + WindowNode(ParcelTransferReader reader) { + Parcel in = reader.readParcel(); + reader.mNumReadWindows++; mX = in.readInt(); mY = in.readInt(); mWidth = in.readInt(); mHeight = in.readInt(); mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mDisplayId = in.readInt(); - mRoot = new ViewNode(in, preader, tmpMatrix); + mRoot = new ViewNode(reader); } - int writeToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) { + void writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) { out.writeInt(mX); out.writeInt(mY); out.writeInt(mWidth); out.writeInt(mHeight); TextUtils.writeToParcel(mTitle, out, 0); out.writeInt(mDisplayId); - return mRoot.writeToParcel(out, pwriter, tmpMatrix); } /** @@ -287,7 +548,10 @@ public class AssistStructure implements Parcelable { ViewNode() { } - ViewNode(Parcel in, PooledStringReader preader, float[] tmpMatrix) { + ViewNode(ParcelTransferReader reader) { + final Parcel in = reader.readParcel(); + reader.mNumReadViews++; + final PooledStringReader preader = reader.mStringReader; mClassName = preader.readString(); mFlags = in.readInt(); final int flags = mFlags; @@ -320,8 +584,8 @@ public class AssistStructure implements Parcelable { } if ((flags&FLAGS_HAS_MATRIX) != 0) { mMatrix = new Matrix(); - in.readFloatArray(tmpMatrix); - mMatrix.setValues(tmpMatrix); + in.readFloatArray(reader.mTmpMatrix); + mMatrix.setValues(reader.mTmpMatrix); } if ((flags&FLAGS_HAS_ELEVATION) != 0) { mElevation = in.readFloat(); @@ -342,12 +606,12 @@ public class AssistStructure implements Parcelable { final int NCHILDREN = in.readInt(); mChildren = new ViewNode[NCHILDREN]; for (int i=0; i<NCHILDREN; i++) { - mChildren[i] = new ViewNode(in, preader, tmpMatrix); + mChildren[i] = new ViewNode(reader); } } } - int writeToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) { + int writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) { int flags = mFlags & ~FLAGS_ALL_CONTROL; if (mId != View.NO_ID) { flags |= FLAGS_HAS_ID; @@ -428,15 +692,7 @@ public class AssistStructure implements Parcelable { if ((flags&FLAGS_HAS_EXTRAS) != 0) { out.writeBundle(mExtras); } - int N = 1; - if ((flags&FLAGS_HAS_CHILDREN) != 0) { - final int NCHILDREN = mChildren.length; - out.writeInt(NCHILDREN); - for (int i=0; i<NCHILDREN; i++) { - N += mChildren[i].writeToParcel(out, pwriter, tmpMatrix); - } - } - return N; + return flags; } /** @@ -1177,22 +1433,11 @@ public class AssistStructure implements Parcelable { return; } mHaveData = true; - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(DESCRIPTOR); - try { - mReceiveChannel.transact(TRANSACTION_XFER, data, reply, 0); - } catch (RemoteException e) { - Log.w(TAG, "Failure reading AssistStructure data", e); - return; - } - readContentFromParcel(reply); - data.recycle(); - reply.recycle(); + ParcelTransferReader reader = new ParcelTransferReader(mReceiveChannel); + reader.go(); } - void writeContentToParcel(Parcel out, int flags) { - // First make sure all content has been created. + boolean waitForReady() { boolean skipStructure = false; synchronized (this) { long endTime = SystemClock.uptimeMillis() + 5000; @@ -1210,30 +1455,14 @@ public class AssistStructure implements Parcelable { skipStructure = true; } } - int start = out.dataPosition(); - PooledStringWriter pwriter = new PooledStringWriter(out); - float[] tmpMatrix = new float[9]; - ComponentName.writeToParcel(mActivityComponent, out); - final int N = skipStructure ? 0 : mWindowNodes.size(); - out.writeInt(N); - int NV = 0; - for (int i=0; i<N; i++) { - NV += mWindowNodes.get(i).writeToParcel(out, pwriter, tmpMatrix); - } - pwriter.finish(); - Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes, containing " - + N + " windows, " + NV + " views"); + return !skipStructure; } - void readContentFromParcel(Parcel in) { - PooledStringReader preader = new PooledStringReader(in); - float[] tmpMatrix = new float[9]; - mActivityComponent = ComponentName.readFromParcel(in); - final int N = in.readInt(); - for (int i=0; i<N; i++) { - mWindowNodes.add(new WindowNode(in, preader, tmpMatrix)); + /** @hide */ + public void clearSendChannel() { + if (mSendChannel != null) { + mSendChannel.mAssistStructure = null; } - //dump(); } public int describeContents() { @@ -1245,7 +1474,7 @@ public class AssistStructure implements Parcelable { // This object holds its data. We want to write a send channel that the // other side can use to retrieve that data. if (mSendChannel == null) { - mSendChannel = new SendChannel(); + mSendChannel = new SendChannel(this); } out.writeStrongBinder(mSendChannel); } else { diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 6302521..fb81fd1 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -240,6 +240,8 @@ public final class BluetoothSocket implements Closeable { as.close(); throw new IOException("bt socket acept failed"); } + + as.mPfd = new ParcelFileDescriptor(fds[0]); as.mSocket = new LocalSocket(fds[0]); as.mSocketIS = as.mSocket.getInputStream(); as.mSocketOS = as.mSocket.getOutputStream(); diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index 72abeaf..3660be7 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -36,7 +36,7 @@ interface IBluetoothGatt { void startScan(in int appIf, in boolean isServer, in ScanSettings settings, in List<ScanFilter> filters, - in List scanStorages); + in List scanStorages, in String callingPackage); void stopScan(in int appIf, in boolean isServer); void flushPendingBatchResults(in int appIf, in boolean isServer); void startMultiAdvertising(in int appIf, diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java index 2e6c4f0..e09ab56 100644 --- a/core/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java @@ -19,6 +19,7 @@ package android.bluetooth.le; import android.Manifest; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.ActivityThread; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallbackWrapper; @@ -313,7 +314,7 @@ public final class BluetoothLeScanner { mClientIf = clientIf; try { mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters, - mResultStorages); + mResultStorages, ActivityThread.currentOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "fail to start le scan: " + e); mClientIf = -1; diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 2b83d86..ceb610a 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -502,8 +502,7 @@ interface IPackageManager { void addOnPermissionsChangeListener(in IOnPermissionsChangeListener listener); void removeOnPermissionsChangeListener(in IOnPermissionsChangeListener listener); - - int getMountExternalMode(int uid); - void grantDefaultPermissionsToEnabledCarrierApps(in String[] packageNames, int userId); + + boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId); } diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java index 96000dd..4dbac05 100644 --- a/core/java/android/content/pm/IntentFilterVerificationInfo.java +++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java @@ -26,7 +26,9 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; + import com.android.internal.util.XmlUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -181,14 +183,28 @@ public final class IntentFilterVerificationInfo implements Parcelable { return getStatusStringFromValue(mMainStatus); } - public static String getStatusStringFromValue(int val) { - switch (val) { - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK : return "ask"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS : return "always"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER : return "never"; + public static String getStatusStringFromValue(long val) { + StringBuilder sb = new StringBuilder(); + switch ((int)(val >> 32)) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: + sb.append("always : "); + sb.append(Long.toHexString(val & 0x00000000FFFFFFFF)); + break; + + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: + sb.append("ask"); + break; + + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: + sb.append("never"); + break; + + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: default: - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED : return "undefined"; + sb.append("undefined"); + break; } + return sb.toString(); } @Override diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 62c2e8c..6533bbc 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2398,7 +2398,7 @@ public abstract class PackageManager { * Check whether a particular package has been granted a particular * permission. * - * @param permName The name of the permission you are checking for, + * @param permName The name of the permission you are checking for. * @param pkgName The name of the package you are checking against. * * @return If the package has the permission, PERMISSION_GRANTED is @@ -2412,6 +2412,21 @@ public abstract class PackageManager { public abstract int checkPermission(String permName, String pkgName); /** + * Checks whether a particular permissions has been revoked for a + * package by policy. Typically the device owner or the profile owner + * may apply such a policy. The user cannot grant policy revoked + * permissions, hence the only way for an app to get such a permission + * is by a policy change. + * + * @param permName The name of the permission you are checking for. + * @param pkgName The name of the package you are checking against. + * + * @return Whether the permission is restricted by policy. + */ + @CheckResult + public abstract boolean isPermissionRevokedByPolicy(String permName, String pkgName); + + /** * Add a new dynamic permission to the system. For this to work, your * package must have defined a permission tree through the * {@link android.R.styleable#AndroidManifestPermissionTree diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 48ffb98..ed7a2a3 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2835,7 +2835,6 @@ public class PackageParser { if (!aii.hasAction(Intent.ACTION_DEFAULT)) continue; if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) || aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) { - Slog.d(TAG, "hasDomainURLs:true for package:" + pkg.packageName); return true; } } diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index 92b8055..9b28401 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -38,6 +38,7 @@ public class PackageUserState { public ArraySet<String> enabledComponents; public int domainVerificationStatus; + public int appLinkGeneration; public PackageUserState() { installed = true; @@ -60,5 +61,6 @@ public class PackageUserState { ? new ArraySet<>(o.enabledComponents) : null; blockUninstall = o.blockUninstall; domainVerificationStatus = o.domainVerificationStatus; + appLinkGeneration = o.appLinkGeneration; } } diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 579634f..19921b5 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -603,7 +603,7 @@ public class ColorStateList implements Parcelable { * @hide only for resource preloading */ public ConstantState<ColorStateList> getConstantState() { - if (mFactory != null) { + if (mFactory == null) { mFactory = new ColorStateListFactory(this); } return mFactory; diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 9a99a46..731903c 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1918,6 +1918,7 @@ public class Resources { other.mResId = mResId; other.mForce = mForce; other.mCount = mCount; + other.mHashCode = mHashCode; return other; } } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index e7deae8..30cdfd3 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -123,7 +123,10 @@ public class SystemSensorManager extends SensorManager { SensorEventQueue queue = mSensorListeners.get(listener); if (queue == null) { Looper looper = (handler != null) ? handler.getLooper() : mMainLooper; - queue = new SensorEventQueue(listener, looper, this); + final String fullClassName = listener.getClass().getEnclosingClass() != null ? + listener.getClass().getEnclosingClass().getName() : + listener.getClass().getName(); + queue = new SensorEventQueue(listener, looper, this, fullClassName); if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) { queue.dispose(); return false; @@ -166,12 +169,17 @@ public class SystemSensorManager extends SensorManager { protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) { if (sensor == null) throw new IllegalArgumentException("sensor cannot be null"); + if (listener == null) throw new IllegalArgumentException("listener cannot be null"); + if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) return false; synchronized (mTriggerListeners) { TriggerEventQueue queue = mTriggerListeners.get(listener); if (queue == null) { - queue = new TriggerEventQueue(listener, mMainLooper, this); + final String fullClassName = listener.getClass().getEnclosingClass() != null ? + listener.getClass().getEnclosingClass().getName() : + listener.getClass().getName(); + queue = new TriggerEventQueue(listener, mMainLooper, this, fullClassName); if (!queue.addSensor(sensor, 0, 0)) { queue.dispose(); return false; @@ -234,7 +242,8 @@ public class SystemSensorManager extends SensorManager { } // Initialize a client for data_injection. if (mInjectEventQueue == null) { - mInjectEventQueue = new InjectEventQueue(mMainLooper, this); + mInjectEventQueue = new InjectEventQueue(mMainLooper, this, + mContext.getPackageName()); } } else { // If data injection is being disabled clean up the native resources. @@ -296,10 +305,11 @@ public class SystemSensorManager extends SensorManager { protected static final int OPERATING_MODE_NORMAL = 0; protected static final int OPERATING_MODE_DATA_INJECTION = 1; - BaseEventQueue(Looper looper, SystemSensorManager manager, int mode) { + BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) { + if (packageName == null) packageName = ""; nSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance, new WeakReference<>(this), looper.getQueue(), mScratch, - manager.mContext.getPackageName(), mode, manager.mContext.getOpPackageName()); + packageName, mode, manager.mContext.getOpPackageName()); mCloseGuard.open("dispose"); mManager = manager; } @@ -419,8 +429,8 @@ public class SystemSensorManager extends SensorManager { private final SparseArray<SensorEvent> mSensorsEvents = new SparseArray<SensorEvent>(); public SensorEventQueue(SensorEventListener listener, Looper looper, - SystemSensorManager manager) { - super(looper, manager, OPERATING_MODE_NORMAL); + SystemSensorManager manager, String packageName) { + super(looper, manager, OPERATING_MODE_NORMAL, packageName); mListener = listener; } @@ -486,8 +496,8 @@ public class SystemSensorManager extends SensorManager { private final SparseArray<TriggerEvent> mTriggerEvents = new SparseArray<TriggerEvent>(); public TriggerEventQueue(TriggerEventListener listener, Looper looper, - SystemSensorManager manager) { - super(looper, manager, OPERATING_MODE_NORMAL); + SystemSensorManager manager, String packageName) { + super(looper, manager, OPERATING_MODE_NORMAL, packageName); mListener = listener; } @@ -540,8 +550,8 @@ public class SystemSensorManager extends SensorManager { } final class InjectEventQueue extends BaseEventQueue { - public InjectEventQueue(Looper looper, SystemSensorManager manager) { - super(looper, manager, OPERATING_MODE_DATA_INJECTION); + public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) { + super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName); } int injectSensorData(int handle, float[] values,int accuracy, long timestamp) { diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 62ebfb3..46cafad 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -482,17 +482,6 @@ public abstract class CameraCaptureSession implements AutoCloseable { public abstract boolean isReprocessable(); /** - * Return if this capture session is constrained high speed session that is created by - * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}. - * - * @return {@code true} if this session is constrained high speed capture session, - * {@code false} otherwise. - * - * @see CameraDevice#createConstrainedHighSpeedCaptureSession - */ - public abstract boolean isConstrainedHighSpeed(); - - /** * Get the input Surface associated with a reprocessable capture session. * * <p>Each reprocessable capture session has an input {@link Surface} where the reprocess diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 547cce4..30aa2d5 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -664,7 +664,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * the max possible number of frames the camera device will group together for this high * speed stream configuration. This max batch size will be used to generate a high speed * recording request list by - * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedRequestList }. + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList }. * The max batch size for each configuration will satisfy below conditions:</p> * <ul> * <li>Each max batch size will be a divisor of its corresponding fps_max / 30. For example, diff --git a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java new file mode 100644 index 0000000..07d2443 --- /dev/null +++ b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java @@ -0,0 +1,131 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2; + +import android.annotation.NonNull; +import android.hardware.camera2.params.StreamConfigurationMap; + +import java.util.List; + +/** + * A constrained high speed capture session for a {@link CameraDevice}, used for capturing high + * speed images from the {@link CameraDevice} for high speed video recording use case. + * <p> + * A CameraHighSpeedCaptureSession is created by providing a set of target output surfaces to + * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, Once created, the session is + * active until a new session is created by the camera device, or the camera device is closed. + * </p> + * <p> + * An active high speed capture session is a specialized capture session that is only targeted at + * high speed video recording (>=120fps) use case if the camera device supports high speed video + * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains + * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}). It only + * accepts request lists created via {@link #createHighSpeedRequestList}, and the request list can + * only be submitted to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or + * {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}. See + * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} for more details of the + * limitations. + * </p> + * <p> + * Creating a session is an expensive operation and can take several hundred milliseconds, since it + * requires configuring the camera device's internal pipelines and allocating memory buffers for + * sending images to the desired targets. Therefore the setup is done asynchronously, and + * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} will send the ready-to-use + * CameraCaptureSession to the provided listener's + * {@link CameraCaptureSession.StateCallback#onConfigured} callback. If configuration cannot be + * completed, then the {@link CameraCaptureSession.StateCallback#onConfigureFailed} is called, and + * the session will not become active. + * </p> + * <!-- + * <p> + * Any capture requests (repeating or non-repeating) submitted before the session is ready will be + * queued up and will begin capture once the session becomes ready. In case the session cannot be + * configured and {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is + * called, all queued capture requests are discarded. </p> + * --> + * <p> + * If a new session is created by the camera device, then the previous session is closed, and its + * associated {@link CameraCaptureSession.StateCallback#onClosed onClosed} callback will be + * invoked. All of the session methods will throw an IllegalStateException if called once the + * session is closed. + * </p> + * <p> + * A closed session clears any repeating requests (as if {@link #stopRepeating} had been called), + * but will still complete all of its in-progress capture requests as normal, before a newly created + * session takes over and reconfigures the camera device. + * </p> + */ +public abstract class CameraConstrainedHighSpeedCaptureSession extends CameraCaptureSession { + + /** + * <p>Create a unmodifiable list of requests that is suitable for constrained high speed capture + * session streaming.</p> + * + * <p>High speed video streaming creates significant performance pressure on the camera device, + * so to achieve efficient high speed streaming, the camera device may have to aggregate + * multiple frames together. This means requests must be sent in batched groups, with all + * requests sharing the same settings. This method takes the list of output target + * Surfaces (subject to the output Surface requirements specified by the constrained high speed + * session) and a {@link CaptureRequest request}, and generates a request list that has the same + * controls for each request. The input {@link CaptureRequest request} must contain the target + * output Surfaces and target high speed FPS range that is one of the + * {@link StreamConfigurationMap#getHighSpeedVideoFpsRangesFor} for the Surface size.</p> + * + * <p>If both preview and recording Surfaces are specified in the {@code request}, the + * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE target FPS range} in the input + * {@link CaptureRequest request} must be a fixed frame rate FPS range, where the + * {@link android.util.Range#getLower minimal FPS} == + * {@link android.util.Range#getUpper() maximum FPS}. The created request list will contain + * a interleaved request pattern such that the preview output FPS is at least 30fps, the + * recording output FPS is {@link android.util.Range#getUpper() maximum FPS} of the requested + * FPS range. The application can submit this request list directly to an active high speed + * capture session to achieve high speed video recording. When only preview or recording + * Surface is specified, this method will return a list of request that have the same controls + * and output targets for all requests.</p> + * + * <p>Submitting a request list created by this method to a normal capture session will result + * in an {@link IllegalArgumentException} if the high speed + * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE FPS range} is not supported by + * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}.</p> + * + * @param request The high speed capture request that will be used to generate the high speed + * request list. + * @return A unmodifiable CaptureRequest list that is suitable for constrained high speed + * capture. + * + * @throws IllegalArgumentException if the set of output Surfaces in the request do not meet the + * high speed video capability requirements, or the camera + * device doesn't support high speed video capability, or the + * request doesn't meet the high speed video capability + * requirements, or the request doesn't contain the required + * controls for high speed capture. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CameraDevice#createConstrainedHighSpeedCaptureSession + * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE + * @see android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes + * @see android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRangesFor + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO + */ + @NonNull + public abstract List<CaptureRequest> createHighSpeedRequestList( + @NonNull CaptureRequest request) throws CameraAccessException; + +} diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 639c8b1..4a71aa0 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -613,8 +613,9 @@ public abstract class CameraDevice implements AutoCloseable { * When multiple Surfaces are configured, their size must be same.</li> * * <li>An active high speed capture session only accepts request lists created via - * {@link #createConstrainedHighSpeedRequestList}, and the request list can only be submitted - * to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or + * {@link CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}, and the + * request list can only be submitted to this session via + * {@link CameraCaptureSession#captureBurst captureBurst}, or * {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}.</li> * * <li>The FPS ranges being requested to this session must be selected from @@ -661,71 +662,13 @@ public abstract class CameraDevice implements AutoCloseable { * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO * @see CameraCaptureSession#captureBurst * @see CameraCaptureSession#setRepeatingBurst - * @see #createConstrainedHighSpeedRequestList + * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList */ public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) throws CameraAccessException; - - /** - * <p>Create a unmodifiable list of requests that is suitable for constrained high speed capture - * session streaming.</p> - * - * <p>High speed video streaming creates significant performance pressue on the camera device, - * so to achieve efficient high speed streaming, the camera device may have to aggregate - * multiple frames together. This means requests must be sent in batched groups, with all - * requests sharing the same settings. This method takes the list of output target - * Surfaces (subject to the output Surface requirements specified by the contrained high speed - * session) and a {@link CaptureRequest request}, and generates a request list that has the same - * controls for each request. The input {@link CaptureRequest request} must contain the target - * output Surfaces and target high speed FPS range that is one of the - * {@link StreamConfigurationMap#getHighSpeedVideoFpsRangesFor} for the Surface size.</p> - * - * <p>If both preview and recording Surfaces are specified in the {@code request}, the - * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE target FPS range} in the input - * {@link CaptureRequest request} must be a fixed framerate FPS range, where the - * {@link android.util.Range#getLower minimal FPS} == - * {@link android.util.Range#getUpper() maximum FPS}. The created request list will contain - * a interleaved request pattern such that the preview output FPS is at least 30fps, the - * recording output FPS is {@link android.util.Range#getUpper() maximum FPS} of the requested - * FPS range. The application can submit this request list directly to an active high speed - * capture session to achieve high speed video recording. When only preview or recording - * Surface is specified, this method will return a list of request that have the same controls - * and output targets for all requests.</p> - * - * <p>Submitting a request list created by this method to a normal capture session will result - * in an {@link IllegalArgumentException} if the high speed - * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE FPS range} is not supported by - * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}.</p> - * - * @param request The high speed capture request that will be used to generate the high speed - * request list. - * @return A unmodifiable CaptureRequest list that is suitable for constrained high speed - * capture. - * - * @throws IllegalArgumentException if the set of output Surfaces in the request do not meet the - * high speed video capability requirements, or the camera - * device doesn't support high speed video capability, or the - * request doesn't meet the high speed video capability - * requirements, or the request doesn't contain the required - * controls for high speed capture. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see #createConstrainedHighSpeedCaptureSession - * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE - * @see StreamConfigurationMap#getHighSpeedVideoSizes - * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor - * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO - */ - @NonNull - public abstract List<CaptureRequest> createConstrainedHighSpeedRequestList( - @NonNull CaptureRequest request)throws CameraAccessException; - /** * <p>Create a {@link CaptureRequest.Builder} for new capture requests, * initialized with template for a target use case. The settings are chosen diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 0aa6447..c36683b 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -655,8 +655,8 @@ public abstract class CameraMetadata<TKey> { * <p>The device supports constrained high speed video recording (frame rate >=120fps) * use case. The camera device will support high speed capture session created by * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }, which - * only accepts high speed request list created by - * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedRequestList }.</p> + * only accepts high speed request lists created by + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList }.</p> * <p>A camera device can still support high speed video streaming by advertising the high speed * FPS ranges in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}. For this case, all normal * capture request per frame control and synchronization requirements will apply to @@ -717,9 +717,9 @@ public abstract class CameraMetadata<TKey> { * <li>The FPS ranges are selected from * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li> * </ul> - * <p>When above conditions are NOT satistied, the + * <p>When above conditions are NOT satistied, * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession } - * and {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedRequestList } will fail.</p> + * will fail.</p> * <p>Switching to a FPS range that has different maximum FPS may trigger some camera device * reconfigurations, which may introduce extra latency. It is recommended that * the application avoids unnecessary maximum target FPS changes as much as possible @@ -1813,9 +1813,8 @@ public abstract class CameraMetadata<TKey> { public static final int CONTROL_SCENE_MODE_BARCODE = 16; /** - * <p>This is deprecated, please use - * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession } - * and {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedRequestList } + * <p>This is deprecated, please use {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession } + * and {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList } * for high speed video recording.</p> * <p>Optimized for high speed video recording (frame rate >=60fps) use case.</p> * <p>The supported high speed video sizes and fps ranges are specified in @@ -1936,21 +1935,21 @@ public abstract class CameraMetadata<TKey> { /** * <p>Same as FACE_PRIORITY scene mode, except that the camera - * device will choose higher sensivity values ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}) + * device will choose higher sensitivity values ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}) * under low light conditions.</p> * <p>The camera device may be tuned to expose the images in a reduced * sensitivity range to produce the best quality images. For example, * if the {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange} gives range of [100, 1600], * the camera device auto-exposure routine tuning process may limit the actual - * exposure sensivity range to [100, 1200] to ensure that the noise level isn't - * exessive to compromise the image quality. Under this situation, the image under + * exposure sensitivity range to [100, 1200] to ensure that the noise level isn't + * exessive in order to preserve the image quality. Under this situation, the image under * low light may be under-exposed when the sensor max exposure time (bounded by the * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of the - * ON_* modes) and effecitve max sensitivity are reached. This scene mode allows the + * ON_* modes) and effective max sensitivity are reached. This scene mode allows the * camera device auto-exposure routine to increase the sensitivity up to the max * sensitivity specified by {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange} when the scene is too * dark and the max exposure time is reached. The captured images may be noisier - * compared with the images captured in normal FACE_PRIORITY mode, therefore, it is + * compared with the images captured in normal FACE_PRIORITY mode; therefore, it is * recommended that the application only use this scene mode when it is capable of * reducing the noise level of the captured images.</p> * <p>Unlike the other scene modes, {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}, diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 6d8cc54..a136d0f 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -184,7 +184,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private final CameraMetadataNative mSettings; private boolean mIsReprocess; // If this request is part of constrained high speed request list that was created by - // {@link CameraDevice#createConstrainedHighSpeedRequestList}. + // {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList} private boolean mIsPartOfCHSRequestList = false; // Each reprocess request must be tied to a reprocessable session ID. // Valid only for reprocess requests (mIsReprocess == true). @@ -340,14 +340,14 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>Determine if this request is part of a constrained high speed request list that was - * created by {@link CameraDevice#createConstrainedHighSpeedRequestList}. A constrained high - * speed request list contains some constrained high speed capture requests with certain - * interleaved pattern that is suitable for high speed preview/video streaming. An active - * constrained high speed capture session only accepts constrained high speed request lists. - * This method can be used to do the sanity check when a constrained high speed capture session - * receives a request list via {@link CameraCaptureSession#setRepeatingBurst} or - * {@link CameraCaptureSession#captureBurst}. - * </p> + * created by + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}. + * A constrained high speed request list contains some constrained high speed capture requests + * with certain interleaved pattern that is suitable for high speed preview/video streaming. An + * active constrained high speed capture session only accepts constrained high speed request + * lists. This method can be used to do the sanity check when a constrained high speed capture + * session receives a request list via {@link CameraCaptureSession#setRepeatingBurst} or + * {@link CameraCaptureSession#captureBurst}. </p> * * * @return {@code true} if this request is part of a constrained high speed request list, @@ -595,9 +595,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>Mark this request as part of a constrained high speed request list created by - * {@link CameraDevice#createConstrainedHighSpeedRequestList}. A constrained high speed - * request list contains some constrained high speed capture requests with certain - * interleaved pattern that is suitable for high speed preview/video streaming.</p> + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}. + * A constrained high speed request list contains some constrained high speed capture + * requests with certain interleaved pattern that is suitable for high speed preview/video + * streaming.</p> * * @hide */ diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionCore.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionCore.java new file mode 100644 index 0000000..116f0f1 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionCore.java @@ -0,0 +1,64 @@ +/* + * 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.hardware.camera2.impl; + +/** + * Internal interface for CameraDeviceImpl to CameraCaptureSessionImpl(s) communication + */ +public interface CameraCaptureSessionCore { + + /** + * Replace this session with another session. + * + * <p>This is an optimization to avoid unconfiguring and then immediately having to + * reconfigure again.</p> + * + * <p>The semantics are identical to {@link #close}, except that unconfiguring will be skipped. + * </p> + * + * <p>After this call completes, the session will not call any further methods on the camera + * device.</p> + * + * @see CameraCaptureSession#close + */ + void replaceSessionClose(); + + /** + * + * Create an internal state callback, to be invoked on the mDeviceHandler + * + * <p>It has a few behaviors: + * <ul> + * <li>Convert device state changes into session state changes. + * <li>Keep track of async tasks that the session began (idle, abort). + * </ul> + * </p> + * */ + CameraDeviceImpl.StateCallbackKK getDeviceStateCallback(); + + /** + * Whether currently in mid-abort. + * + * <p>This is used by the implementation to set the capture failure + * reason, in lieu of more accurate error codes from the camera service. + * Unsynchronized to avoid deadlocks between simultaneous session->device, + * device->session calls.</p> + * + */ + boolean isAborting(); + +} diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index 3d261dd..3c19cd2 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -36,7 +36,8 @@ import java.util.List; import static android.hardware.camera2.impl.CameraDeviceImpl.checkHandler; import static com.android.internal.util.Preconditions.*; -public class CameraCaptureSessionImpl extends CameraCaptureSession { +public class CameraCaptureSessionImpl extends CameraCaptureSession + implements CameraCaptureSessionCore { private static final String TAG = "CameraCaptureSession"; private static final boolean DEBUG = false; @@ -60,7 +61,6 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { private final android.hardware.camera2.impl.CameraDeviceImpl mDeviceImpl; /** Internal handler; used for all incoming events to preserve total order */ private final Handler mDeviceHandler; - private final boolean mIsConstrainedHighSpeedSession; /** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */ private final TaskDrainer<Integer> mSequenceDrainer; @@ -89,14 +89,13 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { CameraCaptureSessionImpl(int id, Surface input, List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler stateHandler, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, - Handler deviceStateHandler, boolean configureSuccess, boolean isConstrainedHighSpeed) { + Handler deviceStateHandler, boolean configureSuccess) { if (outputs == null || outputs.isEmpty()) { throw new IllegalArgumentException("outputs must be a non-null, non-empty list"); } else if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } - mIsConstrainedHighSpeedSession = isConstrainedHighSpeed; mId = id; mIdString = String.format("Session %d: ", mId); @@ -136,30 +135,6 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } } - - private boolean isConstrainedHighSpeedRequestList(List<CaptureRequest> requestList) { - checkCollectionNotEmpty(requestList, "High speed request list"); - for (CaptureRequest request : requestList) { - if (!request.isPartOfCRequestList()) { - return false; - } - } - return true; - } - - /** - * If the session is constrained high speed session, it only accept constrained high speed - * request list. - */ - private void checkConstrainedHighSpeedRequestSanity(List<CaptureRequest> requestList) { - if (mIsConstrainedHighSpeedSession) { - if (!isConstrainedHighSpeedRequestList(requestList)) { - throw new IllegalArgumentException("It is only allowed to submit a constrained " - + "high speed request list to a constrained high speed session!!!"); - } - } - } - @Override public CameraDevice getDevice() { return mDeviceImpl; @@ -181,10 +156,6 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } else if (request.isReprocess() && request.getReprocessableSessionId() != mId) { throw new IllegalArgumentException("capture request was created for another session"); } - if (mIsConstrainedHighSpeedSession) { - throw new UnsupportedOperationException("Constrained high speed session doesn't support" - + " this method"); - } checkNotClosed(); @@ -208,8 +179,6 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { throw new IllegalArgumentException("Requests must have at least one element"); } - checkConstrainedHighSpeedRequestSanity(requests); - for (CaptureRequest request : requests) { if (request.isReprocess()) { if (!isReprocessable()) { @@ -244,10 +213,6 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } else if (request.isReprocess()) { throw new IllegalArgumentException("repeating reprocess requests are not supported"); } - if (mIsConstrainedHighSpeedSession) { - throw new UnsupportedOperationException("Constrained high speed session doesn't support" - + " this method"); - } checkNotClosed(); @@ -271,8 +236,6 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { throw new IllegalArgumentException("requests must have at least one element"); } - checkConstrainedHighSpeedRequestSanity(requests); - for (CaptureRequest r : requests) { if (r.isReprocess()) { throw new IllegalArgumentException("repeating reprocess burst requests are not " + @@ -349,7 +312,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { * * @see CameraCaptureSession#close */ - synchronized void replaceSessionClose() { + @Override + public synchronized void replaceSessionClose() { /* * In order for creating new sessions to be fast, the new session should be created * before the old session is closed. @@ -431,9 +395,9 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { * Unsynchronized to avoid deadlocks between simultaneous session->device, * device->session calls.</p> * - * <p>Package-private.</p> */ - boolean isAborting() { + @Override + public boolean isAborting() { return mAborting; } @@ -521,7 +485,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { * </ul> * </p> * */ - CameraDeviceImpl.StateCallbackKK getDeviceStateCallback() { + @Override + public CameraDeviceImpl.StateCallbackKK getDeviceStateCallback() { final CameraCaptureSession session = this; return new CameraDeviceImpl.StateCallbackKK() { @@ -759,9 +724,4 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } } - @Override - public boolean isConstrainedHighSpeed() { - return mIsConstrainedHighSpeedSession; - } - } diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java new file mode 100644 index 0000000..d732535 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java @@ -0,0 +1,283 @@ +/* + * 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.hardware.camera2.impl; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.hardware.camera2.utils.SurfaceUtils; +import android.os.Handler; +import android.util.Range; +import android.view.Surface; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import static com.android.internal.util.Preconditions.*; + +/** + * Standard implementation of CameraConstrainedHighSpeedCaptureSession. + * + * <p> + * Mostly just forwards calls to an instance of CameraCaptureSessionImpl, + * but implements the few necessary behavior changes and additional methods required + * for the constrained high speed speed mode. + * </p> + */ + +public class CameraConstrainedHighSpeedCaptureSessionImpl + extends CameraConstrainedHighSpeedCaptureSession implements CameraCaptureSessionCore { + private final CameraCharacteristics mCharacteristics; + private final CameraCaptureSessionImpl mSessionImpl; + + /** + * Create a new CameraCaptureSession. + * + * <p>The camera device must already be in the {@code IDLE} state when this is invoked. + * There must be no pending actions + * (e.g. no pending captures, no repeating requests, no flush).</p> + */ + CameraConstrainedHighSpeedCaptureSessionImpl(int id, List<Surface> outputs, + CameraCaptureSession.StateCallback callback, Handler stateHandler, + android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, + Handler deviceStateHandler, boolean configureSuccess, + CameraCharacteristics characteristics) { + mCharacteristics = characteristics; + CameraCaptureSession.StateCallback wrapperCallback = new WrapperCallback(callback); + mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, outputs, wrapperCallback, + stateHandler, deviceImpl, deviceStateHandler, configureSuccess); + } + + @Override + public List<CaptureRequest> createHighSpeedRequestList(CaptureRequest request) + throws CameraAccessException { + if (request == null) { + throw new IllegalArgumentException("Input capture request must not be null"); + } + Collection<Surface> outputSurfaces = request.getTargets(); + Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); + + StreamConfigurationMap config = + mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputSurfaces, fpsRange, config); + + // Request list size: to limit the preview to 30fps, need use maxFps/30; to maximize + // the preview frame rate, should use maxBatch size for that high speed stream + // configuration. We choose the former for now. + int requestListSize = fpsRange.getUpper() / 30; + List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); + + // Prepare the Request builders: need carry over the request controls. + // First, create a request builder that will only include preview or recording target. + CameraMetadataNative requestMetadata = new CameraMetadataNative(request.getNativeCopy()); + // Note that after this step, the requestMetadata is mutated (swapped) and can not be used + // for next request builder creation. + CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder( + requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); + + // Overwrite the capture intent to make sure a good value is set. + Iterator<Surface> iterator = outputSurfaces.iterator(); + Surface firstSurface = iterator.next(); + Surface secondSurface = null; + if (outputSurfaces.size() == 1 && SurfaceUtils.isSurfaceForHwVideoEncoder(firstSurface)) { + singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, + CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW); + } else { + // Video only, or preview + video + singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, + CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); + } + singleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true); + + // Second, Create a request builder that will include both preview and recording targets. + CaptureRequest.Builder doubleTargetRequestBuilder = null; + if (outputSurfaces.size() == 2) { + // Have to create a new copy, the original one was mutated after a new + // CaptureRequest.Builder creation. + requestMetadata = new CameraMetadataNative(request.getNativeCopy()); + doubleTargetRequestBuilder = new CaptureRequest.Builder( + requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); + doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, + CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); + doubleTargetRequestBuilder.addTarget(firstSurface); + secondSurface = iterator.next(); + doubleTargetRequestBuilder.addTarget(secondSurface); + doubleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true); + // Make sure singleTargetRequestBuilder contains only recording surface for + // preview + recording case. + Surface recordingSurface = firstSurface; + if (!SurfaceUtils.isSurfaceForHwVideoEncoder(recordingSurface)) { + recordingSurface = secondSurface; + } + singleTargetRequestBuilder.addTarget(recordingSurface); + } else { + // Single output case: either recording or preview. + singleTargetRequestBuilder.addTarget(firstSurface); + } + + // Generate the final request list. + for (int i = 0; i < requestListSize; i++) { + if (i == 0 && doubleTargetRequestBuilder != null) { + // First request should be recording + preview request + requestList.add(doubleTargetRequestBuilder.build()); + } else { + requestList.add(singleTargetRequestBuilder.build()); + } + } + + return Collections.unmodifiableList(requestList); + } + + private boolean isConstrainedHighSpeedRequestList(List<CaptureRequest> requestList) { + checkCollectionNotEmpty(requestList, "High speed request list"); + for (CaptureRequest request : requestList) { + if (!request.isPartOfCRequestList()) { + return false; + } + } + return true; + } + + @Override + public CameraDevice getDevice() { + return mSessionImpl.getDevice(); + } + + @Override + public void prepare(Surface surface) throws CameraAccessException { + mSessionImpl.prepare(surface); + } + + @Override + public int capture(CaptureRequest request, CaptureCallback listener, Handler handler) + throws CameraAccessException { + throw new UnsupportedOperationException("Constrained high speed session doesn't support" + + " this method"); + } + + @Override + public int captureBurst(List<CaptureRequest> requests, CaptureCallback listener, + Handler handler) throws CameraAccessException { + if (!isConstrainedHighSpeedRequestList(requests)) { + throw new IllegalArgumentException( + "Only request lists created by createHighSpeedRequestList() can be submitted to " + + "a constrained high speed capture session"); + } + return mSessionImpl.captureBurst(requests, listener, handler); + } + + @Override + public int setRepeatingRequest(CaptureRequest request, CaptureCallback listener, + Handler handler) throws CameraAccessException { + throw new UnsupportedOperationException("Constrained high speed session doesn't support" + + " this method"); + } + + @Override + public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback listener, + Handler handler) throws CameraAccessException { + if (!isConstrainedHighSpeedRequestList(requests)) { + throw new IllegalArgumentException( + "Only request lists created by createHighSpeedRequestList() can be submitted to " + + "a constrained high speed capture session"); + } + return mSessionImpl.setRepeatingBurst(requests, listener, handler); + } + + @Override + public void stopRepeating() throws CameraAccessException { + mSessionImpl.stopRepeating(); + } + + @Override + public void abortCaptures() throws CameraAccessException { + mSessionImpl.abortCaptures(); + } + + @Override + public Surface getInputSurface() { + return null; + } + + @Override + public void close() { + mSessionImpl.close(); + } + + @Override + public boolean isReprocessable() { + return false; + } + + // Implementation of CameraCaptureSessionCore methods + + @Override + public void replaceSessionClose() { + mSessionImpl.replaceSessionClose(); + } + + @Override + public CameraDeviceImpl.StateCallbackKK getDeviceStateCallback() { + return mSessionImpl.getDeviceStateCallback(); + } + + @Override + public boolean isAborting() { + return mSessionImpl.isAborting(); + } + + private class WrapperCallback extends StateCallback { + private final StateCallback mCallback; + + public WrapperCallback(StateCallback callback) { + mCallback = callback; + } + + public void onConfigured(CameraCaptureSession session) { + mCallback.onConfigured(CameraConstrainedHighSpeedCaptureSessionImpl.this); + } + + public void onConfigureFailed(CameraCaptureSession session) { + mCallback.onConfigureFailed(CameraConstrainedHighSpeedCaptureSessionImpl.this); + } + + public void onReady(CameraCaptureSession session) { + mCallback.onReady(CameraConstrainedHighSpeedCaptureSessionImpl.this); + } + + public void onActive(CameraCaptureSession session) { + mCallback.onActive(CameraConstrainedHighSpeedCaptureSessionImpl.this); + } + + public void onClosed(CameraCaptureSession session) { + mCallback.onClosed(CameraConstrainedHighSpeedCaptureSessionImpl.this); + } + + public void onSurfacePrepared(CameraCaptureSession session, Surface surface) { + mCallback.onSurfacePrepared(CameraConstrainedHighSpeedCaptureSessionImpl.this, + surface); + } + + + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 83128c3..c594228 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -113,7 +113,7 @@ public class CameraDeviceImpl extends CameraDevice { */ private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker(); - private CameraCaptureSessionImpl mCurrentSession; + private CameraCaptureSessionCore mCurrentSession; private int mNextSessionId = 0; // Runnables for all state transitions, except error, which needs the @@ -510,6 +510,26 @@ public class CameraDeviceImpl extends CameraDevice { /*isConstrainedHighSpeed*/false); } + @Override + public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs, + android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException { + if (outputs == null || outputs.size() == 0 || outputs.size() > 2) { + throw new IllegalArgumentException( + "Output surface list must not be null and the size must be no more than 2"); + } + StreamConfigurationMap config = + getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null, config); + + List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); + for (Surface surface : outputs) { + outConfigurations.add(new OutputConfiguration(surface)); + } + createCaptureSessionInternal(null, outConfigurations, callback, handler, + /*isConstrainedHighSpeed*/true); + } + private void createCaptureSessionInternal(InputConfiguration inputConfig, List<OutputConfiguration> outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler, @@ -565,10 +585,16 @@ public class CameraDeviceImpl extends CameraDevice { outSurfaces.add(config.getSurface()); } // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. - CameraCaptureSessionImpl newSession = - new CameraCaptureSessionImpl(mNextSessionId++, input, - outSurfaces, callback, handler, this, mDeviceHandler, - configureSuccess, isConstrainedHighSpeed); + CameraCaptureSessionCore newSession = null; + if (isConstrainedHighSpeed) { + newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++, + outSurfaces, callback, handler, this, mDeviceHandler, configureSuccess, + mCharacteristics); + } else { + newSession = new CameraCaptureSessionImpl(mNextSessionId++, input, + outSurfaces, callback, handler, this, mDeviceHandler, + configureSuccess); + } // TODO: wait until current session closes, then create the new session mCurrentSession = newSession; @@ -1933,181 +1959,4 @@ public class CameraDeviceImpl extends CameraDevice { return mCharacteristics; } - /** - * A high speed output surface can only be preview or hardware encoder surface. - * - * @param surface The high speed output surface to be checked. - */ - private void checkHighSpeedSurfaceFormat(Surface surface) { - // TODO: remove this override since the default format should be - // ImageFormat.PRIVATE. b/9487482 - final int HAL_FORMAT_RGB_START = 1; // HAL_PIXEL_FORMAT_RGBA_8888 from graphics.h - final int HAL_FORMAT_RGB_END = 5; // HAL_PIXEL_FORMAT_BGRA_8888 from graphics.h - int surfaceFormat = SurfaceUtils.getSurfaceFormat(surface); - if (surfaceFormat >= HAL_FORMAT_RGB_START && - surfaceFormat <= HAL_FORMAT_RGB_END) { - surfaceFormat = ImageFormat.PRIVATE; - } - - if (surfaceFormat != ImageFormat.PRIVATE) { - throw new IllegalArgumentException("Surface format(" + surfaceFormat + ") is not" - + " for preview or hardware video encoding!"); - } - } - - private void checkConstrainedHighSpeedSurfaces(Collection<Surface> surfaces, - Range<Integer> fpsRange) { - if (surfaces == null || surfaces.size() == 0 || surfaces.size() > 2) { - throw new IllegalArgumentException("Output target surface list must not be null and" - + " the size must be 1 or 2"); - } - - StreamConfigurationMap config = - getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - List<Size> highSpeedSizes = null; - if (fpsRange == null) { - highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizes()); - } else { - // Check the FPS range first if provided - Range<Integer>[] highSpeedFpsRanges = config.getHighSpeedVideoFpsRanges(); - if(!Arrays.asList(highSpeedFpsRanges).contains(fpsRange)) { - throw new IllegalArgumentException("Fps range " + fpsRange.toString() + " in the" - + " request is not a supported high speed fps range " + - Arrays.toString(highSpeedFpsRanges)); - } - highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizesFor(fpsRange)); - } - - for (Surface surface : surfaces) { - checkHighSpeedSurfaceFormat(surface); - - // Surface size must be supported high speed sizes. - Size surfaceSize = SurfaceUtils.getSurfaceSize(surface); - if (!highSpeedSizes.contains(surfaceSize)) { - throw new IllegalArgumentException("Surface size " + surfaceSize.toString() + " is" - + " not part of the high speed supported size list " + - Arrays.toString(highSpeedSizes.toArray())); - } - // Each output surface must be either preview surface or recording surface. - if (!SurfaceUtils.isSurfaceForPreview(surface) && - !SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) { - throw new IllegalArgumentException("This output surface is neither preview nor " - + "hardware video encoding surface"); - } - if (SurfaceUtils.isSurfaceForPreview(surface) && - SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) { - throw new IllegalArgumentException("This output surface can not be both preview" - + " and hardware video encoding surface"); - } - } - - // For 2 output surface case, they shouldn't be same type. - if (surfaces.size() == 2) { - // Up to here, each surface can only be either preview or recording. - Iterator<Surface> iterator = surfaces.iterator(); - boolean isFirstSurfacePreview = - SurfaceUtils.isSurfaceForPreview(iterator.next()); - boolean isSecondSurfacePreview = - SurfaceUtils.isSurfaceForPreview(iterator.next()); - if (isFirstSurfacePreview == isSecondSurfacePreview) { - throw new IllegalArgumentException("The 2 output surfaces must have different" - + " type"); - } - } - } - - @Override - public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs, - android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) - throws CameraAccessException { - if (outputs == null || outputs.size() == 0 || outputs.size() > 2) { - throw new IllegalArgumentException( - "Output surface list must not be null and the size must be no more than 2"); - } - checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null); - - List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); - for (Surface surface : outputs) { - outConfigurations.add(new OutputConfiguration(surface)); - } - createCaptureSessionInternal(null, outConfigurations, callback, handler, - /*isConstrainedHighSpeed*/true); - } - - @Override - public List<CaptureRequest> createConstrainedHighSpeedRequestList(CaptureRequest request) - throws CameraAccessException { - if (request == null) { - throw new IllegalArgumentException("Input capture request must not be null"); - } - Collection<Surface> outputSurfaces = request.getTargets(); - Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); - checkConstrainedHighSpeedSurfaces(outputSurfaces, fpsRange); - - // Request list size: to limit the preview to 30fps, need use maxFps/30; to maximize - // the preview frame rate, should use maxBatch size for that high speed stream - // configuration. We choose the former for now. - int requestListSize = fpsRange.getUpper() / 30; - List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); - - // Prepare the Request builders: need carry over the request controls. - // First, create a request builder that will only include preview or recording target. - CameraMetadataNative requestMetadata = new CameraMetadataNative(request.getNativeCopy()); - // Note that after this step, the requestMetadata is mutated (swapped) and can not be used - // for next request builder creation. - CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder( - requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); - - // Overwrite the capture intent to make sure a good value is set. - Iterator<Surface> iterator = outputSurfaces.iterator(); - Surface firstSurface = iterator.next(); - Surface secondSurface = null; - if (outputSurfaces.size() == 1 && SurfaceUtils.isSurfaceForHwVideoEncoder(firstSurface)) { - singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, - CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW); - } else { - // Video only, or preview + video - singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, - CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); - } - singleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true); - - // Second, Create a request builder that will include both preview and recording targets. - CaptureRequest.Builder doubleTargetRequestBuilder = null; - if (outputSurfaces.size() == 2) { - // Have to create a new copy, the original one was mutated after a new - // CaptureRequest.Builder creation. - requestMetadata = new CameraMetadataNative(request.getNativeCopy()); - doubleTargetRequestBuilder = new CaptureRequest.Builder( - requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); - doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, - CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); - doubleTargetRequestBuilder.addTarget(firstSurface); - secondSurface = iterator.next(); - doubleTargetRequestBuilder.addTarget(secondSurface); - doubleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true); - // Make sure singleTargetRequestBuilder contains only recording surface for - // preview + recording case. - Surface recordingSurface = firstSurface; - if (!SurfaceUtils.isSurfaceForHwVideoEncoder(recordingSurface)) { - recordingSurface = secondSurface; - } - singleTargetRequestBuilder.addTarget(recordingSurface); - } else { - // Single output case: either recording or preview. - singleTargetRequestBuilder.addTarget(firstSurface); - } - - // Generate the final request list. - for (int i = 0; i < requestListSize; i++) { - if (i == 0 && doubleTargetRequestBuilder != null) { - // First request should be recording + preview request - requestList.add(doubleTargetRequestBuilder.build()); - } else { - requestList.add(singleTargetRequestBuilder.build()); - } - } - - return Collections.unmodifiableList(requestList); - } } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index 639ad60..8e0eab2 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -495,7 +495,8 @@ public final class StreamConfigurationMap { * <p> * To enable high speed video recording, application must create a constrained create high speed * capture session via {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, and submit - * a CaptureRequest list created by {@link CameraDevice#createConstrainedHighSpeedRequestList} + * a CaptureRequest list created by + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList} * to this session. The application must select the video size from this method and * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE FPS range} from * {@link #getHighSpeedVideoFpsRangesFor} to configure the constrained high speed session and @@ -506,14 +507,15 @@ public final class StreamConfigurationMap { * the same size). Otherwise, the high speed session creation will fail. Once the size is * selected, application can get the supported FPS ranges by * {@link #getHighSpeedVideoFpsRangesFor}, and use these FPS ranges to setup the recording - * request lists via {@link CameraDevice#createConstrainedHighSpeedRequestList}. + * request lists via + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}. * </p> * * @return an array of supported high speed video recording sizes * @see #getHighSpeedVideoFpsRangesFor(Size) * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO * @see CameraDevice#createConstrainedHighSpeedCaptureSession - * @see CameraDevice#createConstrainedHighSpeedRequestList + * @see android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList */ public Size[] getHighSpeedVideoSizes() { Set<Size> keySet = mHighSpeedVideoSizeMap.keySet(); @@ -571,7 +573,8 @@ public final class StreamConfigurationMap { * <p> * To enable high speed video recording, application must create a constrained create high speed * capture session via {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, and submit - * a CaptureRequest list created by {@link CameraDevice#createConstrainedHighSpeedRequestList} + * a CaptureRequest list created by + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList} * to this session. The application must select the video size from this method and * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE FPS range} from * {@link #getHighSpeedVideoFpsRangesFor} to configure the constrained high speed session and @@ -583,7 +586,7 @@ public final class StreamConfigurationMap { * recording streams must have the same size). Otherwise, the high speed session creation will * fail. Once the high speed capture session is created, the application can set the FPS range * in the recording request lists via - * {@link CameraDevice#createConstrainedHighSpeedRequestList}. + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}. * </p> * <p> * The FPS ranges reported by this method will have below characteristics: @@ -601,7 +604,7 @@ public final class StreamConfigurationMap { * @see #getHighSpeedVideoSizesFor * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO * @see CameraDevice#createConstrainedHighSpeedCaptureSession - * @see CameraDevice#createConstrainedHighSpeedRequestList + * @see CameraDevice#createHighSpeedRequestList */ @SuppressWarnings("unchecked") public Range<Integer>[] getHighSpeedVideoFpsRanges() { diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java index 064b21a..4b958df 100644 --- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java +++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java @@ -19,9 +19,16 @@ package android.hardware.camera2.utils; import android.graphics.ImageFormat; import android.hardware.camera2.legacy.LegacyCameraDevice; import android.hardware.camera2.legacy.LegacyExceptionUtils.BufferQueueAbandonedException; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.util.Range; import android.util.Size; import android.view.Surface; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + /** * Various Surface utilities. */ @@ -105,4 +112,93 @@ public class SurfaceUtils { return LegacyCameraDevice.isFlexibleConsumer(output); } + /** + * A high speed output surface can only be preview or hardware encoder surface. + * + * @param surface The high speed output surface to be checked. + */ + private static void checkHighSpeedSurfaceFormat(Surface surface) { + // TODO: remove this override since the default format should be + // ImageFormat.PRIVATE. b/9487482 + final int HAL_FORMAT_RGB_START = 1; // HAL_PIXEL_FORMAT_RGBA_8888 from graphics.h + final int HAL_FORMAT_RGB_END = 5; // HAL_PIXEL_FORMAT_BGRA_8888 from graphics.h + int surfaceFormat = SurfaceUtils.getSurfaceFormat(surface); + if (surfaceFormat >= HAL_FORMAT_RGB_START && + surfaceFormat <= HAL_FORMAT_RGB_END) { + surfaceFormat = ImageFormat.PRIVATE; + } + + if (surfaceFormat != ImageFormat.PRIVATE) { + throw new IllegalArgumentException("Surface format(" + surfaceFormat + ") is not" + + " for preview or hardware video encoding!"); + } + } + + /** + * Verify that that the surfaces are valid for high-speed recording mode, + * and that the FPS range is supported + * + * @param surfaces the surfaces to verify as valid in terms of size and format + * @param fpsRange the target high-speed FPS range to validate + * @param config The stream configuration map for the device in question + */ + public static void checkConstrainedHighSpeedSurfaces(Collection<Surface> surfaces, + Range<Integer> fpsRange, StreamConfigurationMap config) { + if (surfaces == null || surfaces.size() == 0 || surfaces.size() > 2) { + throw new IllegalArgumentException("Output target surface list must not be null and" + + " the size must be 1 or 2"); + } + + List<Size> highSpeedSizes = null; + if (fpsRange == null) { + highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizes()); + } else { + // Check the FPS range first if provided + Range<Integer>[] highSpeedFpsRanges = config.getHighSpeedVideoFpsRanges(); + if(!Arrays.asList(highSpeedFpsRanges).contains(fpsRange)) { + throw new IllegalArgumentException("Fps range " + fpsRange.toString() + " in the" + + " request is not a supported high speed fps range " + + Arrays.toString(highSpeedFpsRanges)); + } + highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizesFor(fpsRange)); + } + + for (Surface surface : surfaces) { + checkHighSpeedSurfaceFormat(surface); + + // Surface size must be supported high speed sizes. + Size surfaceSize = SurfaceUtils.getSurfaceSize(surface); + if (!highSpeedSizes.contains(surfaceSize)) { + throw new IllegalArgumentException("Surface size " + surfaceSize.toString() + " is" + + " not part of the high speed supported size list " + + Arrays.toString(highSpeedSizes.toArray())); + } + // Each output surface must be either preview surface or recording surface. + if (!SurfaceUtils.isSurfaceForPreview(surface) && + !SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) { + throw new IllegalArgumentException("This output surface is neither preview nor " + + "hardware video encoding surface"); + } + if (SurfaceUtils.isSurfaceForPreview(surface) && + SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) { + throw new IllegalArgumentException("This output surface can not be both preview" + + " and hardware video encoding surface"); + } + } + + // For 2 output surface case, they shouldn't be same type. + if (surfaces.size() == 2) { + // Up to here, each surface can only be either preview or recording. + Iterator<Surface> iterator = surfaces.iterator(); + boolean isFirstSurfacePreview = + SurfaceUtils.isSurfaceForPreview(iterator.next()); + boolean isSecondSurfacePreview = + SurfaceUtils.isSurfaceForPreview(iterator.next()); + if (isFirstSurfacePreview == isSecondSurfacePreview) { + throw new IllegalArgumentException("The 2 output surfaces must have different" + + " type"); + } + } + } + } diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 31a6a96..0fe112c 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -82,6 +82,9 @@ interface IUsbManager /* Clears default preferences and permissions for the package */ void clearDefaults(String packageName, int userId); + /* Returns true if the specified USB function is enabled. */ + boolean isFunctionEnabled(String function); + /* Sets the current USB function. */ void setCurrentFunction(String function); diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index c83f466..f58b9d6 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -22,7 +22,6 @@ import android.content.Context; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.SystemProperties; import android.util.Log; import java.util.HashMap; @@ -54,8 +53,6 @@ public class UsbManager { * <li> {@link #USB_CONNECTED} boolean indicating whether USB is connected or disconnected. * <li> {@link #USB_CONFIGURED} boolean indicating whether USB is configured. * currently zero if not configured, one for configured. - * <li> {@link #USB_FUNCTION_MASS_STORAGE} boolean extra indicating whether the - * mass storage function is enabled * <li> {@link #USB_FUNCTION_ADB} boolean extra indicating whether the * adb function is enabled * <li> {@link #USB_FUNCTION_RNDIS} boolean extra indicating whether the @@ -152,12 +149,13 @@ public class UsbManager { public static final String USB_DATA_UNLOCKED = "unlocked"; /** - * Name of the USB mass storage USB function. - * Used in extras for the {@link #ACTION_USB_STATE} broadcast + * A placeholder indicating that no USB function is being specified. + * Used to distinguish between selecting no function vs. the default function in + * {@link #setCurrentFunction(String)}. * * {@hide} */ - public static final String USB_FUNCTION_MASS_STORAGE = "mass_storage"; + public static final String USB_FUNCTION_NONE = "none"; /** * Name of the adb USB function. @@ -218,15 +216,14 @@ public class UsbManager { /** * Name of extra for {@link #ACTION_USB_DEVICE_ATTACHED} and * {@link #ACTION_USB_DEVICE_DETACHED} broadcasts - * containing the UsbDevice object for the device. + * containing the {@link UsbDevice} object for the device. */ - public static final String EXTRA_DEVICE = "device"; /** * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} and * {@link #ACTION_USB_ACCESSORY_DETACHED} broadcasts - * containing the UsbAccessory object for the accessory. + * containing the {@link UsbAccessory} object for the accessory. */ public static final String EXTRA_ACCESSORY = "accessory"; @@ -238,23 +235,6 @@ public class UsbManager { */ public static final String EXTRA_PERMISSION_GRANTED = "permission"; - /** - * The persistent property which stores whether adb is enabled or not. Other values are ignored. - * Previously this value stored non-adb settings, but not anymore. - * TODO: rename this to something adb specific, rather than using usb. - * - * {@hide} - */ - public static final String ADB_PERSISTENT_PROPERTY = "persist.sys.usb.config"; - - /** - * The non-persistent property which stores the current USB settings. - * - * {@hide} - */ - public static final String USB_SETTINGS_PROPERTY = "sys.usb.config"; - - private final Context mContext; private final IUsbManager mService; @@ -437,31 +417,44 @@ public class UsbManager { } } - private static boolean propertyContainsFunction(String property, String function) { - String functions = SystemProperties.get(property, ""); - int index = functions.indexOf(function); - if (index < 0) return false; - if (index > 0 && functions.charAt(index - 1) != ',') return false; - int charAfter = index + function.length(); - if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; - return true; - } - /** - * Returns true if the specified USB function is currently enabled. + * Returns true if the specified USB function is currently enabled when in device mode. + * <p> + * USB functions represent interfaces which are published to the host to access + * services offered by the device. + * </p> * * @param function name of the USB function - * @return true if the USB function is enabled. + * @return true if the USB function is enabled * * {@hide} */ public boolean isFunctionEnabled(String function) { - return propertyContainsFunction(USB_SETTINGS_PROPERTY, function); + try { + return mService.isFunctionEnabled(function); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in setCurrentFunction", e); + return false; + } } /** - * Sets the current USB function. - * If function is null, then the current function is set to the default function. + * Sets the current USB function when in device mode. + * <p> + * USB functions represent interfaces which are published to the host to access + * services offered by the device. + * </p><p> + * This method is intended to select among primary USB functions. The system may + * automatically activate additional functions such as {@link #USB_FUNCTION_ADB} + * or {@link #USB_FUNCTION_ACCESSORY} based on other settings and states. + * </p><p> + * The allowed values are: {@link #USB_FUNCTION_NONE}, {@link #USB_FUNCTION_AUDIO_SOURCE}, + * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP}, + * or {@link #USB_FUNCTION_RNDIS}. + * </p><p> + * Note: This function is asynchronous and may fail silently without applying + * the requested changes. + * </p> * * @param function name of the USB function, or null to restore the default function * @@ -477,8 +470,9 @@ public class UsbManager { /** * Sets whether USB data (for example, MTP exposed pictures) should be made available - * on the USB connection. Unlocking usb data should only be done with user involvement, - * since exposing pictures or other data could leak sensitive user information. + * on the USB connection when in device mode. Unlocking usb data should only be done with + * user involvement, since exposing pictures or other data could leak sensitive + * user information. * * {@hide} */ @@ -491,7 +485,8 @@ public class UsbManager { } /** - * Returns {@code true} iff access to sensitive USB data is currently allowed. + * Returns {@code true} iff access to sensitive USB data is currently allowed when + * in device mode. * * {@hide} */ @@ -504,4 +499,51 @@ public class UsbManager { return false; } + /** @hide */ + public static String addFunction(String functions, String function) { + if ("none".equals(functions)) { + return function; + } + if (!containsFunction(functions, function)) { + if (functions.length() > 0) { + functions += ","; + } + functions += function; + } + return functions; + } + + /** @hide */ + public static String removeFunction(String functions, String function) { + String[] split = functions.split(","); + for (int i = 0; i < split.length; i++) { + if (function.equals(split[i])) { + split[i] = null; + } + } + if (split.length == 1 && split[0] == null) { + return "none"; + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < split.length; i++) { + String s = split[i]; + if (s != null) { + if (builder.length() > 0) { + builder.append(","); + } + builder.append(s); + } + } + return builder.toString(); + } + + /** @hide */ + public static boolean containsFunction(String functions, String function) { + int index = functions.indexOf(function); + if (index < 0) return false; + if (index > 0 && functions.charAt(index - 1) != ',') return false; + int charAfter = index + function.length(); + if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; + return true; + } } diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java index 8bc2876..b039fc7 100644 --- a/core/java/android/inputmethodservice/ExtractEditText.java +++ b/core/java/android/inputmethodservice/ExtractEditText.java @@ -29,7 +29,7 @@ import android.widget.EditText; public class ExtractEditText extends EditText { private InputMethodService mIME; private int mSettingExtractedText; - + public ExtractEditText(Context context) { super(context, null); } @@ -45,11 +45,11 @@ public class ExtractEditText extends EditText { public ExtractEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } - + void setIME(InputMethodService ime) { mIME = ime; } - + /** * Start making changes that will not be reported to the client. That * is, {@link #onSelectionChanged(int, int)} will not result in sending @@ -58,7 +58,7 @@ public class ExtractEditText extends EditText { public void startInternalChanges() { mSettingExtractedText += 1; } - + /** * Finish making changes that will not be reported to the client. That * is, {@link #onSelectionChanged(int, int)} will not result in sending @@ -67,7 +67,7 @@ public class ExtractEditText extends EditText { public void finishInternalChanges() { mSettingExtractedText -= 1; } - + /** * Implement just to keep track of when we are setting text from the * client (vs. seeing changes in ourself from the user). @@ -80,7 +80,7 @@ public class ExtractEditText extends EditText { mSettingExtractedText--; } } - + /** * Report to the underlying text editor about selection changes. */ @@ -89,7 +89,7 @@ public class ExtractEditText extends EditText { mIME.onExtractedSelectionChanged(selStart, selEnd); } } - + /** * Redirect clicks to the IME for handling there. First allows any * on click handler to run, though. @@ -101,17 +101,18 @@ public class ExtractEditText extends EditText { } return false; } - + @Override public boolean onTextContextMenuItem(int id) { - if (mIME != null && mIME.onExtractTextContextMenuItem(id)) { + // Select all shouldn't be handled by the original edit text, but by the extracted one. + if (id != android.R.id.selectAll && mIME != null && mIME.onExtractTextContextMenuItem(id)) { // Mode was started on Extracted, needs to be stopped here. - // Cut and paste will change the text, which stops selection mode. - if (id == android.R.id.copy) stopTextActionMode(); + // Cut will change the text, which stops selection mode. + if (id == android.R.id.copy || id == android.R.id.paste) stopTextActionMode(); return true; } return super.onTextContextMenuItem(id); } - + /** * We are always considered to be an input method target. */ @@ -119,14 +120,14 @@ public class ExtractEditText extends EditText { public boolean isInputMethodTarget() { return true; } - + /** * Return true if the edit text is currently showing a scroll bar. */ public boolean hasVerticalScrollBar() { return computeVerticalScrollRange() > computeVerticalScrollExtent(); } - + /** * Pretend like the window this view is in always has focus, so its * highlight and cursor will be displayed. diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java new file mode 100644 index 0000000..ee05f28 --- /dev/null +++ b/core/java/android/net/CaptivePortal.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed urnder the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +/** + * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN} + * activity to indicate to the system different outcomes of captive portal sign in. This class is + * passed as an extra named {@link ConnectivityManager#EXTRA_CAPTIVE_PORTAL} with the + * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity. + */ +public class CaptivePortal implements Parcelable { + /** @hide */ + public static final int APP_RETURN_DISMISSED = 0; + /** @hide */ + public static final int APP_RETURN_UNWANTED = 1; + /** @hide */ + public static final int APP_RETURN_WANTED_AS_IS = 2; + + private final IBinder mBinder; + + /** @hide */ + public CaptivePortal(IBinder binder) { + mBinder = binder; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeStrongBinder(mBinder); + } + + public static final Parcelable.Creator<CaptivePortal> CREATOR + = new Parcelable.Creator<CaptivePortal>() { + @Override + public CaptivePortal createFromParcel(Parcel in) { + return new CaptivePortal(in.readStrongBinder()); + } + + @Override + public CaptivePortal[] newArray(int size) { + return new CaptivePortal[size]; + } + }; + + /** + * Indicate to the system that the captive portal has been + * dismissed. In response the framework will re-evaluate the network's + * connectivity and might take further action thereafter. + */ + public void reportCaptivePortalDismissed() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_DISMISSED); + } catch (RemoteException e) { + } + } + + /** + * Indicate to the system that the user does not want to pursue signing in to the + * captive portal and the system should continue to prefer other networks + * without captive portals for use as the default active data network. The + * system will not retest the network for a captive portal so as to avoid + * disturbing the user with further sign in to network notifications. + */ + public void ignoreNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_UNWANTED); + } catch (RemoteException e) { + } + } + + /** + * Indicate to the system the user wants to use this network as is, even though + * the captive portal is still in place. The system will treat the network + * as if it did not have a captive portal when selecting the network to use + * as the default active data network. This may result in this network + * becoming the default active data network, which could disrupt network + * connectivity for apps because the captive portal is still in place. + * @hide + */ + public void useNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS); + } catch (RemoteException e) { + } + } +} diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 01334c3..dc8ff8f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -102,26 +102,26 @@ public class ConnectivityManager { * portal, which is blocking Internet connectivity. The user was presented * with a notification that network sign in is required, * and the user invoked the notification's action indicating they - * desire to sign in to the network. Apps handling this action should + * desire to sign in to the network. Apps handling this activity should * facilitate signing in to the network. This action includes a * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents * the network presenting the captive portal; all communication with the * captive portal must be done using this {@code Network} object. * <p/> - * When the app handling this action believes the user has signed in to - * the network and the captive portal has been dismissed, the app should call - * {@link #reportCaptivePortalDismissed} so the system can reevaluate the network. - * If reevaluation finds the network no longer subject to a captive portal, - * the network may become the default active data network. - * <p/> - * When the app handling this action believes the user explicitly wants + * This activity includes a {@link CaptivePortal} extra named + * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different + * outcomes of the captive portal sign in to the system: + * <ul> + * <li> When the app handling this action believes the user has signed in to + * the network and the captive portal has been dismissed, the app should + * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can + * reevaluate the network. If reevaluation finds the network no longer + * subject to a captive portal, the network may become the default active + * data network. </li> + * <li> When the app handling this action believes the user explicitly wants * to ignore the captive portal and the network, the app should call - * {@link #ignoreNetworkWithCaptivePortal}. - * <p/> - * Note that this action includes a {@code String} extra named - * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} that must - * be passed in to {@link #reportCaptivePortalDismissed} and - * {@link #ignoreNetworkWithCaptivePortal}. + * {@link CaptivePortal#ignoreNetwork}. </li> + * </ul> */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; @@ -187,16 +187,15 @@ public class ConnectivityManager { * {@hide} */ public static final String EXTRA_INET_CONDITION = "inetCondition"; - /** - * The lookup key for a string that is sent out with - * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}. This string must be - * passed in to {@link #reportCaptivePortalDismissed} and - * {@link #ignoreNetworkWithCaptivePortal}. Retrieve it with - * {@link android.content.Intent#getStringExtra(String)}. + * The lookup key for a {@link CaptivePortal} object included with the + * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent. The {@code CaptivePortal} + * object can be used to either indicate to the system that the captive + * portal has been dismissed or that the user does not want to pursue + * signing in to captive portal. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. */ - public static final String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken"; - + public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; /** * Broadcast action to indicate the change of data activity status * (idle or active) on a network in a recent period. @@ -432,7 +431,8 @@ public class ConnectivityManager { public static final int TYPE_MOBILE_IA = 14; /** - * Emergency PDN connection for emergency calls + * Emergency PDN connection for emergency services. This + * may include IMS and MMS in emergency situations. * {@hide} */ public static final int TYPE_MOBILE_EMERGENCY = 15; @@ -1779,82 +1779,6 @@ public class ConnectivityManager { } } - /** {@hide} */ - public static final int CAPTIVE_PORTAL_APP_RETURN_DISMISSED = 0; - /** {@hide} */ - public static final int CAPTIVE_PORTAL_APP_RETURN_UNWANTED = 1; - /** {@hide} */ - public static final int CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS = 2; - - /** - * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} - * action to indicate to the system that the captive portal has been - * dismissed. In response the framework will re-evaluate the network's - * connectivity and might take further action thereafter. - * - * @param network The {@link Network} object passed via - * {@link #EXTRA_NETWORK} with the - * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action. - * @param actionToken The {@code String} passed via - * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the - * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action. - */ - public void reportCaptivePortalDismissed(Network network, String actionToken) { - try { - mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_DISMISSED, - actionToken); - } catch (RemoteException e) { - } - } - - /** - * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} - * action to indicate that the user does not want to pursue signing in to - * captive portal and the system should continue to prefer other networks - * without captive portals for use as the default active data network. The - * system will not retest the network for a captive portal so as to avoid - * disturbing the user with further sign in to network notifications. - * - * @param network The {@link Network} object passed via - * {@link #EXTRA_NETWORK} with the - * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action. - * @param actionToken The {@code String} passed via - * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the - * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action. - */ - public void ignoreNetworkWithCaptivePortal(Network network, String actionToken) { - try { - mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_UNWANTED, - actionToken); - } catch (RemoteException e) { - } - } - - /** - * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} - * action to indicate the user wants to use this network as is, even though - * the captive portal is still in place. The system will treat the network - * as if it did not have a captive portal when selecting the network to use - * as the default active data network. This may result in this network - * becoming the default active data network, which could disrupt network - * connectivity for apps because the captive portal is still in place. - * - * @param network The {@link Network} object passed via - * {@link #EXTRA_NETWORK} with the - * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action. - * @param actionToken The {@code String} passed via - * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the - * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action. - * @hide - */ - public void useNetworkWithCaptivePortal(Network network, String actionToken) { - try { - mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS, - actionToken); - } catch (RemoteException e) { - } - } - /** * Set a network-independent global http proxy. This is not normally what you want * for typical HTTP proxies - they are general network dependent. However if you're diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl new file mode 100644 index 0000000..a013e79 --- /dev/null +++ b/core/java/android/net/ICaptivePortal.aidl @@ -0,0 +1,26 @@ +/** + * 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.net; + +/** + * Interface to inform NetworkMonitor of decisions of app handling captive portal. + * @hide + */ +interface ICaptivePortal +{ + oneway void appResponse(int response); +} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 29557bb..46c28a6 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -96,8 +96,6 @@ interface IConnectivityManager void reportNetworkConnectivity(in Network network, boolean hasConnectivity); - void captivePortalAppResponse(in Network network, int response, String actionToken); - ProxyInfo getGlobalProxy(); void setGlobalProxy(in ProxyInfo p); @@ -114,7 +112,7 @@ interface IConnectivityManager void startLegacyVpn(in VpnProfile profile); - LegacyVpnInfo getLegacyVpnInfo(); + LegacyVpnInfo getLegacyVpnInfo(int userId); VpnInfo[] getAllVpnInfo(); diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 658051c..514d24a 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -133,7 +133,8 @@ public final class NetworkCapabilities implements Parcelable { /** * Indicates this is a network that has the ability to reach a carrier's - * Emergency IMS servers, used for network signaling during emergency calls. + * Emergency IMS servers or other services, used for network signaling + * during emergency calls. */ public static final int NET_CAPABILITY_EIMS = 10; diff --git a/core/java/android/os/PooledStringReader.java b/core/java/android/os/PooledStringReader.java index 7795957..6fc71c7 100644 --- a/core/java/android/os/PooledStringReader.java +++ b/core/java/android/os/PooledStringReader.java @@ -36,6 +36,10 @@ public class PooledStringReader { mPool = new String[size]; } + public int getStringCount() { + return mPool.length; + } + public String readString() { int idx = mIn.readInt(); if (idx >= 0) { diff --git a/core/java/android/os/PooledStringWriter.java b/core/java/android/os/PooledStringWriter.java index eac297d..ee592d9 100644 --- a/core/java/android/os/PooledStringWriter.java +++ b/core/java/android/os/PooledStringWriter.java @@ -67,6 +67,10 @@ public class PooledStringWriter { } } + public int getStringCount() { + return mPool.size(); + } + public void finish() { final int pos = mOut.dataPosition(); mOut.setDataPosition(mStart); diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 84a879c..c3b098b 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -758,13 +758,15 @@ public interface IMountService extends IInterface { return _result; } - public StorageVolume[] getVolumeList(int userId) throws RemoteException { + public StorageVolume[] getVolumeList(int uid, String packageName) + throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); StorageVolume[] _result; try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(userId); + _data.writeInt(uid); + _data.writeString(packageName); mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArray(StorageVolume.CREATOR); @@ -1177,21 +1179,6 @@ public interface IMountService extends IInterface { _data.recycle(); } } - - @Override - public void remountUid(int uid) throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(uid); - mRemote.transact(Stub.TRANSACTION_remountUid, _data, _reply, 0); - _reply.readException(); - } finally { - _reply.recycle(); - _data.recycle(); - } - } } private static final String DESCRIPTOR = "IMountService"; @@ -1307,8 +1294,6 @@ public interface IMountService extends IInterface { static final int TRANSACTION_benchmark = IBinder.FIRST_CALL_TRANSACTION + 59; static final int TRANSACTION_setDebugFlags = IBinder.FIRST_CALL_TRANSACTION + 60; - static final int TRANSACTION_remountUid = IBinder.FIRST_CALL_TRANSACTION + 61; - /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1622,8 +1607,9 @@ public interface IMountService extends IInterface { } case TRANSACTION_getVolumeList: { data.enforceInterface(DESCRIPTOR); - int userId = data.readInt(); - StorageVolume[] result = getVolumeList(userId); + int uid = data.readInt(); + String packageName = data.readString(); + StorageVolume[] result = getVolumeList(uid, packageName); reply.writeNoException(); reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; @@ -1862,13 +1848,6 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } - case TRANSACTION_remountUid: { - data.enforceInterface(DESCRIPTOR); - int uid = data.readInt(); - remountUid(uid); - reply.writeNoException(); - return true; - } } return super.onTransact(code, data, reply, flags); } @@ -2080,11 +2059,11 @@ public interface IMountService extends IInterface { /** * Returns list of all mountable volumes. */ - public StorageVolume[] getVolumeList(int userId) throws RemoteException; + public StorageVolume[] getVolumeList(int uid, String packageName) throws RemoteException; /** * Gets the path on the filesystem for the ASEC container itself. - * + * * @param cid ASEC container ID * @return path to filesystem or {@code null} if it's not found * @throws RemoteException @@ -2178,6 +2157,4 @@ public interface IMountService extends IInterface { public String getPrimaryStorageUuid() throws RemoteException; public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) throws RemoteException; - - public void remountUid(int uid) throws RemoteException; } diff --git a/core/java/android/os/storage/MountServiceInternal.java b/core/java/android/os/storage/MountServiceInternal.java new file mode 100644 index 0000000..17aaef9 --- /dev/null +++ b/core/java/android/os/storage/MountServiceInternal.java @@ -0,0 +1,82 @@ +/* + * 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.os.storage; + +/** + * Mount service local interface. + * + * @hide Only for use within the system server. + */ +public abstract class MountServiceInternal { + + /** + * Policy that influences how external storage is mounted and reported. + */ + public interface ExternalStorageMountPolicy { + /** + * Gets the external storage mount mode for the given uid. + * + * @param uid The UID for which to determine mount mode. + * @param packageName The package in the UID for making the call. + * @return The mount mode. + * + * @see com.android.internal.os.Zygote#MOUNT_EXTERNAL_NONE + * @see com.android.internal.os.Zygote#MOUNT_EXTERNAL_DEFAULT + * @see com.android.internal.os.Zygote#MOUNT_EXTERNAL_READ + * @see com.android.internal.os.Zygote#MOUNT_EXTERNAL_WRITE + */ + public int getMountMode(int uid, String packageName); + + /** + * Gets whether external storage should be reported to the given UID. + * + * @param uid The UID for which to determine whether it has external storage. + * @param packageName The package in the UID for making the call. + * @return Weather to report external storage. + * @return True to report the state of external storage, false to + * report it as unmounted. + */ + public boolean hasExternalStorage(int uid, String packageName); + } + + /** + * Adds a policy for determining how external storage is mounted and reported. + * The mount mode is the most conservative result from querying all registered + * policies. Similarly, the reported state is the most conservative result from + * querying all registered policies. + * + * @param policy The policy to add. + */ + public abstract void addExternalStoragePolicy(ExternalStorageMountPolicy policy); + + /** + * Notify the mount service that the mount policy for a UID changed. + * @param uid The UID for which policy changed. + * @param packageName The package in the UID for making the call. + */ + public abstract void onExternalStoragePolicyChanged(int uid, String packageName); + + /** + * Gets the mount mode to use for a given UID as determined by consultin all + * policies. + * + * @param uid The UID for which to get mount mode. + * @param packageName The package in the UID for making the call. + * @return The mount mode. + */ + public abstract int getExternalStorageMountMode(int uid, String packageName); +} diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index f03e04e..320aa2c 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -20,6 +20,7 @@ import static android.net.TrafficStats.MB_IN_BYTES; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityThread; import android.content.ContentResolver; import android.content.Context; import android.content.pm.IPackageMoveObserver; @@ -857,7 +858,9 @@ public class StorageManager { final IMountService mountService = IMountService.Stub.asInterface( ServiceManager.getService("mount")); try { - return mountService.getVolumeList(userId); + final String packageName = ActivityThread.currentOpPackageName(); + final int uid = ActivityThread.getPackageManager().getPackageUid(packageName, userId); + return mountService.getVolumeList(uid, packageName); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -894,15 +897,6 @@ public class StorageManager { } /** {@hide} */ - public void remountUid(int uid) { - try { - mMountService.remountUid(uid); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - - /** {@hide} */ private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10; private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES; private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES; @@ -956,7 +950,7 @@ public class StorageManager { || vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isMountedReadable()) { final File internalPath = FileUtils.rewriteAfterRename(vol.getPath(), vol.getInternalPath(), path); - if (internalPath != null) { + if (internalPath != null && internalPath.exists()) { return internalPath; } } diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 32f7bc9..8d603a1 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -298,14 +298,14 @@ public class VolumeInfo implements Parcelable { } } - public StorageVolume buildStorageVolume(Context context, int userId) { + public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) { final StorageManager storage = context.getSystemService(StorageManager.class); final boolean removable; final boolean emulated; final boolean allowMassStorage = false; - final String envState = getEnvironmentForState(state); - + final String envState = reportUnmounted + ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state); File userPath = getPathForUser(userId); if (userPath == null) { userPath = new File("/dev/null"); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 4e13758..fff355b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -574,7 +574,22 @@ public final class Settings { */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_MANAGE_OVERLAY_PERMISSION = - "android.settings.MANAGE_OVERLAY_PERMISSION"; + "android.settings.action.MANAGE_OVERLAY_PERMISSION"; + + /** + * Activity Action: Show settings to toggle apps' capablity to + * to read/write system settings. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_WRITE_SETTINGS = + "android.settings.action.MANAGE_WRITE_SETTINGS"; /** * Activity Action: Show screen of details about a particular application. diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index a7e0e08..e408b36 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -209,16 +209,30 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } @Override - public void handleAssist(Bundle data, AssistStructure structure, - AssistContent content) { + public void handleAssist(final Bundle data, final AssistStructure structure, + final AssistContent content) { // We want to pre-warm the AssistStructure before handing it off to the main - // thread. There is a strong argument to be made that it should be handed - // through as a separate param rather than part of the assistBundle. - if (structure != null) { - structure.ensureData(); - } - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOO(MSG_HANDLE_ASSIST, - data, structure, content)); + // thread. We also want to do this on a separate thread, so that if the app + // is for some reason slow (due to slow filling in of async children in the + // structure), we don't block other incoming IPCs (such as the screenshot) to + // us (since we are a oneway interface, they get serialized). (Okay?) + Thread retriever = new Thread("AssistStructure retriever") { + @Override + public void run() { + Throwable failure = null; + if (structure != null) { + try { + structure.ensureData(); + } catch (Throwable e) { + Log.w(TAG, "Failure retrieving AssistStructure", e); + failure = e; + } + } + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_HANDLE_ASSIST, + data, failure == null ? structure : null, failure, content)); + } + }; + retriever.start(); } @Override @@ -689,8 +703,8 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onHandleAssist: data=" + args.arg1 + " structure=" + args.arg2 + " content=" + args.arg3); - onHandleAssist((Bundle) args.arg1, (AssistStructure) args.arg2, - (AssistContent) args.arg3); + doOnHandleAssist((Bundle) args.arg1, (AssistStructure) args.arg2, + (Throwable) args.arg3, (AssistContent) args.arg4); break; case MSG_HANDLE_SCREENSHOT: if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj); @@ -1111,9 +1125,45 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall mContentFrame.requestApplyInsets(); } + void doOnHandleAssist(Bundle data, AssistStructure structure, Throwable failure, + AssistContent content) { + if (failure != null) { + onAssistStructureFailure(failure); + } + onHandleAssist(data, structure, content); + } + + /** + * Called when there has been a failure transferring the {@link AssistStructure} to + * the assistant. This may happen, for example, if the data is too large and results + * in an out of memory exception, or the client has provided corrupt data. This will + * be called immediately before {@link #onHandleAssist} and the AssistStructure supplied + * there afterwards will be null. + * + * @param failure The failure exception that was thrown when building the + * {@link AssistStructure}. + */ + public void onAssistStructureFailure(Throwable failure) { + } + + /** + * Called to receive data from the application that the user was currently viewing when + * an assist session is started. + * + * @param data Arbitrary data supplied by the app through + * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}. + * @param structure If available, the structure definition of all windows currently + * displayed by the app; if structure has been turned off by the user, will be null. + * @param content Additional content data supplied by the app through + * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. + */ public void onHandleAssist(Bundle data, AssistStructure structure, AssistContent content) { } + /** + * Called to receive a screenshot of what the user was currently viewing when an assist + * session is started. Will be null if screenshots are disabled by the user. + */ public void onHandleScreenshot(Bitmap screenshot) { } diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index e78cf8f..aa8b71c 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -269,7 +269,15 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback for (int a = 0; a < n; a++) { char c = temp[a]; - if (c == '\n' || c == '\t' || c >= FIRST_RIGHT_TO_LEFT) { + if (c == '\n' || c == '\t' || + (c >= 0x0590 && c <= 0x08FF) || // RTL scripts + c == 0x200F || // Bidi format character + (c >= 0x202A && c <= 0x202E) || // Bidi format characters + (c >= 0x2066 && c <= 0x2069) || // Bidi format characters + (c >= 0xD800 && c <= 0xDFFF) || // surrogate pairs + (c >= 0xFB1D && c <= 0xFDFF) || // Hebrew and Arabic presentation forms + (c >= 0xFE70 && c <= 0xFEFE) // Arabic presentation forms + ) { boring = false; break outer; } diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index a55a08c..dc1d6f6 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -137,7 +137,12 @@ public class Html { } /** - * Returns an HTML representation of the provided Spanned text. + * Returns an HTML representation of the provided Spanned text. A best effort is + * made to add HTML tags corresponding to spans. Also note that HTML metacharacters + * (such as "<" and "&") within the input text are escaped. + * + * @param text input text to convert + * @return string containing input converted to HTML */ public static String toHtml(Spanned text) { StringBuilder out = new StringBuilder(); diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index d822138..fa347b9 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -206,7 +206,6 @@ public abstract class Layout { mText = text; mPaint = paint; - mWorkPaint = new TextPaint(); mWidth = width; mAlignment = align; mSpacingMult = spacingMult; @@ -1993,7 +1992,6 @@ public abstract class Layout { private CharSequence mText; private TextPaint mPaint; - /* package */ TextPaint mWorkPaint; private int mWidth; private Alignment mAlignment = Alignment.ALIGN_NORMAL; private float mSpacingMult; diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java index d114d32..5c5deb4 100644 --- a/core/java/android/text/SpannableStringInternal.java +++ b/core/java/android/text/SpannableStringInternal.java @@ -214,10 +214,6 @@ import java.lang.reflect.Array; Object ret1 = null; for (int i = 0; i < spanCount; i++) { - if (kind != null && !kind.isInstance(spans[i])) { - continue; - } - int spanStart = data[i * COLUMNS + START]; int spanEnd = data[i * COLUMNS + END]; @@ -237,6 +233,11 @@ import java.lang.reflect.Array; } } + // verify span class as late as possible, since it is expensive + if (kind != null && !kind.isInstance(spans[i])) { + continue; + } + if (count == 0) { ret1 = spans[i]; count++; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index cdff395..efc9e1a 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -744,7 +744,8 @@ public class StaticLayout extends Layout { && (ellipsize == TextUtils.TruncateAt.END || (mMaximumVisibleLineCount == 1 && ellipsize != TextUtils.TruncateAt.MARQUEE)); - if (remainingLineCount < breakCount && ellipsisMayBeApplied) { + if (remainingLineCount > 0 && remainingLineCount < breakCount && + ellipsisMayBeApplied) { // Treat the last line and overflowed lines as a single line. breaks[remainingLineCount - 1] = breaks[breakCount - 1]; // Calculate width and flag. diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java index 4862f01..39f66a5 100644 --- a/core/java/android/util/LocalLog.java +++ b/core/java/android/util/LocalLog.java @@ -55,6 +55,12 @@ public final class LocalLog { } } + public synchronized void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) { + for (int i = mLog.size() - 1; i >= 0; i--) { + pw.println(mLog.get(i)); + } + } + public static class ReadOnlyLocalLog { private final LocalLog mLog; ReadOnlyLocalLog(LocalLog log) { diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 64210ea..be564f8 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -342,6 +342,14 @@ public interface WindowManagerPolicy { boolean isGoneForLayoutLw(); /** + * Returns true if the window has a surface that it has drawn a + * complete UI in to. Note that this is different from {@link #hasDrawnLw()} + * in that it also returns true if the window is READY_TO_SHOW, but was not yet + * promoted to HAS_DRAWN. + */ + boolean isDrawnLw(); + + /** * Returns true if this window has been shown on screen at some time in * the past. Must be called with the window manager lock held. */ diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java index c54b28d..e3ce6f2 100644 --- a/core/java/android/widget/AppSecurityPermissions.java +++ b/core/java/android/widget/AppSecurityPermissions.java @@ -61,8 +61,6 @@ import java.util.Set; */ public class AppSecurityPermissions { - public static final int WHICH_PERSONAL = 1<<0; - public static final int WHICH_DEVICE = 1<<1; public static final int WHICH_NEW = 1<<2; public static final int WHICH_ALL = 0xffff; @@ -75,7 +73,8 @@ public class AppSecurityPermissions { = new HashMap<String, MyPermissionGroupInfo>(); private final List<MyPermissionGroupInfo> mPermGroupsList = new ArrayList<MyPermissionGroupInfo>(); - private final PermissionGroupInfoComparator mPermGroupComparator = new PermissionGroupInfoComparator(); + private final PermissionGroupInfoComparator mPermGroupComparator = + new PermissionGroupInfoComparator(); private final PermissionInfoComparator mPermComparator = new PermissionInfoComparator(); private final List<MyPermissionInfo> mPermsList = new ArrayList<MyPermissionInfo>(); private final CharSequence mNewPermPrefix; @@ -85,8 +84,6 @@ public class AppSecurityPermissions { CharSequence mLabel; final ArrayList<MyPermissionInfo> mNewPermissions = new ArrayList<MyPermissionInfo>(); - final ArrayList<MyPermissionInfo> mPersonalPermissions = new ArrayList<MyPermissionInfo>(); - final ArrayList<MyPermissionInfo> mDevicePermissions = new ArrayList<MyPermissionInfo>(); final ArrayList<MyPermissionInfo> mAllPermissions = new ArrayList<MyPermissionInfo>(); MyPermissionGroupInfo(PermissionInfo perm) { @@ -98,18 +95,12 @@ public class AppSecurityPermissions { super(info); } - public Drawable loadGroupIcon(PackageManager pm) { + public Drawable loadGroupIcon(Context context, PackageManager pm) { if (icon != 0) { return loadUnbadgedIcon(pm); } else { - ApplicationInfo appInfo; - try { - appInfo = pm.getApplicationInfo(packageName, 0); - return appInfo.loadUnbadgedIcon(pm); - } catch (NameNotFoundException e) { - } + return context.getDrawable(R.drawable.ic_perm_device_info); } - return null; } } @@ -163,7 +154,7 @@ public class AppSecurityPermissions { PackageManager pm = getContext().getPackageManager(); Drawable icon = null; if (first) { - icon = grp.loadGroupIcon(pm); + icon = grp.loadGroupIcon(getContext(), pm); } CharSequence label = perm.mLabel; if (perm.mNew && newPermPrefix != null) { @@ -213,7 +204,7 @@ public class AppSecurityPermissions { builder.setMessage(sbuilder.toString()); } builder.setCancelable(true); - builder.setIcon(mGroup.loadGroupIcon(pm)); + builder.setIcon(mGroup.loadGroupIcon(getContext(), pm)); addRevokeUIIfNecessary(builder); mDialog = builder.show(); mDialog.setCanceledOnTouchOutside(true); @@ -358,13 +349,6 @@ public class AppSecurityPermissions { } for (int i=0; i<strList.length; i++) { String permName = strList[i]; - // If we are only looking at an existing app, then we only - // care about permissions that have actually been granted to it. - if (installedPkgInfo != null && info != installedPkgInfo) { - if ((flagsList[i]&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { - continue; - } - } try { PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0); if (tmpPermInfo == null) { @@ -437,10 +421,6 @@ public class AppSecurityPermissions { private List<MyPermissionInfo> getPermissionList(MyPermissionGroupInfo grp, int which) { if (which == WHICH_NEW) { return grp.mNewPermissions; - } else if (which == WHICH_PERSONAL) { - return grp.mPersonalPermissions; - } else if (which == WHICH_DEVICE) { - return grp.mDevicePermissions; } else { return grp.mAllPermissions; } @@ -583,15 +563,8 @@ public class AppSecurityPermissions { private static class PermissionGroupInfoComparator implements Comparator<MyPermissionGroupInfo> { private final Collator sCollator = Collator.getInstance(); - PermissionGroupInfoComparator() { - } + @Override public final int compare(MyPermissionGroupInfo a, MyPermissionGroupInfo b) { - if (((a.flags^b.flags)&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) { - return ((a.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) ? -1 : 1; - } - if (a.priority != b.priority) { - return a.priority > b.priority ? -1 : 1; - } return sCollator.compare(a.mLabel, b.mLabel); } } @@ -634,11 +607,6 @@ public class AppSecurityPermissions { if (pInfo.mNew) { addPermToList(group.mNewPermissions, pInfo); } - if ((group.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) { - addPermToList(group.mPersonalPermissions, pInfo); - } else { - addPermToList(group.mDevicePermissions, pInfo); - } } } } @@ -658,12 +626,5 @@ public class AppSecurityPermissions { mPermGroupsList.add(pgrp); } Collections.sort(mPermGroupsList, mPermGroupComparator); - if (localLOGV) { - for (MyPermissionGroupInfo grp : mPermGroupsList) { - Log.i(TAG, "Group " + grp.name + " personal=" - + ((grp.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) - + " priority=" + grp.priority); - } - } } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 96e033a..d897f49 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -16,6 +16,12 @@ package android.widget; +import java.text.BreakIterator; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + import android.R; import android.annotation.Nullable; import android.app.PendingIntent; @@ -106,12 +112,6 @@ import com.android.internal.util.GrowingArrayUtils; import com.android.internal.util.Preconditions; import com.android.internal.widget.EditableInputConnection; -import java.text.BreakIterator; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; - /** * Helper class used by TextView to handle editable text views. @@ -127,6 +127,7 @@ public class Editor { private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20; private static final float LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS = 0.5f; private static final int UNSET_X_VALUE = -1; + private static final int UNSET_LINE = -1; // Tag used when the Editor maintains its own separate UndoManager. private static final String UNDO_OWNER_TAG = "Editor"; @@ -3510,7 +3511,11 @@ public class Editor { // Minimum touch target size for handles private int mMinSize; // Indicates the line of text that the handle is on. - protected int mPrevLine = -1; + protected int mPrevLine = UNSET_LINE; + // Indicates the line of text that the user was touching. This can differ from mPrevLine + // when selecting text when the handles jump to the end / start of words which may be on + // a different line. + protected int mPreviousLineTouched = UNSET_LINE; public HandleView(Drawable drawableLtr, Drawable drawableRtl) { super(mTextView.getContext()); @@ -3801,6 +3806,7 @@ public class Editor { mLastParentX = positionListener.getPositionX(); mLastParentY = positionListener.getPositionY(); mIsDragging = true; + mPreviousLineTouched = UNSET_LINE; break; } @@ -4015,8 +4021,12 @@ public class Editor { Layout layout = mTextView.getLayout(); int offset; if (layout != null) { - int currLine = getCurrentLineAdjustedForSlop(layout, mPrevLine, y); + if (mPreviousLineTouched == UNSET_LINE) { + mPreviousLineTouched = mTextView.getLineAtCoordinate(y); + } + int currLine = getCurrentLineAdjustedForSlop(layout, mPreviousLineTouched, y); offset = mTextView.getOffsetAtCoordinate(currLine, x); + mPreviousLineTouched = currLine; } else { offset = mTextView.getOffsetForPosition(x, y); } @@ -4092,9 +4102,13 @@ public class Editor { return; } + if (mPreviousLineTouched == UNSET_LINE) { + mPreviousLineTouched = mTextView.getLineAtCoordinate(y); + } + boolean positionCursor = false; final int selectionEnd = mTextView.getSelectionEnd(); - int currLine = getCurrentLineAdjustedForSlop(layout, mPrevLine, y); + int currLine = getCurrentLineAdjustedForSlop(layout, mPreviousLineTouched, y); int initialOffset = mTextView.getOffsetAtCoordinate(currLine, x); if (initialOffset >= selectionEnd) { @@ -4138,9 +4152,9 @@ public class Editor { } else { final float xDiff = x - mPrevX; if (atRtl) { - isExpanding = xDiff > 0 || currLine > mPrevLine; + isExpanding = xDiff > 0 || currLine > mPreviousLineTouched; } else { - isExpanding = xDiff < 0 || currLine < mPrevLine; + isExpanding = xDiff < 0 || currLine < mPreviousLineTouched; } } @@ -4204,6 +4218,7 @@ public class Editor { offset = getNextCursorOffset(selectionEnd, false); mTouchWordDelta = 0.0f; } + mPreviousLineTouched = currLine; positionAtCursorOffset(offset, false); } mPrevX = x; @@ -4218,8 +4233,9 @@ public class Editor { @Override public boolean onTouchEvent(MotionEvent event) { boolean superResult = super.onTouchEvent(event); - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - // Reset the touch word offset when the user has lifted their finger. + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + // Reset the touch word offset and x value when the user + // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; } @@ -4280,9 +4296,13 @@ public class Editor { return; } + if (mPreviousLineTouched == UNSET_LINE) { + mPreviousLineTouched = mTextView.getLineAtCoordinate(y); + } + boolean positionCursor = false; final int selectionStart = mTextView.getSelectionStart(); - int currLine = getCurrentLineAdjustedForSlop(layout, mPrevLine, y); + int currLine = getCurrentLineAdjustedForSlop(layout, mPreviousLineTouched, y); int initialOffset = mTextView.getOffsetAtCoordinate(currLine, x); if (initialOffset <= selectionStart) { @@ -4326,9 +4346,9 @@ public class Editor { } else { final float xDiff = x - mPrevX; if (atRtl) { - isExpanding = xDiff < 0 || currLine < mPrevLine; + isExpanding = xDiff < 0 || currLine < mPreviousLineTouched; } else { - isExpanding = xDiff > 0 || currLine > mPrevLine; + isExpanding = xDiff > 0 || currLine > mPreviousLineTouched; } } @@ -4392,6 +4412,7 @@ public class Editor { offset = getNextCursorOffset(selectionStart, true); mTouchWordDelta = 0.0f; } + mPreviousLineTouched = currLine; positionAtCursorOffset(offset, false); } mPrevX = x; @@ -4406,8 +4427,9 @@ public class Editor { @Override public boolean onTouchEvent(MotionEvent event) { boolean superResult = super.onTouchEvent(event); - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - // Reset the touch word offset when the user has lifted their finger. + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + // Reset the touch word offset and x value when the user + // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; } @@ -4416,10 +4438,16 @@ public class Editor { } private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { + final int trueLine = mTextView.getLineAtCoordinate(y); if (layout == null || prevLine > layout.getLineCount() || layout.getLineCount() <= 0 || prevLine < 0) { // Invalid parameters, just return whatever line is at y. - return mTextView.getLineAtCoordinate(y); + return trueLine; + } + + if (Math.abs(trueLine - prevLine) >= 2) { + // Only stick to lines if we're within a line of the previous selection. + return trueLine; } final float verticalOffset = mTextView.viewportToContentVerticalOffset(); diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index a1582f2..e13b96f 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -91,6 +91,7 @@ public class ImageView extends View { private boolean mColorMod = false; private Drawable mDrawable = null; + private ImageViewBitmapDrawable mRecycleableBitmapDrawable = null; private ColorStateList mDrawableTintList = null; private PorterDuff.Mode mDrawableTintMode = null; private boolean mHasDrawableTint = false; @@ -570,6 +571,17 @@ public class ImageView extends View { } } + private static class ImageViewBitmapDrawable extends BitmapDrawable { + public ImageViewBitmapDrawable(Resources res, Bitmap bitmap) { + super(res, bitmap); + } + + @Override + public void setBitmap(Bitmap bitmap) { + super.setBitmap(bitmap); + } + }; + /** * Sets a Bitmap as the content of this ImageView. * @@ -577,9 +589,16 @@ public class ImageView extends View { */ @android.view.RemotableViewMethod public void setImageBitmap(Bitmap bm) { - // if this is used frequently, may handle bitmaps explicitly - // to reduce the intermediate drawable object - setImageDrawable(new BitmapDrawable(mContext.getResources(), bm)); + // Hacky fix to force setImageDrawable to do a full setImageDrawable + // instead of doing an object reference comparison + mDrawable = null; + if (mRecycleableBitmapDrawable == null) { + mRecycleableBitmapDrawable = new ImageViewBitmapDrawable( + mContext.getResources(), bm); + } else { + mRecycleableBitmapDrawable.setBitmap(bm); + } + setImageDrawable(mRecycleableBitmapDrawable); } public void setImageState(int[] state, boolean merge) { @@ -848,6 +867,10 @@ public class ImageView extends View { } private void updateDrawable(Drawable d) { + if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) { + mRecycleableBitmapDrawable.setBitmap(null); + } + if (mDrawable != null) { mDrawable.setCallback(null); unscheduleDrawable(mDrawable); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 42ac599..131ba46 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6347,17 +6347,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (text.text != null) { if (content == null) { setText(text.text, TextView.BufferType.EDITABLE); - } else if (text.partialStartOffset < 0) { - removeParcelableSpans(content, 0, content.length()); - content.replace(0, content.length(), text.text); } else { - final int N = content.length(); - int start = text.partialStartOffset; - if (start > N) start = N; - int end = text.partialEndOffset; - if (end > N) end = N; + int start = 0; + int end = content.length(); + + if (text.partialStartOffset >= 0) { + final int N = content.length(); + start = text.partialStartOffset; + if (start > N) start = N; + end = text.partialEndOffset; + if (end > N) end = N; + } + removeParcelableSpans(content, start, end); - content.replace(start, end, text.text); + if (TextUtils.equals(content.subSequence(start, end), text.text)) { + if (text.text instanceof Spanned) { + // OK to copy spans only. + TextUtils.copySpansFrom((Spanned) text.text, start, end, + Object.class, content, start); + } + } else { + content.replace(start, end, text.text); + } } } |
