diff options
Diffstat (limited to 'core/java/android')
134 files changed, 4851 insertions, 1900 deletions
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 480d171..fd40d99 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -16,6 +16,8 @@ package android.accounts; +import android.annotation.RequiresPermission; +import android.annotation.Size; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -49,6 +51,11 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static android.Manifest.permission.AUTHENTICATE_ACCOUNTS; +import static android.Manifest.permission.GET_ACCOUNTS; +import static android.Manifest.permission.MANAGE_ACCOUNTS; +import static android.Manifest.permission.USE_CREDENTIALS; + /** * This class provides access to a centralized registry of the user's * online accounts. The user enters credentials (username and password) once @@ -207,8 +214,7 @@ public class AccountManager { * were authenticated successfully. Time is specified in milliseconds since * epoch. */ - public static final String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = - "lastAuthenticatedTimeMillisEpoch"; + public static final String KEY_LAST_AUTHENTICATED_TIME = "lastAuthenticatedTime"; /** * Authenticators using 'customTokens' option will also get the UID of the @@ -320,6 +326,7 @@ public class AccountManager { * @param account The account to query for a password * @return The account's password, null if none or if the account doesn't exist */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public String getPassword(final Account account) { if (account == null) throw new IllegalArgumentException("account is null"); try { @@ -345,6 +352,7 @@ public class AccountManager { * @param account The account to query for user data * @return The user data, null if the account or key doesn't exist */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public String getUserData(final Account account, final String key) { if (account == null) throw new IllegalArgumentException("account is null"); if (key == null) throw new IllegalArgumentException("key is null"); @@ -410,6 +418,7 @@ public class AccountManager { * @return An array of {@link Account}, one for each account. Empty * (never null) if no accounts have been added. */ + @RequiresPermission(GET_ACCOUNTS) public Account[] getAccounts() { try { return mService.getAccounts(null); @@ -432,6 +441,7 @@ public class AccountManager { * @return An array of {@link Account}, one for each account. Empty * (never null) if no accounts have been added. */ + @RequiresPermission(GET_ACCOUNTS) public Account[] getAccountsAsUser(int userId) { try { return mService.getAccountsAsUser(null, userId); @@ -491,6 +501,7 @@ public class AccountManager { * @return An array of {@link Account}, one per matching account. Empty * (never null) if no accounts of the specified type have been added. */ + @RequiresPermission(GET_ACCOUNTS) public Account[] getAccountsByType(String type) { return getAccountsByTypeAsUser(type, Process.myUserHandle()); } @@ -577,6 +588,7 @@ public class AccountManager { * @return An {@link AccountManagerFuture} which resolves to a Boolean, * true if the account exists and has all of the specified features. */ + @RequiresPermission(GET_ACCOUNTS) public AccountManagerFuture<Boolean> hasFeatures(final Account account, final String[] features, AccountManagerCallback<Boolean> callback, Handler handler) { @@ -622,6 +634,7 @@ public class AccountManager { * {@link Account}, one per account of the specified type which * matches the requested features. */ + @RequiresPermission(GET_ACCOUNTS) public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( final String type, final String[] features, AccountManagerCallback<Account[]> callback, Handler handler) { @@ -660,6 +673,7 @@ public class AccountManager { * @return True if the account was successfully added, false if the account * already exists, the account is null, or another error occurs. */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public boolean addAccountExplicitly(Account account, String password, Bundle userdata) { if (account == null) throw new IllegalArgumentException("account is null"); try { @@ -671,8 +685,8 @@ public class AccountManager { } /** - * Informs the system that the account has been authenticated recently. This - * recency may be used by other applications to verify the account. This + * Notifies the system that the account has just been authenticated. This + * information may be used by other applications to verify the account. This * should be called only when the user has entered correct credentials for * the account. * <p> @@ -685,7 +699,8 @@ public class AccountManager { * * @param account The {@link Account} to be updated. */ - public boolean accountAuthenticated(Account account) { + @RequiresPermission(AUTHENTICATE_ACCOUNTS) + public boolean notifyAccountAuthenticated(Account account) { if (account == null) throw new IllegalArgumentException("account is null"); try { @@ -716,9 +731,10 @@ public class AccountManager { * after the name change. If successful the account's name will be the * specified new name. */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public AccountManagerFuture<Account> renameAccount( final Account account, - final String newName, + @Size(min = 1) final String newName, AccountManagerCallback<Account> callback, Handler handler) { if (account == null) throw new IllegalArgumentException("account is null."); @@ -784,6 +800,7 @@ public class AccountManager { * {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)} * instead */ + @RequiresPermission(MANAGE_ACCOUNTS) @Deprecated public AccountManagerFuture<Boolean> removeAccount(final Account account, AccountManagerCallback<Boolean> callback, Handler handler) { @@ -838,6 +855,7 @@ public class AccountManager { * adding accounts (of this type) has been disabled by policy * </ul> */ + @RequiresPermission(MANAGE_ACCOUNTS) public AccountManagerFuture<Bundle> removeAccount(final Account account, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { if (account == null) throw new IllegalArgumentException("account is null"); @@ -910,6 +928,7 @@ public class AccountManager { * account did not exist, the account is null, or another error * occurs. */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public boolean removeAccountExplicitly(Account account) { if (account == null) throw new IllegalArgumentException("account is null"); try { @@ -936,6 +955,7 @@ public class AccountManager { * @param accountType The account type of the auth token to invalidate, must not be null * @param authToken The auth token to invalidate, may be null */ + @RequiresPermission(anyOf = {MANAGE_ACCOUNTS, USE_CREDENTIALS}) public void invalidateAuthToken(final String accountType, final String authToken) { if (accountType == null) throw new IllegalArgumentException("accountType is null"); try { @@ -965,6 +985,7 @@ public class AccountManager { * @return The cached auth token for this account and type, or null if * no auth token is cached or the account does not exist. */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public String peekAuthToken(final Account account, final String authTokenType) { if (account == null) throw new IllegalArgumentException("account is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); @@ -991,6 +1012,7 @@ public class AccountManager { * @param account The account to set a password for * @param password The password to set, null to clear the password */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public void setPassword(final Account account, final String password) { if (account == null) throw new IllegalArgumentException("account is null"); try { @@ -1015,6 +1037,7 @@ public class AccountManager { * * @param account The account whose password to clear */ + @RequiresPermission(MANAGE_ACCOUNTS) public void clearPassword(final Account account) { if (account == null) throw new IllegalArgumentException("account is null"); try { @@ -1040,6 +1063,7 @@ public class AccountManager { * @param key The userdata key to set. Must not be null * @param value The value to set, null to clear this userdata key */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public void setUserData(final Account account, final String key, final String value) { if (account == null) throw new IllegalArgumentException("account is null"); if (key == null) throw new IllegalArgumentException("key is null"); @@ -1067,6 +1091,7 @@ public class AccountManager { * @param authTokenType The type of the auth token, see {#getAuthToken} * @param authToken The auth token to add to the cache */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public void setAuthToken(Account account, final String authTokenType, final String authToken) { if (account == null) throw new IllegalArgumentException("account is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); @@ -1101,6 +1126,7 @@ public class AccountManager { * @throws java.io.IOException if the authenticator experienced an I/O problem * creating a new auth token, usually because of network trouble */ + @RequiresPermission(USE_CREDENTIALS) public String blockingGetAuthToken(Account account, String authTokenType, boolean notifyAuthFailure) throws OperationCanceledException, IOException, AuthenticatorException { @@ -1175,6 +1201,7 @@ public class AccountManager { * authenticator-dependent. The caller should verify the validity of the * account before requesting an auth token. */ + @RequiresPermission(USE_CREDENTIALS) public AccountManagerFuture<Bundle> getAuthToken( final Account account, final String authTokenType, final Bundle options, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { @@ -1265,6 +1292,7 @@ public class AccountManager { * boolean, AccountManagerCallback, android.os.Handler)} instead */ @Deprecated + @RequiresPermission(USE_CREDENTIALS) public AccountManagerFuture<Bundle> getAuthToken( final Account account, final String authTokenType, final boolean notifyAuthFailure, @@ -1343,6 +1371,7 @@ public class AccountManager { * authenticator-dependent. The caller should verify the validity of the * account before requesting an auth token. */ + @RequiresPermission(USE_CREDENTIALS) public AccountManagerFuture<Bundle> getAuthToken( final Account account, final String authTokenType, final Bundle options, final boolean notifyAuthFailure, @@ -1412,6 +1441,7 @@ public class AccountManager { * creating a new account, usually because of network trouble * </ul> */ + @RequiresPermission(MANAGE_ACCOUNTS) public AccountManagerFuture<Bundle> addAccount(final String accountType, final String authTokenType, final String[] requiredFeatures, final Bundle addAccountOptions, @@ -1587,7 +1617,7 @@ public class AccountManager { * password prompt. * * <p>Also the returning Bundle may contain {@link - * #KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH} indicating the last time the + * #KEY_LAST_AUTHENTICATED_TIME} indicating the last time the * credential was validated/created. * * If an error occurred,{@link AccountManagerFuture#getResult()} throws: @@ -1599,6 +1629,7 @@ public class AccountManager { * verifying the password, usually because of network trouble * </ul> */ + @RequiresPermission(MANAGE_ACCOUNTS) public AccountManagerFuture<Bundle> confirmCredentials(final Account account, final Bundle options, final Activity activity, @@ -1675,6 +1706,7 @@ public class AccountManager { * verifying the password, usually because of network trouble * </ul> */ + @RequiresPermission(MANAGE_ACCOUNTS) public AccountManagerFuture<Bundle> updateCredentials(final Account account, final String authTokenType, final Bundle options, final Activity activity, @@ -1726,6 +1758,7 @@ public class AccountManager { * updating settings, usually because of network trouble * </ul> */ + @RequiresPermission(MANAGE_ACCOUNTS) public AccountManagerFuture<Bundle> editProperties(final String accountType, final Activity activity, final AccountManagerCallback<Bundle> callback, final Handler handler) { @@ -2259,6 +2292,7 @@ public class AccountManager { * updating settings, usually because of network trouble * </ul> */ + @RequiresPermission(MANAGE_ACCOUNTS) public AccountManagerFuture<Bundle> getAuthTokenByFeatures( final String accountType, final String authTokenType, final String[] features, final Activity activity, final Bundle addAccountOptions, @@ -2383,6 +2417,7 @@ public class AccountManager { * @throws IllegalArgumentException if listener is null * @throws IllegalStateException if listener was already added */ + @RequiresPermission(GET_ACCOUNTS) public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener, Handler handler, boolean updateImmediately) { if (listener == null) { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 69cba78..7260d10 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -21,6 +21,7 @@ import android.annotation.DrawableRes; import android.annotation.IdRes; import android.annotation.IntDef; import android.annotation.LayoutRes; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StyleRes; @@ -108,6 +109,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * An activity is a single, focused thing that the user can do. Almost all @@ -706,8 +708,6 @@ public class Activity extends ContextThemeWrapper /*package*/ ActivityThread mMainThread; Activity mParent; boolean mCalled; - boolean mCheckedForLoaderManager; - boolean mLoadersStarted; /*package*/ boolean mResumed; private boolean mStopped; boolean mFinished; @@ -726,8 +726,8 @@ public class Activity extends ContextThemeWrapper static final class NonConfigurationInstances { Object activity; HashMap<String, Object> children; - ArrayList<Fragment> fragments; - ArrayMap<String, LoaderManagerImpl> loaders; + List<Fragment> fragments; + ArrayMap<String, LoaderManager> loaders; VoiceInteractor voiceInteractor; } /* package */ NonConfigurationInstances mLastNonConfigurationInstances; @@ -747,26 +747,13 @@ public class Activity extends ContextThemeWrapper private CharSequence mTitle; private int mTitleColor = 0; - final FragmentManagerImpl mFragments = new FragmentManagerImpl(); - final FragmentContainer mContainer = new FragmentContainer() { - @Override - @Nullable - public View findViewById(int id) { - return Activity.this.findViewById(id); - } - @Override - public boolean hasView() { - Window window = Activity.this.getWindow(); - return (window != null && window.peekDecorView() != null); - } - }; + // we must have a handler before the FragmentController is constructed + final Handler mHandler = new Handler(); + final FragmentController mFragments = FragmentController.createController(new HostCallbacks()); // Most recent call to requestVisibleBehind(). boolean mVisibleBehind; - ArrayMap<String, LoaderManagerImpl> mAllLoaderManagers; - LoaderManagerImpl mLoaderManager; - private static final class ManagedCursor { ManagedCursor(Cursor cursor) { mCursor = cursor; @@ -802,7 +789,6 @@ public class Activity extends ContextThemeWrapper private final Object mInstanceTracker = StrictMode.trackActivity(this); private Thread mUiThread; - final Handler mHandler = new Handler(); ActivityTransitionState mActivityTransitionState = new ActivityTransitionState(); SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK; @@ -863,28 +849,7 @@ public class Activity extends ContextThemeWrapper * Return the LoaderManager for this activity, creating it if needed. */ public LoaderManager getLoaderManager() { - if (mLoaderManager != null) { - return mLoaderManager; - } - mCheckedForLoaderManager = true; - mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true); - return mLoaderManager; - } - - LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) { - if (mAllLoaderManagers == null) { - mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>(); - } - LoaderManagerImpl lm = mAllLoaderManagers.get(who); - if (lm == null) { - if (create) { - lm = new LoaderManagerImpl(who, this, started); - mAllLoaderManagers.put(who, lm); - } - } else { - lm.updateActivity(this); - } - return lm; + return mFragments.getLoaderManager(); } /** @@ -927,11 +892,12 @@ public class Activity extends ContextThemeWrapper * @see #onRestoreInstanceState * @see #onPostCreate */ + @MainThread @CallSuper protected void onCreate(@Nullable Bundle savedInstanceState) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); if (mLastNonConfigurationInstances != null) { - mAllLoaderManagers = mLastNonConfigurationInstances.loaders; + mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders); } if (mActivityInfo.parentActivityName != null) { if (mActionBar == null) { @@ -1172,15 +1138,7 @@ public class Activity extends ContextThemeWrapper if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this); mCalled = true; - if (!mLoadersStarted) { - mLoadersStarted = true; - if (mLoaderManager != null) { - mLoaderManager.doStart(); - } else if (!mCheckedForLoaderManager) { - mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false); - } - mCheckedForLoaderManager = true; - } + mFragments.doLoaderStart(); getApplication().dispatchActivityStarted(this); } @@ -1873,27 +1831,9 @@ public class Activity extends ContextThemeWrapper NonConfigurationInstances retainNonConfigurationInstances() { Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); - ArrayList<Fragment> fragments = mFragments.retainNonConfig(); - boolean retainLoaders = false; - if (mAllLoaderManagers != null) { - // prune out any loader managers that were already stopped and so - // have nothing useful to retain. - final int N = mAllLoaderManagers.size(); - LoaderManagerImpl loaders[] = new LoaderManagerImpl[N]; - for (int i=N-1; i>=0; i--) { - loaders[i] = mAllLoaderManagers.valueAt(i); - } - for (int i=0; i<N; i++) { - LoaderManagerImpl lm = loaders[i]; - if (lm.mRetaining) { - retainLoaders = true; - } else { - lm.doDestroy(); - mAllLoaderManagers.remove(lm.mWho); - } - } - } - if (activity == null && children == null && fragments == null && !retainLoaders + List<Fragment> fragments = mFragments.retainNonConfig(); + ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig(); + if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { return null; } @@ -1902,7 +1842,7 @@ public class Activity extends ContextThemeWrapper nci.activity = activity; nci.children = children; nci.fragments = fragments; - nci.loaders = mAllLoaderManagers; + nci.loaders = loaders; nci.voiceInteractor = mVoiceInteractor; return nci; } @@ -1924,18 +1864,7 @@ public class Activity extends ContextThemeWrapper * with this activity. */ public FragmentManager getFragmentManager() { - return mFragments; - } - - void invalidateFragment(String who) { - //Log.v(TAG, "invalidateFragmentIndex: index=" + index); - if (mAllLoaderManagers != null) { - LoaderManagerImpl lm = mAllLoaderManagers.get(who); - if (lm != null && !lm.mRetaining) { - lm.doDestroy(); - mAllLoaderManagers.remove(who); - } - } + return mFragments.getFragmentManager(); } /** @@ -2518,7 +2447,7 @@ public class Activity extends ContextThemeWrapper return; } - if (!mFragments.popBackStackImmediate()) { + if (!mFragments.getFragmentManager().popBackStackImmediate()) { finishAfterTransition(); } } @@ -5518,21 +5447,13 @@ public class Activity extends ContextThemeWrapper writer.print(mResumed); writer.print(" mStopped="); writer.print(mStopped); writer.print(" mFinished="); writer.println(mFinished); - writer.print(innerPrefix); writer.print("mLoadersStarted="); - writer.println(mLoadersStarted); writer.print(innerPrefix); writer.print("mChangingConfigurations="); writer.println(mChangingConfigurations); writer.print(innerPrefix); writer.print("mCurrentConfig="); writer.println(mCurrentConfig); - if (mLoaderManager != null) { - writer.print(prefix); writer.print("Loader Manager "); - writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager))); - writer.println(":"); - mLoaderManager.dump(prefix + " ", fd, writer, args); - } - - mFragments.dump(prefix, fd, writer, args); + mFragments.dumpLoaders(innerPrefix, fd, writer, args); + mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args); if (getWindow() != null && getWindow().peekDecorView() != null && @@ -6128,7 +6049,7 @@ public class Activity extends ContextThemeWrapper Configuration config, String referrer, IVoiceInteractor voiceInteractor) { attachBaseContext(context); - mFragments.attachActivity(this, mContainer, null); + mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this); mWindow.setCallback(this); @@ -6211,18 +6132,7 @@ public class Activity extends ContextThemeWrapper " did not call through to super.onStart()"); } mFragments.dispatchStart(); - if (mAllLoaderManagers != null) { - final int N = mAllLoaderManagers.size(); - LoaderManagerImpl loaders[] = new LoaderManagerImpl[N]; - for (int i=N-1; i>=0; i--) { - loaders[i] = mAllLoaderManagers.valueAt(i); - } - for (int i=0; i<N; i++) { - LoaderManagerImpl lm = loaders[i]; - lm.finishRetain(); - lm.doReportStart(); - } - } + mFragments.reportLoaderStart(); mActivityTransitionState.enterReady(this); } @@ -6328,16 +6238,7 @@ public class Activity extends ContextThemeWrapper final void performStop() { mDoReportFullyDrawn = false; - if (mLoadersStarted) { - mLoadersStarted = false; - if (mLoaderManager != null) { - if (!mChangingConfigurations) { - mLoaderManager.doStop(); - } else { - mLoaderManager.doRetain(); - } - } - } + mFragments.doLoaderStop(mChangingConfigurations /*retain*/); if (!mStopped) { if (mWindow != null) { @@ -6379,9 +6280,7 @@ public class Activity extends ContextThemeWrapper mWindow.destroy(); mFragments.dispatchDestroy(); onDestroy(); - if (mLoaderManager != null) { - mLoaderManager.doDestroy(); - } + mFragments.doLoaderDestroy(); if (mVoiceInteractor != null) { mVoiceInteractor.detachActivity(); } @@ -6541,4 +6440,79 @@ public class Activity extends ContextThemeWrapper return intent != null && PackageManager.ACTION_REQUEST_PERMISSIONS.equals(intent.getAction()); } + + class HostCallbacks extends FragmentHostCallback<Activity> { + public HostCallbacks() { + super(Activity.this /*activity*/); + } + + @Override + public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + Activity.this.dump(prefix, fd, writer, args); + } + + @Override + public boolean onShouldSaveFragmentState(Fragment fragment) { + return !isFinishing(); + } + + @Override + public LayoutInflater onGetLayoutInflater() { + final LayoutInflater result = Activity.this.getLayoutInflater(); + if (onUseFragmentManagerInflaterFactory()) { + return result.cloneInContext(Activity.this); + } + return result; + } + + @Override + public boolean onUseFragmentManagerInflaterFactory() { + // Newer platform versions use the child fragment manager's LayoutInflaterFactory. + return getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP; + } + + @Override + public Activity onGetHost() { + return Activity.this; + } + + @Override + public void onInvalidateOptionsMenu() { + Activity.this.invalidateOptionsMenu(); + } + + @Override + public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode, + Bundle options) { + Activity.this.startActivityFromFragment(fragment, intent, requestCode, options); + } + + @Override + public boolean onHasWindowAnimations() { + return getWindow() != null; + } + + @Override + public int onGetWindowAnimations() { + final Window w = getWindow(); + return (w == null) ? 0 : w.getAttributes().windowAnimations; + } + + @Override + public void onAttachFragment(Fragment fragment) { + Activity.this.onAttachFragment(fragment); + } + + @Nullable + @Override + public View onFindViewById(int id) { + return Activity.this.findViewById(id); + } + + @Override + public boolean onHasView() { + final Window w = getWindow(); + return (w != null && w.peekDecorView() != null); + } + } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 576a046..9bbb4be 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -16,8 +16,10 @@ package android.app; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.graphics.Canvas; import android.graphics.Matrix; @@ -26,6 +28,7 @@ import android.os.BatteryStats; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.util.Log; import com.android.internal.app.ProcessStats; import com.android.internal.os.TransferPipe; import com.android.internal.util.FastPrintWriter; @@ -2396,7 +2399,24 @@ public class ActivityManager { } catch (RemoteException e) { } } - + + /** + * Kills the specified UID. + * @param uid The UID to kill. + * @param reason The reason for the kill. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.KILL_UID) + public void killUid(int uid, String reason) { + try { + ActivityManagerNative.getDefault().killUid(uid, reason); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't kill uid:" + uid, e); + } + } + /** * Have the system perform a force stop of everything associated with * the given application package. All processes that share its uid diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 9bad9bb..da6d8c5 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -114,7 +114,6 @@ import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.net.InetAddress; -import java.security.Security; import java.text.DateFormat; import java.util.ArrayList; import java.util.List; @@ -1639,6 +1638,12 @@ public final class ActivityThread { return sCurrentActivityThread; } + public static String currentOpPackageName() { + ActivityThread am = currentActivityThread(); + return (am != null && am.getApplication() != null) + ? am.getApplication().getOpPackageName() : null; + } + public static String currentPackageName() { ActivityThread am = currentActivityThread(); return (am != null && am.mBoundApplication != null) @@ -5338,7 +5343,7 @@ public final class ActivityThread { // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); - Security.addProvider(new AndroidKeyStoreProvider()); + AndroidKeyStoreProvider.install(); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 9d1d312..b0fda9c 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -26,6 +26,10 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.os.WorkSource; +import android.text.TextUtils; +import libcore.util.ZoneInfoDB; + +import java.io.IOException; /** * This class provides access to the system alarm services. These allow you @@ -151,6 +155,7 @@ public class AlarmManager private final IAlarmManager mService; private final boolean mAlwaysExact; + private final int mTargetSdkVersion; /** @@ -159,8 +164,8 @@ public class AlarmManager AlarmManager(IAlarmManager service, Context ctx) { mService = service; - final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion; - mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT); + mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion; + mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT); } private long legacyExactLength() { @@ -585,12 +590,38 @@ public class AlarmManager } /** - * Set the system default time zone. - * Requires the permission android.permission.SET_TIME_ZONE. - * - * @param timeZone in the format understood by {@link java.util.TimeZone} + * Sets the system's persistent default time zone. This is the time zone for all apps, even + * after a reboot. Use {@link java.util.TimeZone#setDefault} if you just want to change the + * time zone within your app, and even then prefer to pass an explicit + * {@link java.util.TimeZone} to APIs that require it rather than changing the time zone for + * all threads. + * + * <p> On android M and above, it is an error to pass in a non-Olson timezone to this + * function. Note that this is a bad idea on all Android releases because POSIX and + * the {@code TimeZone} class have opposite interpretations of {@code '+'} and {@code '-'} + * in the same non-Olson ID. + * + * @param timeZone one of the Olson ids from the list returned by + * {@link java.util.TimeZone#getAvailableIDs} */ public void setTimeZone(String timeZone) { + if (TextUtils.isEmpty(timeZone)) { + return; + } + + // Reject this timezone if it isn't an Olson zone we recognize. + if (mTargetSdkVersion >= Build.VERSION_CODES.MNC) { + boolean hasTimeZone = false; + try { + hasTimeZone = ZoneInfoDB.getInstance().hasTimeZone(timeZone); + } catch (IOException ignored) { + } + + if (!hasTimeZone) { + throw new IllegalArgumentException("Timezone: " + timeZone + " is not an Olson ID"); + } + } + try { mService.setTimeZone(timeZone); } catch (RemoteException ex) { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 223d528..5aa399b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -217,12 +217,21 @@ public class AppOpsManager { public static final int OP_READ_PHONE_STATE = 51; /** @hide Add voicemail messages to the voicemail content provider. */ public static final int OP_ADD_VOICEMAIL = 52; + /** @hide Access APIs for SIP calling over VOIP or WiFi. */ + public static final int OP_USE_SIP = 53; + /** @hide Intercept outgoing calls. */ + public static final int OP_PROCESS_OUTGOING_CALLS = 54; + /** @hide User the fingerprint API. */ + public static final int OP_USE_FINGERPRINT = 55; + /** @hide Access to body sensors such as heart rate, etc. */ + public static final int OP_BODY_SENSORS = 56; + /** @hide Read previously received cell broadcast messages. */ + public static final int OP_READ_CELL_BROADCASTS = 57; /** @hide */ - public static final int _NUM_OP = 53; + public static final int _NUM_OP = 58; /** Access to coarse location information. */ - public static final String OPSTR_COARSE_LOCATION = - "android:coarse_location"; + public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; /** Access to fine location information. */ public static final String OPSTR_FINE_LOCATION = "android:fine_location"; @@ -237,7 +246,68 @@ public class AppOpsManager { = "android:get_usage_stats"; /** Activate a VPN connection without user intervention. @hide */ @SystemApi - public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; + public static final String OPSTR_ACTIVATE_VPN + = "android:activate_vpn"; + /** @hide Allows an application to read the user's contacts data. */ + public static final String OPSTR_READ_CONTACTS + = "android:read_contacts"; + /** @hide Allows an application to write to the user's contacts data. */ + public static final String OPSTR_WRITE_CONTACTS + = "android:write_contacts"; + /** @hide Allows an application to read the user's call log. */ + public static final String OPSTR_READ_CALL_LOG + = "android:read_call_log"; + /** @hide Allows an application to write to the user's call log. */ + public static final String OPSTR_WRITE_CALL_LOG + = "android:write_call_log"; + /** @hide Allows an application to read the user's calendar data. */ + public static final String OPSTR_READ_CALENDAR + = "android:read_calendar"; + /** @hide Allows an application to write to the user's calendar data. */ + public static final String OPSTR_WRITE_CALENDAR + = "android:write_calendar"; + /** @hide Allows an application to initiate a phone call. */ + public static final String OPSTR_CALL_PHONE + = "android:call_phone"; + /** @hide Allows an application to read SMS messages. */ + public static final String OPSTR_READ_SMS + = "android:read_sms"; + /** @hide Allows an application to receive SMS messages. */ + public static final String OPSTR_RECEIVE_SMS + = "android:receive_sms"; + /** @hide Allows an application to receive MMS messages. */ + public static final String OPSTR_RECEIVE_MMS + = "android:receive_mms"; + /** @hide Allows an application to receive WAP push messages. */ + public static final String OPSTR_RECEIVE_WAP_PUSH + = "android:receive_wap_push"; + /** @hide Allows an application to send SMS messages. */ + public static final String OPSTR_SEND_SMS + = "android:send_sms"; + /** @hide Required to be able to access the camera device. */ + public static final String OPSTR_CAMERA + = "android:camera"; + /** @hide Required to be able to access the microphone device. */ + public static final String OPSTR_RECORD_AUDIO + = "android:record_audio"; + /** @hide Required to access phone state related information. */ + public static final String OPSTR_READ_PHONE_STATE + = "android:read_phone_state"; + /** @hide Required to access phone state related information. */ + public static final String OPSTR_ADD_VOICEMAIL + = "android:add_voicemail"; + /** @hide Access APIs for SIP calling over VOIP or WiFi */ + public static final String OPSTR_USE_SIP + = "android:use_sip"; + /** @hide Use the fingerprint API. */ + public static final String OPSTR_USE_FINGERPRINT + = "android:use_fingerprint"; + /** @hide Access to body sensors such as heart rate, etc. */ + public static final String OPSTR_BODY_SENSORS + = "android:body_sensors"; + /** @hide Read previously received cell broadcast messages. */ + public static final String OPSTR_READ_CELL_BROADCASTS + = "android:read_cell_broadcasts"; /** * This maps each operation to the operation that serves as the @@ -300,7 +370,12 @@ public class AppOpsManager { OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT, OP_READ_PHONE_STATE, - OP_ADD_VOICEMAIL + OP_ADD_VOICEMAIL, + OP_USE_SIP, + OP_PROCESS_OUTGOING_CALLS, + OP_USE_FINGERPRINT, + OP_BODY_SENSORS, + OP_READ_CELL_BROADCASTS }; /** @@ -312,30 +387,30 @@ public class AppOpsManager { OPSTR_FINE_LOCATION, null, null, + OPSTR_READ_CONTACTS, + OPSTR_WRITE_CONTACTS, + OPSTR_READ_CALL_LOG, + OPSTR_WRITE_CALL_LOG, + OPSTR_READ_CALENDAR, + OPSTR_WRITE_CALENDAR, null, null, null, + OPSTR_CALL_PHONE, + OPSTR_READ_SMS, null, + OPSTR_RECEIVE_SMS, null, + OPSTR_RECEIVE_MMS, + OPSTR_RECEIVE_WAP_PUSH, + OPSTR_SEND_SMS, null, null, null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + OPSTR_CAMERA, + OPSTR_RECORD_AUDIO, null, null, null, @@ -359,8 +434,13 @@ public class AppOpsManager { null, null, null, + OPSTR_READ_PHONE_STATE, + OPSTR_ADD_VOICEMAIL, + OPSTR_USE_SIP, null, - null + OPSTR_USE_FINGERPRINT, + OPSTR_BODY_SENSORS, + OPSTR_READ_CELL_BROADCASTS }; /** @@ -420,7 +500,12 @@ public class AppOpsManager { "ASSIST_STRUCTURE", "ASSIST_SCREENSHOT", "OP_READ_PHONE_STATE", - "ADD_VOICEMAIL" + "ADD_VOICEMAIL", + "USE_SIP", + "PROCESS_OUTGOING_CALLS", + "USE_FINGERPRINT", + "BODY_SENSORS", + "READ_CELL_BROADCASTS" }; /** @@ -480,7 +565,12 @@ public class AppOpsManager { null, // no permission for receiving assist structure null, // no permission for receiving assist screenshot Manifest.permission.READ_PHONE_STATE, - Manifest.permission.ADD_VOICEMAIL + Manifest.permission.ADD_VOICEMAIL, + Manifest.permission.USE_SIP, + Manifest.permission.PROCESS_OUTGOING_CALLS, + Manifest.permission.USE_FINGERPRINT, + Manifest.permission.BODY_SENSORS, + Manifest.permission.READ_CELL_BROADCASTS }; /** @@ -541,7 +631,12 @@ public class AppOpsManager { null, // ASSIST_STRUCTURE null, // ASSIST_SCREENSHOT null, // READ_PHONE_STATE - null // ADD_VOICEMAIL + null, // ADD_VOICEMAIL + null, // USE_SIP + null, // PROCESS_OUTGOING_CALLS + null, // USE_FINGERPRINT + null, // BODY_SENSORS + null // READ_CELL_BROADCASTS }; /** @@ -601,7 +696,12 @@ public class AppOpsManager { false, //ASSIST_STRUCTURE false, //ASSIST_SCREENSHOT false, //READ_PHONE_STATE - false //ADD_VOICEMAIL + false, //ADD_VOICEMAIL + false, // USE_SIP + false, // PROCESS_OUTGOING_CALLS + false, // USE_FINGERPRINT + false, // BODY_SENSORS + false // READ_CELL_BROADCASTS }; /** @@ -660,6 +760,11 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED }; @@ -723,10 +828,23 @@ public class AppOpsManager { false, false, false, + false, + false, + false, + false, + false, false }; - private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>(); + /** + * Mapping from an app op name to the app op code. + */ + private static HashMap<String, Integer> sOpStrToOp = new HashMap<>(); + + /** + * Mapping from a permission to the corresponding app op. + */ + private static HashMap<String, Integer> sPermToOp = new HashMap<>(); static { if (sOpToSwitch.length != _NUM_OP) { @@ -766,6 +884,11 @@ public class AppOpsManager { sOpStrToOp.put(sOpToString[i], i); } } + for (int i=0; i<_NUM_OP; i++) { + if (sOpPerms[i] != null) { + sPermToOp.put(sOpPerms[i], i); + } + } } /** @@ -814,6 +937,15 @@ public class AppOpsManager { } /** + * Retrieve the app op code for a permission, or null if there is not one. + * @hide + */ + public static int permissionToOpCode(String permission) { + Integer boxedOpCode = sPermToOp.get(permission); + return boxedOpCode != null ? boxedOpCode : OP_NONE; + } + + /** * Retrieve whether the op allows the system (and system ui) to * bypass the user restriction. * @hide @@ -1066,6 +1198,25 @@ public class AppOpsManager { } /** + * Gets the app op name associated with a given permission. + * The app op name is one of the public constants defined + * in this class such as {@link #OPSTR_COARSE_LOCATION}. + * + * @param permission The permission. + * @return The app op associated with the permission or null. + * + * @hide + */ + @SystemApi + public static String permissionToOp(String permission) { + final Integer opCode = sPermToOp.get(permission); + if (opCode == null) { + return null; + } + return sOpToString[opCode]; + } + + /** * Monitor for changes to the operating mode for the given op in the given app package. * @param op The operation to monitor, one of OPSTR_*. * @param packageName The name of the application to monitor. diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 16a2430..90293a4 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -62,6 +62,7 @@ import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -79,6 +80,7 @@ import android.view.Display; import dalvik.system.VMRuntime; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import com.android.internal.util.UserIcons; @@ -1560,13 +1562,7 @@ final class ApplicationPackageManager extends PackageManager { public @Nullable VolumeInfo getPrimaryStorageCurrentVolume() { final StorageManager storage = mContext.getSystemService(StorageManager.class); final String volumeUuid = storage.getPrimaryStorageUuid(); - if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) { - return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL); - } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) { - return storage.getPrimaryPhysicalVolume(); - } else { - return storage.findVolumeByUuid(volumeUuid); - } + return storage.findVolumeByQualifiedUuid(volumeUuid); } @Override @@ -2054,7 +2050,7 @@ final class ApplicationPackageManager extends PackageManager { /** {@hide} */ private static class MoveCallbackDelegate extends IPackageMoveObserver.Stub implements Handler.Callback { - private static final int MSG_STARTED = 1; + private static final int MSG_CREATED = 1; private static final int MSG_STATUS_CHANGED = 2; final MoveCallback mCallback; @@ -2067,26 +2063,38 @@ final class ApplicationPackageManager extends PackageManager { @Override public boolean handleMessage(Message msg) { - final int moveId = msg.arg1; switch (msg.what) { - case MSG_STARTED: - mCallback.onStarted(moveId, (String) msg.obj); + case MSG_CREATED: { + final SomeArgs args = (SomeArgs) msg.obj; + mCallback.onCreated(args.argi1, (Bundle) args.arg2); + args.recycle(); return true; - case MSG_STATUS_CHANGED: - mCallback.onStatusChanged(moveId, msg.arg2, (long) msg.obj); + } + case MSG_STATUS_CHANGED: { + final SomeArgs args = (SomeArgs) msg.obj; + mCallback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3); + args.recycle(); return true; + } } return false; } @Override - public void onStarted(int moveId, String title) { - mHandler.obtainMessage(MSG_STARTED, moveId, 0, title).sendToTarget(); + public void onCreated(int moveId, Bundle extras) { + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.arg2 = extras; + mHandler.obtainMessage(MSG_CREATED, args).sendToTarget(); } @Override public void onStatusChanged(int moveId, int status, long estMillis) { - mHandler.obtainMessage(MSG_STATUS_CHANGED, moveId, status, estMillis).sendToTarget(); + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.argi2 = status; + args.arg3 = estMillis; + mHandler.obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget(); } } diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java index 9946d79..3abbb5b 100644 --- a/core/java/android/app/AssistStructure.java +++ b/core/java/android/app/AssistStructure.java @@ -224,6 +224,7 @@ final public class AssistStructure implements Parcelable { static final int FLAGS_CHECKED = 0x00000200; static final int FLAGS_CLICKABLE = 0x00004000; static final int FLAGS_LONG_CLICKABLE = 0x00200000; + static final int FLAGS_STYLUS_BUTTON_PRESSABLE = 0x00400000; int mFlags; @@ -401,6 +402,10 @@ final public class AssistStructure implements Parcelable { return (mFlags&ViewNode.FLAGS_LONG_CLICKABLE) != 0; } + public boolean isStylusButtonPressable() { + return (mFlags&ViewNode.FLAGS_STYLUS_BUTTON_PRESSABLE) != 0; + } + public String getClassName() { return mClassName; } @@ -513,6 +518,12 @@ final public class AssistStructure implements Parcelable { } @Override + public void setStylusButtonPressable(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_STYLUS_BUTTON_PRESSABLE) + | (state ? ViewNode.FLAGS_STYLUS_BUTTON_PRESSABLE : 0); + } + + @Override public void setFocusable(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSABLE) | (state ? ViewNode.FLAGS_FOCUSABLE : 0); diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 8fb048b..49644a7 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -416,14 +416,14 @@ final class BackStackRecord extends FragmentTransaction implements public CharSequence getBreadCrumbTitle() { if (mBreadCrumbTitleRes != 0) { - return mManager.mActivity.getText(mBreadCrumbTitleRes); + return mManager.mHost.getContext().getText(mBreadCrumbTitleRes); } return mBreadCrumbTitleText; } public CharSequence getBreadCrumbShortTitle() { if (mBreadCrumbShortTitleRes != 0) { - return mManager.mActivity.getText(mBreadCrumbShortTitleRes); + return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes); } return mBreadCrumbShortTitleText; } @@ -868,7 +868,7 @@ final class BackStackRecord extends FragmentTransaction implements */ private void calculateFragments(SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) { - if (!mManager.mContainer.hasView()) { + if (!mManager.mContainer.onHasView()) { return; // nothing to see, so no transitions } Op op = mHead; @@ -926,7 +926,7 @@ final class BackStackRecord extends FragmentTransaction implements */ public void calculateBackFragments(SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) { - if (!mManager.mContainer.hasView()) { + if (!mManager.mContainer.onHasView()) { return; // nothing to see, so no transitions } Op op = mHead; @@ -1002,7 +1002,7 @@ final class BackStackRecord extends FragmentTransaction implements // Adding a non-existent target view makes sure that the transitions don't target // any views by default. They'll only target the views we tell add. If we don't // add any, then no views will be targeted. - state.nonExistentView = new View(mManager.mActivity); + state.nonExistentView = new View(mManager.mHost.getContext()); // Go over all leaving fragments. for (int i = 0; i < firstOutFragments.size(); i++) { @@ -1275,7 +1275,7 @@ final class BackStackRecord extends FragmentTransaction implements */ private void configureTransitions(int containerId, TransitionState state, boolean isBack, SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) { - ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.findViewById(containerId); + ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.onFindViewById(containerId); if (sceneRoot != null) { Fragment inFragment = lastInFragments.get(containerId); Fragment outFragment = firstOutFragments.get(containerId); diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java index bde5a61..2fb8cc2 100644 --- a/core/java/android/app/DialogFragment.java +++ b/core/java/android/app/DialogFragment.java @@ -410,7 +410,7 @@ public class DialogFragment extends Fragment return (LayoutInflater)mDialog.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); } - return (LayoutInflater)mActivity.getSystemService( + return (LayoutInflater) mHost.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 169952a..0f286fb 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -239,7 +239,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK)); } ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, - mAllSharedElementNames, resultCode, data); + mSharedElementNames, resultCode, data); mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { @Override public void onTranslucentConversionComplete(boolean drawComplete) { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 4fdae7f..40c5c64 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; -import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -94,19 +93,20 @@ final class FragmentState implements Parcelable { mSavedFragmentState = in.readBundle(); } - public Fragment instantiate(Activity activity, Fragment parent) { + public Fragment instantiate(FragmentHostCallback host, Fragment parent) { if (mInstance != null) { return mInstance; } + final Context context = host.getContext(); if (mArguments != null) { - mArguments.setClassLoader(activity.getClassLoader()); + mArguments.setClassLoader(context.getClassLoader()); } - mInstance = Fragment.instantiate(activity, mClassName, mArguments); + mInstance = Fragment.instantiate(context, mClassName, mArguments); if (mSavedFragmentState != null) { - mSavedFragmentState.setClassLoader(activity.getClassLoader()); + mSavedFragmentState.setClassLoader(context.getClassLoader()); mInstance.mSavedFragmentState = mSavedFragmentState; } mInstance.setIndex(mIndex, parent); @@ -117,7 +117,7 @@ final class FragmentState implements Parcelable { mInstance.mTag = mTag; mInstance.mRetainInstance = mRetainInstance; mInstance.mDetached = mDetached; - mInstance.mFragmentManager = activity.mFragments; + mInstance.mFragmentManager = host.mFragmentManager; if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, "Instantiated fragment " + mInstance); @@ -425,7 +425,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene FragmentManagerImpl mFragmentManager; // Activity this fragment is attached to. - Activity mActivity; + FragmentHostCallback mHost; // Private fragment manager for child fragments inside of this one. FragmentManagerImpl mChildFragmentManager; @@ -775,20 +775,36 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } /** + * Return the {@link Context} this fragment is currently associated with. + */ + public Context getContext() { + return mHost == null ? null : mHost.getContext(); + } + + /** * Return the Activity this fragment is currently associated with. */ final public Activity getActivity() { - return mActivity; + return mHost == null ? null : mHost.getActivity(); + } + + /** + * Return the host object of this fragment. May return {@code null} if the fragment + * isn't currently being hosted. + */ + @Nullable + final public Object getHost() { + return mHost == null ? null : mHost.onGetHost(); } /** * Return <code>getActivity().getResources()</code>. */ final public Resources getResources() { - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - return mActivity.getResources(); + return mHost.getContext().getResources(); } /** @@ -870,7 +886,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * Return true if the fragment is currently added to its activity. */ final public boolean isAdded() { - return mActivity != null && mAdded; + return mHost != null && mAdded; } /** @@ -1037,11 +1053,11 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (mLoaderManager != null) { return mLoaderManager; } - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true); + mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, true); return mLoaderManager; } @@ -1065,15 +1081,15 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * Context.startActivity(Intent, Bundle)} for more details. */ public void startActivity(Intent intent, Bundle options) { - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } if (options != null) { - mActivity.startActivityFromFragment(this, intent, -1, options); + mHost.onStartActivityFromFragment(this, intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. - mActivity.startActivityFromFragment(this, intent, -1); + mHost.onStartActivityFromFragment(this, intent, -1, null /*options*/); } } @@ -1090,10 +1106,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * containing Activity. */ public void startActivityForResult(Intent intent, int requestCode, Bundle options) { - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - mActivity.startActivityFromFragment(this, intent, requestCode, options); + mHost.onStartActivityFromFragment(this, intent, requestCode, options); } /** @@ -1181,11 +1197,12 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * @see android.content.Context#checkSelfPermission(String) */ public final void requestPermissions(@NonNull String[] permissions, int requestCode) { - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - Intent intent = mActivity.getPackageManager().buildRequestPermissionsIntent(permissions); - mActivity.startActivityFromFragment(this, intent, requestCode, null); + Intent intent = + mHost.getContext().getPackageManager().buildRequestPermissionsIntent(permissions); + mHost.onStartActivityFromFragment(this, intent, requestCode, null); } /** @@ -1211,19 +1228,16 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * inflation. Maybe this should become a public API. Note sure. */ public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { - // Newer platform versions use the child fragment manager's LayoutInflaterFactory. - if (mActivity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { - LayoutInflater result = mActivity.getLayoutInflater().cloneInContext(mActivity); + final LayoutInflater result = mHost.onGetLayoutInflater(); + if (mHost.onUseFragmentManagerInflaterFactory()) { getChildFragmentManager(); // Init if needed; use raw implementation below. result.setPrivateFactory(mChildFragmentManager.getLayoutInflaterFactory()); - return result; - } else { - return mActivity.getLayoutInflater(); } + return result; } /** - * @deprecated Use {@link #onInflate(Activity, AttributeSet, Bundle)} instead. + * @deprecated Use {@link #onInflate(Context, AttributeSet, Bundle)} instead. */ @Deprecated public void onInflate(AttributeSet attrs, Bundle savedInstanceState) { @@ -1266,29 +1280,29 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentArguments.java * create} * - * @param activity The Activity that is inflating this fragment. + * @param context The Context that is inflating this fragment. * @param attrs The attributes at the tag where the fragment is * being created. * @param savedInstanceState If the fragment is being re-created from * a previous saved state, this is the state. */ - public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { onInflate(attrs, savedInstanceState); mCalled = true; - TypedArray a = activity.obtainStyledAttributes(attrs, + TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment); - mEnterTransition = loadTransition(activity, a, mEnterTransition, null, + mEnterTransition = loadTransition(context, a, mEnterTransition, null, com.android.internal.R.styleable.Fragment_fragmentEnterTransition); - mReturnTransition = loadTransition(activity, a, mReturnTransition, USE_DEFAULT_TRANSITION, + mReturnTransition = loadTransition(context, a, mReturnTransition, USE_DEFAULT_TRANSITION, com.android.internal.R.styleable.Fragment_fragmentReturnTransition); - mExitTransition = loadTransition(activity, a, mExitTransition, null, + mExitTransition = loadTransition(context, a, mExitTransition, null, com.android.internal.R.styleable.Fragment_fragmentExitTransition); - mReenterTransition = loadTransition(activity, a, mReenterTransition, USE_DEFAULT_TRANSITION, + mReenterTransition = loadTransition(context, a, mReenterTransition, USE_DEFAULT_TRANSITION, com.android.internal.R.styleable.Fragment_fragmentReenterTransition); - mSharedElementEnterTransition = loadTransition(activity, a, mSharedElementEnterTransition, + mSharedElementEnterTransition = loadTransition(context, a, mSharedElementEnterTransition, null, com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition); - mSharedElementReturnTransition = loadTransition(activity, a, mSharedElementReturnTransition, + mSharedElementReturnTransition = loadTransition(context, a, mSharedElementReturnTransition, USE_DEFAULT_TRANSITION, com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition); if (mAllowEnterTransitionOverlap == null) { @@ -1300,12 +1314,39 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap, true); } a.recycle(); + + final Activity hostActivity = mHost == null ? null : mHost.getActivity(); + if (hostActivity != null) { + mCalled = false; + onInflate(hostActivity, attrs, savedInstanceState); + } } /** - * Called when a fragment is first attached to its activity. + * @deprecated Use {@link #onInflate(Context, AttributeSet, Bundle)} instead. + */ + @Deprecated + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + mCalled = true; + } + + /** + * Called when a fragment is first attached to its context. * {@link #onCreate(Bundle)} will be called after this. */ + public void onAttach(Context context) { + mCalled = true; + final Activity hostActivity = mHost == null ? null : mHost.getActivity(); + if (hostActivity != null) { + mCalled = false; + onAttach(hostActivity); + } + } + + /** + * @deprecated Use {@link #onAttach(Context)} instead. + */ + @Deprecated public void onAttach(Activity activity) { mCalled = true; } @@ -1428,7 +1469,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene mLoadersStarted = true; if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); + mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doStart(); @@ -1521,7 +1562,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // + " mLoaderManager=" + mLoaderManager); if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); + mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doDestroy(); @@ -1546,7 +1587,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene mBackStackNesting = 0; mFragmentManager = null; mChildFragmentManager = null; - mActivity = null; + mHost = null; mFragmentId = 0; mContainerId = 0; mTag = null; @@ -2034,9 +2075,9 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene writer.print(prefix); writer.print("mFragmentManager="); writer.println(mFragmentManager); } - if (mActivity != null) { - writer.print(prefix); writer.print("mActivity="); - writer.println(mActivity); + if (mHost != null) { + writer.print(prefix); writer.print("mHost="); + writer.println(mHost); } if (mParentFragment != null) { writer.print(prefix); writer.print("mParentFragment="); @@ -2094,10 +2135,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene void instantiateChildFragmentManager() { mChildFragmentManager = new FragmentManagerImpl(); - mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() { + mChildFragmentManager.attachController(mHost, new FragmentContainer() { @Override @Nullable - public View findViewById(int id) { + public View onFindViewById(int id) { if (mView == null) { throw new IllegalStateException("Fragment does not have a view"); } @@ -2105,7 +2146,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } @Override - public boolean hasView() { + public boolean onHasView() { return (mView != null); } }, this); @@ -2319,13 +2360,13 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene mLoadersStarted = false; if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); + mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { - if (mActivity == null || !mActivity.mChangingConfigurations) { - mLoaderManager.doStop(); - } else { + if (mRetaining) { mLoaderManager.doRetain(); + } else { + mLoaderManager.doStop(); } } } diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java new file mode 100644 index 0000000..b2e0300 --- /dev/null +++ b/core/java/android/app/FragmentContainer.java @@ -0,0 +1,38 @@ +/* + * 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.app; + +import android.annotation.IdRes; +import android.annotation.Nullable; +import android.view.View; + +/** + * Callbacks to a {@link Fragment}'s container. + */ +public abstract class FragmentContainer { + /** + * Return the view with the given resource ID. May return {@code null} if the + * view is not a child of this container. + */ + @Nullable + public abstract View onFindViewById(@IdRes int id); + + /** + * Return {@code true} if the container holds any view. + */ + public abstract boolean onHasView(); +} diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java new file mode 100644 index 0000000..28dadfa --- /dev/null +++ b/core/java/android/app/FragmentController.java @@ -0,0 +1,384 @@ +/* + * 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.app; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Provides integration points with a {@link FragmentManager} for a fragment host. + * <p> + * It is the responsibility of the host to take care of the Fragment's lifecycle. + * The methods provided by {@link FragmentController} are for that purpose. + */ +public class FragmentController { + private final FragmentHostCallback<?> mHost; + + /** + * Returns a {@link FragmentController}. + */ + public static final FragmentController createController(FragmentHostCallback<?> callbacks) { + return new FragmentController(callbacks); + } + + private FragmentController(FragmentHostCallback<?> callbacks) { + mHost = callbacks; + } + + /** + * Returns a {@link FragmentManager} for this controller. + */ + public FragmentManager getFragmentManager() { + return mHost.getFragmentManagerImpl(); + } + + /** + * Returns a {@link LoaderManager}. + */ + public LoaderManager getLoaderManager() { + return mHost.getLoaderManagerImpl(); + } + + /** + * Returns a fragment with the given identifier. + */ + @Nullable + public Fragment findFragmentByWho(String who) { + return mHost.mFragmentManager.findFragmentByWho(who); + } + + /** + * Attaches the host to the FragmentManager for this controller. The host must be + * attached before the FragmentManager can be used to manage Fragments. + * */ + public void attachHost(Fragment parent) { + mHost.mFragmentManager.attachController( + mHost, mHost /*container*/, parent); + } + + /** + * Instantiates a Fragment's view. + * + * @param parent The parent that the created view will be placed + * in; <em>note that this may be null</em>. + * @param name Tag name to be inflated. + * @param context The context the view is being created in. + * @param attrs Inflation attributes as specified in XML file. + * + * @return view the newly created view + */ + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + return mHost.mFragmentManager.onCreateView(parent, name, context, attrs); + } + + /** + * Marks the fragment state as unsaved. This allows for "state loss" detection. + */ + public void noteStateNotSaved() { + mHost.mFragmentManager.noteStateNotSaved(); + } + + /** + * Saves the state for all Fragments. + */ + public Parcelable saveAllState() { + return mHost.mFragmentManager.saveAllState(); + } + + /** + * Restores the saved state for all Fragments. The given Fragment list are Fragment + * instances retained across configuration changes. + * + * @see #retainNonConfig() + */ + public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) { + mHost.mFragmentManager.restoreAllState(state, nonConfigList); + } + + /** + * Returns a list of Fragments that have opted to retain their instance across + * configuration changes. + */ + public List<Fragment> retainNonConfig() { + return mHost.mFragmentManager.retainNonConfig(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the create state. + * <p>Call when Fragments should be created. + * + * @see Fragment#onCreate(Bundle) + */ + public void dispatchCreate() { + mHost.mFragmentManager.dispatchCreate(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the activity created state. + * <p>Call when Fragments should be informed their host has been created. + * + * @see Fragment#onActivityCreated(Bundle) + */ + public void dispatchActivityCreated() { + mHost.mFragmentManager.dispatchActivityCreated(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the start state. + * <p>Call when Fragments should be started. + * + * @see Fragment#onStart() + */ + public void dispatchStart() { + mHost.mFragmentManager.dispatchStart(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the resume state. + * <p>Call when Fragments should be resumed. + * + * @see Fragment#onResume() + */ + public void dispatchResume() { + mHost.mFragmentManager.dispatchResume(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the pause state. + * <p>Call when Fragments should be paused. + * + * @see Fragment#onPause() + */ + public void dispatchPause() { + mHost.mFragmentManager.dispatchPause(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the stop state. + * <p>Call when Fragments should be stopped. + * + * @see Fragment#onStop() + */ + public void dispatchStop() { + mHost.mFragmentManager.dispatchStop(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the destroy view state. + * <p>Call when the Fragment's views should be destroyed. + * + * @see Fragment#onDestroyView() + */ + public void dispatchDestroyView() { + mHost.mFragmentManager.dispatchDestroyView(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the destroy state. + * <p>Call when Fragments should be destroyed. + * + * @see Fragment#onDestroy() + */ + public void dispatchDestroy() { + mHost.mFragmentManager.dispatchDestroy(); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know a configuration change occurred. + * <p>Call when there is a configuration change. + * + * @see Fragment#onConfigurationChanged(Configuration) + */ + public void dispatchConfigurationChanged(Configuration newConfig) { + mHost.mFragmentManager.dispatchConfigurationChanged(newConfig); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know the device is in a low memory condition. + * <p>Call when the device is low on memory and Fragment's should trim + * their memory usage. + * + * @see Fragment#onLowMemory() + */ + public void dispatchLowMemory() { + mHost.mFragmentManager.dispatchLowMemory(); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know they should trim their memory usage. + * <p>Call when the Fragment can release allocated memory [such as if + * the Fragment is in the background]. + * + * @see Fragment#onTrimMemory(int) + */ + public void dispatchTrimMemory(int level) { + mHost.mFragmentManager.dispatchTrimMemory(level); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know they should create an options menu. + * <p>Call when the Fragment should create an options menu. + * + * @return {@code true} if the options menu contains items to display + * @see Fragment#onCreateOptionsMenu(Menu, MenuInflater) + */ + public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { + return mHost.mFragmentManager.dispatchCreateOptionsMenu(menu, inflater); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know they should prepare their options menu for display. + * <p>Call immediately before displaying the Fragment's options menu. + * + * @return {@code true} if the options menu contains items to display + * @see Fragment#onPrepareOptionsMenu(Menu) + */ + public boolean dispatchPrepareOptionsMenu(Menu menu) { + return mHost.mFragmentManager.dispatchPrepareOptionsMenu(menu); + } + + /** + * Sends an option item selection event to the Fragments managed by the + * controller's FragmentManager. Once the event has been consumed, + * no additional handling will be performed. + * <p>Call immediately after an options menu item has been selected + * + * @return {@code true} if the options menu selection event was consumed + * @see Fragment#onOptionsItemSelected(MenuItem) + */ + public boolean dispatchOptionsItemSelected(MenuItem item) { + return mHost.mFragmentManager.dispatchOptionsItemSelected(item); + } + + /** + * Sends a context item selection event to the Fragments managed by the + * controller's FragmentManager. Once the event has been consumed, + * no additional handling will be performed. + * <p>Call immediately after an options menu item has been selected + * + * @return {@code true} if the context menu selection event was consumed + * @see Fragment#onContextItemSelected(MenuItem) + */ + public boolean dispatchContextItemSelected(MenuItem item) { + return mHost.mFragmentManager.dispatchContextItemSelected(item); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know their options menu has closed. + * <p>Call immediately after closing the Fragment's options menu. + * + * @see Fragment#onOptionsMenuClosed(Menu) + */ + public void dispatchOptionsMenuClosed(Menu menu) { + mHost.mFragmentManager.dispatchOptionsMenuClosed(menu); + } + + /** + * Execute any pending actions for the Fragments managed by the + * controller's FragmentManager. + * <p>Call when queued actions can be performed [eg when the + * Fragment moves into a start or resume state]. + * @return {@code true} if queued actions were performed + */ + public boolean execPendingActions() { + return mHost.mFragmentManager.execPendingActions(); + } + + /** + * Starts the loaders. + */ + public void doLoaderStart() { + mHost.doLoaderStart(); + } + + /** + * Stops the loaders, optionally retaining their state. This is useful for keeping the + * loader state across configuration changes. + * + * @param retain When {@code true}, the loaders aren't stopped, but, their instances + * are retained in a started state + */ + public void doLoaderStop(boolean retain) { + mHost.doLoaderStop(retain); + } + + /** + * Destroys the loaders and, if their state is not being retained, removes them. + */ + public void doLoaderDestroy() { + mHost.doLoaderDestroy(); + } + + /** + * Lets the loaders know the host is ready to receive notifications. + */ + public void reportLoaderStart() { + mHost.reportLoaderStart(); + } + + /** + * Returns a list of LoaderManagers that have opted to retain their instance across + * configuration changes. + */ + public ArrayMap<String, LoaderManager> retainLoaderNonConfig() { + return mHost.retainLoaderNonConfig(); + } + + /** + * Restores the saved state for all LoaderManagers. The given LoaderManager list are + * LoaderManager instances retained across configuration changes. + * + * @see #retainLoaderNonConfig() + */ + public void restoreLoaderNonConfig(ArrayMap<String, LoaderManager> loaderManagers) { + mHost.restoreLoaderNonConfig(loaderManagers); + } + + /** + * Dumps the current state of the loaders. + */ + public void dumpLoaders(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + mHost.dumpLoaders(prefix, fd, writer, args); + } +} diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java new file mode 100644 index 0000000..3e753f0 --- /dev/null +++ b/core/java/android/app/FragmentHostCallback.java @@ -0,0 +1,314 @@ +/* + * 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.app; + +import android.annotation.Nullable; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.util.ArrayMap; +import android.view.LayoutInflater; +import android.view.View; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Integration points with the Fragment host. + * <p> + * Fragments may be hosted by any object; such as an {@link Activity}. In order to + * host fragments, implement {@link FragmentHostCallback}, overriding the methods + * applicable to the host. + */ +public abstract class FragmentHostCallback<E> extends FragmentContainer { + private final Activity mActivity; + final Context mContext; + private final Handler mHandler; + final int mWindowAnimations; + final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl(); + private ArrayMap<String, LoaderManager> mAllLoaderManagers; + private LoaderManagerImpl mLoaderManager; + private boolean mCheckedForLoaderManager; + private boolean mLoadersStarted; + + public FragmentHostCallback(Context context, Handler handler, int windowAnimations) { + this(null /*activity*/, context, handler, windowAnimations); + } + + FragmentHostCallback(Activity activity) { + this(activity, activity /*context*/, activity.mHandler, 0 /*windowAnimations*/); + } + + FragmentHostCallback(Activity activity, Context context, Handler handler, + int windowAnimations) { + mActivity = activity; + mContext = context; + mHandler = handler; + mWindowAnimations = windowAnimations; + } + + /** + * Print internal state into the given stream. + * + * @param prefix Desired prefix to prepend at each line of output. + * @param fd The raw file descriptor that the dump is being sent to. + * @param writer The PrintWriter to which you should dump your state. This will be closed + * for you after you return. + * @param args additional arguments to the dump request. + */ + public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + } + + /** + * Return {@code true} if the fragment's state needs to be saved. + */ + public boolean onShouldSaveFragmentState(Fragment fragment) { + return true; + } + + /** + * Return a {@link LayoutInflater}. + * See {@link Activity#getLayoutInflater()}. + */ + public LayoutInflater onGetLayoutInflater() { + return (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + /** + * Return {@code true} if the FragmentManager's LayoutInflaterFactory should be used. + */ + public boolean onUseFragmentManagerInflaterFactory() { + return false; + } + + /** + * Return the object that's currently hosting the fragment. If a {@link Fragment} + * is hosted by a {@link Activity}, the object returned here should be the same + * object returned from {@link Fragment#getActivity()}. + */ + @Nullable + public abstract E onGetHost(); + + /** + * Invalidates the activity's options menu. + * See {@link Activity#invalidateOptionsMenu()} + */ + public void onInvalidateOptionsMenu() { + } + + /** + * Starts a new {@link Activity} from the given fragment. + * See {@link Activity#startActivityForResult(Intent, int)}. + */ + public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode, + Bundle options) { + if (requestCode != -1) { + throw new IllegalStateException( + "Starting activity with a requestCode requires a FragmentActivity host"); + } + mContext.startActivity(intent); + } + + /** + * Return {@code true} if there are window animations. + */ + public boolean onHasWindowAnimations() { + return true; + } + + /** + * Return the window animations. + */ + public int onGetWindowAnimations() { + return mWindowAnimations; + } + + /** + * Called when a {@link Fragment} is being attached to this host, immediately + * after the call to its {@link Fragment#onAttach(Context)} method and before + * {@link Fragment#onCreate(Bundle)}. + */ + public void onAttachFragment(Fragment fragment) { + } + + @Nullable + @Override + public View onFindViewById(int id) { + return null; + } + + @Override + public boolean onHasView() { + return true; + } + + Activity getActivity() { + return mActivity; + } + + Context getContext() { + return mContext; + } + + Handler getHandler() { + return mHandler; + } + + FragmentManagerImpl getFragmentManagerImpl() { + return mFragmentManager; + } + + LoaderManagerImpl getLoaderManagerImpl() { + if (mLoaderManager != null) { + return mLoaderManager; + } + mCheckedForLoaderManager = true; + mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true /*create*/); + return mLoaderManager; + } + + void inactivateFragment(String who) { + //Log.v(TAG, "invalidateSupportFragment: who=" + who); + if (mAllLoaderManagers != null) { + LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who); + if (lm != null && !lm.mRetaining) { + lm.doDestroy(); + mAllLoaderManagers.remove(who); + } + } + } + + void doLoaderStart() { + if (mLoadersStarted) { + return; + } + mLoadersStarted = true; + + if (mLoaderManager != null) { + mLoaderManager.doStart(); + } else if (!mCheckedForLoaderManager) { + mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false); + } + mCheckedForLoaderManager = true; + } + + void doLoaderStop(boolean retain) { + if (mLoaderManager == null) { + return; + } + + if (!mLoadersStarted) { + return; + } + mLoadersStarted = false; + + if (retain) { + mLoaderManager.doRetain(); + } else { + mLoaderManager.doStop(); + } + } + + void doLoaderRetain() { + if (mLoaderManager == null) { + return; + } + mLoaderManager.doRetain(); + } + + void doLoaderDestroy() { + if (mLoaderManager == null) { + return; + } + mLoaderManager.doDestroy(); + } + + void reportLoaderStart() { + if (mAllLoaderManagers != null) { + final int N = mAllLoaderManagers.size(); + LoaderManagerImpl loaders[] = new LoaderManagerImpl[N]; + for (int i=N-1; i>=0; i--) { + loaders[i] = (LoaderManagerImpl) mAllLoaderManagers.valueAt(i); + } + for (int i=0; i<N; i++) { + LoaderManagerImpl lm = loaders[i]; + lm.finishRetain(); + lm.doReportStart(); + } + } + } + + LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) { + if (mAllLoaderManagers == null) { + mAllLoaderManagers = new ArrayMap<String, LoaderManager>(); + } + LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who); + if (lm == null) { + if (create) { + lm = new LoaderManagerImpl(who, this, started); + mAllLoaderManagers.put(who, lm); + } + } else { + lm.updateHostController(this); + } + return lm; + } + + ArrayMap<String, LoaderManager> retainLoaderNonConfig() { + boolean retainLoaders = false; + if (mAllLoaderManagers != null) { + // prune out any loader managers that were already stopped and so + // have nothing useful to retain. + final int N = mAllLoaderManagers.size(); + LoaderManagerImpl loaders[] = new LoaderManagerImpl[N]; + for (int i=N-1; i>=0; i--) { + loaders[i] = (LoaderManagerImpl) mAllLoaderManagers.valueAt(i); + } + for (int i=0; i<N; i++) { + LoaderManagerImpl lm = loaders[i]; + if (lm.mRetaining) { + retainLoaders = true; + } else { + lm.doDestroy(); + mAllLoaderManagers.remove(lm.mWho); + } + } + } + + if (retainLoaders) { + return mAllLoaderManagers; + } + return null; + } + + void restoreLoaderNonConfig(ArrayMap<String, LoaderManager> loaderManagers) { + mAllLoaderManagers = loaderManagers; + } + + void dumpLoaders(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + writer.print(prefix); writer.print("mLoadersStarted="); + writer.println(mLoadersStarted); + if (mLoaderManager != null) { + writer.print(prefix); writer.print("Loader Manager "); + writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager))); + writer.println(":"); + mLoaderManager.dump(prefix + " ", fd, writer, args); + } + } +} diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 975b20d..6b5239d 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -19,9 +19,7 @@ package android.app; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; -import android.annotation.Nullable; import android.content.Context; -import android.annotation.IdRes; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Bundle; @@ -48,6 +46,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Interface for interacting with {@link Fragment} objects inside of an @@ -393,15 +392,6 @@ final class FragmentManagerState implements Parcelable { } /** - * Callbacks from FragmentManagerImpl to its container. - */ -interface FragmentContainer { - @Nullable - public View findViewById(@IdRes int id); - public boolean hasView(); -} - -/** * Container for fragments associated with an activity. */ final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 { @@ -430,7 +420,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate ArrayList<OnBackStackChangedListener> mBackStackChangeListeners; int mCurState = Fragment.INITIALIZING; - Activity mActivity; + FragmentHostCallback<?> mHost; + FragmentController mController; FragmentContainer mContainer; Fragment mParent; @@ -455,10 +446,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate Log.e(TAG, ex.getMessage()); LogWriter logw = new LogWriter(Log.ERROR, TAG); PrintWriter pw = new FastPrintWriter(logw, false, 1024); - if (mActivity != null) { + if (mHost != null) { Log.e(TAG, "Activity state:"); try { - mActivity.dump(" ", null, pw, new String[] { }); + mHost.onDump(" ", null, pw, new String[] { }); } catch (Exception e) { pw.flush(); Log.e(TAG, "Failed dumping state", e); @@ -490,7 +481,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate public void popBackStack() { enqueueAction(new Runnable() { @Override public void run() { - popBackStackState(mActivity.mHandler, null, -1, 0); + popBackStackState(mHost.getHandler(), null, -1, 0); } }, false); } @@ -499,14 +490,14 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate public boolean popBackStackImmediate() { checkStateLoss(); executePendingTransactions(); - return popBackStackState(mActivity.mHandler, null, -1, 0); + return popBackStackState(mHost.getHandler(), null, -1, 0); } @Override public void popBackStack(final String name, final int flags) { enqueueAction(new Runnable() { @Override public void run() { - popBackStackState(mActivity.mHandler, name, -1, flags); + popBackStackState(mHost.getHandler(), name, -1, flags); } }, false); } @@ -515,7 +506,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate public boolean popBackStackImmediate(String name, int flags) { checkStateLoss(); executePendingTransactions(); - return popBackStackState(mActivity.mHandler, name, -1, flags); + return popBackStackState(mHost.getHandler(), name, -1, flags); } @Override @@ -525,7 +516,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } enqueueAction(new Runnable() { @Override public void run() { - popBackStackState(mActivity.mHandler, null, id, flags); + popBackStackState(mHost.getHandler(), null, id, flags); } }, false); } @@ -537,7 +528,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (id < 0) { throw new IllegalArgumentException("Bad id: " + id); } - return popBackStackState(mActivity.mHandler, null, id, flags); + return popBackStackState(mHost.getHandler(), null, id, flags); } @Override @@ -619,7 +610,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (mParent != null) { DebugUtils.buildShortClassTag(mParent, sb); } else { - DebugUtils.buildShortClassTag(mActivity, sb); + DebugUtils.buildShortClassTag(mHost, sb); } sb.append("}}"); return sb.toString(); @@ -716,7 +707,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } writer.print(prefix); writer.println("FragmentManager misc state:"); - writer.print(prefix); writer.print(" mActivity="); writer.println(mActivity); + writer.print(prefix); writer.print(" mHost="); writer.println(mHost); writer.print(prefix); writer.print(" mContainer="); writer.println(mContainer); if (mParent != null) { writer.print(prefix); writer.print(" mParent="); writer.println(mParent); @@ -747,7 +738,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } if (fragment.mNextAnim != 0) { - Animator anim = AnimatorInflater.loadAnimator(mActivity, fragment.mNextAnim); + Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(), fragment.mNextAnim); if (anim != null) { return anim; } @@ -762,14 +753,14 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate return null; } - if (transitionStyle == 0 && mActivity.getWindow() != null) { - transitionStyle = mActivity.getWindow().getAttributes().windowAnimations; + if (transitionStyle == 0 && mHost.onHasWindowAnimations()) { + transitionStyle = mHost.onGetWindowAnimations(); } if (transitionStyle == 0) { return null; } - TypedArray attrs = mActivity.obtainStyledAttributes(transitionStyle, + TypedArray attrs = mHost.getContext().obtainStyledAttributes(transitionStyle, com.android.internal.R.styleable.FragmentAnimation); int anim = attrs.getResourceId(styleIndex, 0); attrs.recycle(); @@ -778,7 +769,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate return null; } - return AnimatorInflater.loadAnimator(mActivity, anim); + return AnimatorInflater.loadAnimator(mHost.getContext(), anim); } public void performPendingDeferredStart(Fragment f) { @@ -848,18 +839,18 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } } - f.mActivity = mActivity; + f.mHost = mHost; f.mParentFragment = mParent; f.mFragmentManager = mParent != null - ? mParent.mChildFragmentManager : mActivity.mFragments; + ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); f.mCalled = false; - f.onAttach(mActivity); + f.onAttach(mHost.getContext()); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onAttach()"); } if (f.mParentFragment == null) { - mActivity.onAttachFragment(f); + mHost.onAttachFragment(f); } if (!f.mRetaining) { @@ -884,7 +875,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { - container = (ViewGroup)mContainer.findViewById(f.mContainerId); + container = (ViewGroup)mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { throwException(new IllegalArgumentException( "No view found for id 0x" @@ -954,7 +945,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (f.mView != null) { // Need to save the current view state if not // done already. - if (!mActivity.isFinishing() && f.mSavedViewState == null) { + if (!mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) { saveFragmentViewState(f); } } @@ -1030,7 +1021,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (!f.mRetaining) { makeInactive(f); } else { - f.mActivity = null; + f.mHost = null; f.mParentFragment = null; f.mFragmentManager = null; f.mChildFragmentManager = null; @@ -1053,7 +1044,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } void moveToState(int newState, int transit, int transitStyle, boolean always) { - if (mActivity == null && newState != Fragment.INITIALIZING) { + if (mHost == null && newState != Fragment.INITIALIZING) { throw new IllegalStateException("No activity"); } @@ -1078,8 +1069,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate startPendingDeferredFragments(); } - if (mNeedMenuInvalidate && mActivity != null && mCurState == Fragment.RESUMED) { - mActivity.invalidateOptionsMenu(); + if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) { + mHost.onInvalidateOptionsMenu(); mNeedMenuInvalidate = false; } } @@ -1126,7 +1117,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate mAvailIndices = new ArrayList<Integer>(); } mAvailIndices.add(f.mIndex); - mActivity.invalidateFragment(f.mWho); + mHost.inactivateFragment(f.mWho); f.initState(); } @@ -1349,7 +1340,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate checkStateLoss(); } synchronized (this) { - if (mDestroyed || mActivity == null) { + if (mDestroyed || mHost == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { @@ -1357,8 +1348,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } mPendingActions.add(action); if (mPendingActions.size() == 1) { - mActivity.mHandler.removeCallbacks(mExecCommit); - mActivity.mHandler.post(mExecCommit); + mHost.getHandler().removeCallbacks(mExecCommit); + mHost.getHandler().post(mExecCommit); } } } @@ -1427,7 +1418,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate throw new IllegalStateException("Recursive entry to executePendingTransactions"); } - if (Looper.myLooper() != mActivity.mHandler.getLooper()) { + if (Looper.myLooper() != mHost.getHandler().getLooper()) { throw new IllegalStateException("Must be called from main thread of process"); } @@ -1447,7 +1438,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } mPendingActions.toArray(mTmpActions); mPendingActions.clear(); - mActivity.mHandler.removeCallbacks(mExecCommit); + mHost.getHandler().removeCallbacks(mExecCommit); } mExecutingActions = true; @@ -1737,7 +1728,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate return fms; } - void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) { + void restoreAllState(Parcelable state, List<Fragment> nonConfig) { // If there is no saved state at all, then there can not be // any nonConfig fragments either, so that is that. if (state == null) return; @@ -1758,7 +1749,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate f.mAdded = false; f.mTarget = null; if (fs.mSavedFragmentState != null) { - fs.mSavedFragmentState.setClassLoader(mActivity.getClassLoader()); + fs.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader()); f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); f.mSavedFragmentState = fs.mSavedFragmentState; @@ -1775,7 +1766,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate for (int i=0; i<fms.mActive.length; i++) { FragmentState fs = fms.mActive[i]; if (fs != null) { - Fragment f = fs.instantiate(mActivity, mParent); + Fragment f = fs.instantiate(mHost, mParent); if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f); mActive.add(f); // Now that the fragment is instantiated (or came from being @@ -1851,9 +1842,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } - public void attachActivity(Activity activity, FragmentContainer container, Fragment parent) { - if (mActivity != null) throw new IllegalStateException("Already attached"); - mActivity = activity; + public void attachController(FragmentHostCallback<?> host, FragmentContainer container, + Fragment parent) { + if (mHost != null) throw new IllegalStateException("Already attached"); + mHost = host; mContainer = container; mParent = parent; } @@ -1898,7 +1890,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate mDestroyed = true; execPendingActions(); moveToState(Fragment.INITIALIZING, false); - mActivity = null; + mHost = null; mContainer = null; mParent = null; } @@ -2024,8 +2016,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate @Override public void invalidateOptionsMenu() { - if (mActivity != null && mCurState == Fragment.RESUMED) { - mActivity.invalidateOptionsMenu(); + if (mHost != null && mCurState == Fragment.RESUMED) { + mHost.onInvalidateOptionsMenu(); } else { mNeedMenuInvalidate = true; } @@ -2115,7 +2107,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate fragment.mTag = tag; fragment.mInLayout = true; fragment.mFragmentManager = this; - fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState); + fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); addFragment(fragment, true); } else if (fragment.mInLayout) { // A fragment already exists and it is not one we restored from @@ -2132,7 +2124,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // from last saved state), then give it the attributes to // initialize itself. if (!fragment.mRetaining) { - fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState); + fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); } } diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java index 96767ae..3cda973 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.WorkerThread; import android.content.Intent; import android.os.Handler; import android.os.HandlerThread; @@ -158,5 +159,6 @@ public abstract class IntentService extends Service { * @param intent The value passed to {@link * android.content.Context#startService(Intent)}. */ + @WorkerThread protected abstract void onHandleIntent(Intent intent); } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index ebb3c43..2c12317 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -199,9 +199,12 @@ public class KeyguardManager { } /** - * Return whether the keyguard requires a password to unlock. + * Return whether the keyguard is secured by a PIN, pattern or password or a SIM card + * is currently locked. * - * @return true if keyguard is secure. + * <p>See also {@link #isDeviceSecure()} which ignores SIM locked states. + * + * @return true if a PIN, pattern or password is set or a SIM card is locked. */ public boolean isKeyguardSecure() { try { @@ -240,12 +243,8 @@ public class KeyguardManager { } /** - * Returns whether the device is currently locked and requires a PIN, pattern or - * password to unlock. + * Per-user version of {@link #isDeviceLocked()}. * - * @param userId the user for which the locked state should be reported. - * @return true if unlocking the device currently requires a PIN, pattern or - * password. * @hide */ public boolean isDeviceLocked(int userId) { @@ -260,6 +259,8 @@ public class KeyguardManager { * Returns whether the device is secured with a PIN, pattern or * password. * + * <p>See also {@link #isKeyguardSecure} which treats SIM locked states as secure. + * * @return true if a PIN, pattern or password was set. */ public boolean isDeviceSecure() { @@ -267,11 +268,8 @@ public class KeyguardManager { } /** - * Returns whether the device is secured with a PIN, pattern or - * password. + * Per-user version of {@link #isDeviceSecure()}. * - * @param userId the user for which the secure state should be reported. - * @return true if a PIN, pattern or password was set. * @hide */ public boolean isDeviceSecure(int userId) { diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index b13b24a..f0e35c9 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -214,12 +214,12 @@ class LoaderManagerImpl extends LoaderManager { final String mWho; - Activity mActivity; boolean mStarted; boolean mRetaining; boolean mRetainingStarted; boolean mCreatingLoader; + private FragmentHostCallback mHost; final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>, Loader.OnLoadCanceledListener<Object> { @@ -356,15 +356,15 @@ class LoaderManagerImpl extends LoaderManager { if (mCallbacks != null && mLoader != null && mHaveData && needReset) { if (DEBUG) Log.v(TAG, " Reseting: " + this); String lastBecause = null; - if (mActivity != null) { - lastBecause = mActivity.mFragments.mNoTransactionsBecause; - mActivity.mFragments.mNoTransactionsBecause = "onLoaderReset"; + if (mHost != null) { + lastBecause = mHost.mFragmentManager.mNoTransactionsBecause; + mHost.mFragmentManager.mNoTransactionsBecause = "onLoaderReset"; } try { mCallbacks.onLoaderReset(mLoader); } finally { - if (mActivity != null) { - mActivity.mFragments.mNoTransactionsBecause = lastBecause; + if (mHost != null) { + mHost.mFragmentManager.mNoTransactionsBecause = lastBecause; } } } @@ -465,25 +465,25 @@ class LoaderManagerImpl extends LoaderManager { mInactiveLoaders.remove(mId); } - if (mActivity != null && !hasRunningLoaders()) { - mActivity.mFragments.startPendingDeferredFragments(); + if (mHost != null && !hasRunningLoaders()) { + mHost.mFragmentManager.startPendingDeferredFragments(); } } void callOnLoadFinished(Loader<Object> loader, Object data) { if (mCallbacks != null) { String lastBecause = null; - if (mActivity != null) { - lastBecause = mActivity.mFragments.mNoTransactionsBecause; - mActivity.mFragments.mNoTransactionsBecause = "onLoadFinished"; + if (mHost != null) { + lastBecause = mHost.mFragmentManager.mNoTransactionsBecause; + mHost.mFragmentManager.mNoTransactionsBecause = "onLoadFinished"; } try { if (DEBUG) Log.v(TAG, " onLoadFinished in " + loader + ": " + loader.dataToString(data)); mCallbacks.onLoadFinished(loader, data); } finally { - if (mActivity != null) { - mActivity.mFragments.mNoTransactionsBecause = lastBecause; + if (mHost != null) { + mHost.mFragmentManager.mNoTransactionsBecause = lastBecause; } } mDeliveredData = true; @@ -530,14 +530,14 @@ class LoaderManagerImpl extends LoaderManager { } } - LoaderManagerImpl(String who, Activity activity, boolean started) { + LoaderManagerImpl(String who, FragmentHostCallback host, boolean started) { mWho = who; - mActivity = activity; + mHost = host; mStarted = started; } - void updateActivity(Activity activity) { - mActivity = activity; + void updateHostController(FragmentHostCallback host) { + mHost = host; } private LoaderInfo createLoader(int id, Bundle args, @@ -730,8 +730,8 @@ class LoaderManagerImpl extends LoaderManager { mInactiveLoaders.removeAt(idx); info.destroy(); } - if (mActivity != null && !hasRunningLoaders()) { - mActivity.mFragments.startPendingDeferredFragments(); + if (mHost != null && !hasRunningLoaders()) { + mHost.mFragmentManager.startPendingDeferredFragments(); } } @@ -849,7 +849,7 @@ class LoaderManagerImpl extends LoaderManager { sb.append("LoaderManager{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" in "); - DebugUtils.buildShortClassTag(mActivity, sb); + DebugUtils.buildShortClassTag(mHost, sb); sb.append("}}"); return sb.toString(); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2cf23af..49b2549 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2095,6 +2095,7 @@ public class Notification implements Parcelable try { Constructor<? extends Style> constructor = styleClass.getConstructor(); + constructor.setAccessible(true); style = constructor.newInstance(); style.restoreFromExtras(extras); } catch (Throwable t) { @@ -5493,477 +5494,6 @@ public class Notification implements Parcelable } /** - * <p> - * Helper class to add content info extensions to notifications. To create a notification with - * content info extensions: - * <ol> - * <li>Create an {@link Notification.Builder}, setting any desired properties. - * <li>Create a {@link ContentInfoExtender}. - * <li>Set content info specific properties using the {@code add} and {@code set} methods of - * {@link ContentInfoExtender}. - * <li>Call {@link Notification.Builder#extend(Notification.Extender)} to apply the extensions - * to a notification. - * </ol> - * - * <pre class="prettyprint">Notification notification = new Notification.Builder(context) * ... * .extend(new ContentInfoExtender() * .set*(...)) * .build(); * </pre> - * <p> - * Content info extensions can be accessed on an existing notification by using the - * {@code ContentInfoExtender(Notification)} constructor, and then using the {@code get} methods - * to access values. - */ - public static final class ContentInfoExtender implements Extender { - private static final String TAG = "ContentInfoExtender"; - - // Key for the Content info extensions bundle in the main Notification extras bundle - private static final String EXTRA_CONTENT_INFO_EXTENDER = "android.CONTENT_INFO_EXTENSIONS"; - - // Keys within EXTRA_CONTENT_INFO_EXTENDER for individual content info options. - - private static final String KEY_CONTENT_TYPE = "android.contentType"; - - private static final String KEY_CONTENT_GENRES = "android.contentGenre"; - - private static final String KEY_CONTENT_PRICING_TYPE = "android.contentPricing.type"; - - private static final String KEY_CONTENT_PRICING_VALUE = "android.contentPricing.value"; - - private static final String KEY_CONTENT_STATUS = "android.contentStatus"; - - private static final String KEY_CONTENT_MATURITY_RATING = "android.contentMaturity"; - - private static final String KEY_CONTENT_RUN_LENGTH = "android.contentLength"; - - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a video clip. - */ - public static final String CONTENT_TYPE_VIDEO = "android.contentType.video"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a movie. - */ - public static final String CONTENT_TYPE_MOVIE = "android.contentType.movie"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a trailer. - */ - public static final String CONTENT_TYPE_TRAILER = "android.contentType.trailer"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is serial. It can refer to an entire show, a single season or - * series, or a single episode. - */ - public static final String CONTENT_TYPE_SERIAL = "android.contentType.serial"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a song or album. - */ - public static final String CONTENT_TYPE_MUSIC = "android.contentType.music"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a radio station. - */ - public static final String CONTENT_TYPE_RADIO = "android.contentType.radio"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a podcast. - */ - public static final String CONTENT_TYPE_PODCAST = "android.contentType.podcast"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a news item. - */ - public static final String CONTENT_TYPE_NEWS = "android.contentType.news"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is sports. - */ - public static final String CONTENT_TYPE_SPORTS = "android.contentType.sports"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is an application. - */ - public static final String CONTENT_TYPE_APP = "android.contentType.app"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a game. - */ - public static final String CONTENT_TYPE_GAME = "android.contentType.game"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a book. - */ - public static final String CONTENT_TYPE_BOOK = "android.contentType.book"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a comic book. - */ - public static final String CONTENT_TYPE_COMIC = "android.contentType.comic"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a magazine. - */ - public static final String CONTENT_TYPE_MAGAZINE = "android.contentType.magazine"; - - /** - * Value to be used with {@link #setContentTypes} to indicate that the content referred by - * the notification item is a website. - */ - public static final String CONTENT_TYPE_WEBSITE = "android.contentType.website"; - - - /** - * Value to be used with {@link #setPricingInformation} to indicate that the content - * referred by the notification item is free to consume. - */ - public static final String CONTENT_PRICING_FREE = "android.contentPrice.free"; - - /** - * Value to be used with {@link #setPricingInformation} to indicate that the content - * referred by the notification item is available as a rental, and the price value provided - * is the rental price for the item. - */ - public static final String CONTENT_PRICING_RENTAL = "android.contentPrice.rental"; - - /** - * Value to be used with {@link #setPricingInformation} to indicate that the content - * referred by the notification item is available for purchase, and the price value provided - * is the purchase price for the item. - */ - public static final String CONTENT_PRICING_PURCHASE = "android.contentPrice.purchase"; - - /** - * Value to be used with {@link #setPricingInformation} to indicate that the content - * referred by the notification item is available currently as a pre-order, and the price - * value provided is the purchase price for the item. - */ - public static final String CONTENT_PRICING_PREORDER = "android.contentPrice.preorder"; - - /** - * Value to be used with {@link #setPricingInformation} to indicate that the content - * referred by the notification item is available as part of a subscription based service, - * and the price value provided is the subscription price for the service. - */ - public static final String CONTENT_PRICING_SUBSCRIPTION = - "android.contentPrice.subscription"; - - /** - * Value to be used with {@link #setStatus} to indicate that the content referred by the - * notification is available and ready to be consumed immediately. - */ - public static final int CONTENT_STATUS_READY = 0; - - /** - * Value to be used with {@link #setStatus} to indicate that the content referred by the - * notification is pending, waiting on either a download or purchase operation to complete - * before it can be consumed. - */ - public static final int CONTENT_STATUS_PENDING = 1; - - /** - * Value to be used with {@link #setStatus} to indicate that the content referred by the - * notification is available, but needs to be first purchased, rented, subscribed or - * downloaded before it can be consumed. - */ - public static final int CONTENT_STATUS_AVAILABLE = 2; - - /** - * Value to be used with {@link #setStatus} to indicate that the content referred by the - * notification is not available. This could be content not available in a certain region or - * incompatible with the device in use. - */ - public static final int CONTENT_STATUS_UNAVAILABLE = 3; - - /** - * Value to be used with {@link #setMaturityRating} to indicate that the content referred by - * the notification is suitable for all audiences. - */ - public static final String CONTENT_MATURITY_ALL = "android.contentMaturity.all"; - - /** - * Value to be used with {@link #setMaturityRating} to indicate that the content - * referred by the notification is suitable for audiences of low maturity and above. - */ - public static final String CONTENT_MATURITY_LOW = "android.contentMaturity.low"; - - /** - * Value to be used with {@link #setMaturityRating} to indicate that the content - * referred by the notification is suitable for audiences of medium maturity and above. - */ - public static final String CONTENT_MATURITY_MEDIUM = "android.contentMaturity.medium"; - - /** - * Value to be used with {@link #setMaturityRating} to indicate that the content - * referred by the notification is suitable for audiences of high maturity and above. - */ - public static final String CONTENT_MATURITY_HIGH = "android.contentMaturity.high"; - - private String[] mTypes; - private String[] mGenres; - private String mPricingType; - private String mPricingValue; - private int mContentStatus = -1; - private String mMaturityRating; - private long mRunLength = -1; - - /** - * Create a {@link ContentInfoExtender} with default options. - */ - public ContentInfoExtender() { - } - - /** - * Create a {@link ContentInfoExtender} from the ContentInfoExtender options of an existing - * Notification. - * - * @param notif The notification from which to copy options. - */ - public ContentInfoExtender(Notification notif) { - Bundle contentBundle = notif.extras == null ? - null : notif.extras.getBundle(EXTRA_CONTENT_INFO_EXTENDER); - if (contentBundle != null) { - mTypes = contentBundle.getStringArray(KEY_CONTENT_TYPE); - mGenres = contentBundle.getStringArray(KEY_CONTENT_GENRES); - mPricingType = contentBundle.getString(KEY_CONTENT_PRICING_TYPE); - mPricingValue = contentBundle.getString(KEY_CONTENT_PRICING_VALUE); - mContentStatus = contentBundle.getInt(KEY_CONTENT_STATUS, -1); - mMaturityRating = contentBundle.getString(KEY_CONTENT_MATURITY_RATING); - mRunLength = contentBundle.getLong(KEY_CONTENT_RUN_LENGTH, -1); - } - } - - /** - * Apply content extensions to a notification that is being built. This is typically called - * by the {@link Notification.Builder#extend(Notification.Extender)} method of - * {@link Notification.Builder}. - */ - @Override - public Notification.Builder extend(Notification.Builder builder) { - Bundle contentBundle = new Bundle(); - - if (mTypes != null) { - contentBundle.putStringArray(KEY_CONTENT_TYPE, mTypes); - } - if (mGenres != null) { - contentBundle.putStringArray(KEY_CONTENT_GENRES, mGenres); - } - if (mPricingType != null) { - contentBundle.putString(KEY_CONTENT_PRICING_TYPE, mPricingType); - } - if (mPricingValue != null) { - contentBundle.putString(KEY_CONTENT_PRICING_VALUE, mPricingValue); - } - if (mContentStatus != -1) { - contentBundle.putInt(KEY_CONTENT_STATUS, mContentStatus); - } - if (mMaturityRating != null) { - contentBundle.putString(KEY_CONTENT_MATURITY_RATING, mMaturityRating); - } - if (mRunLength > 0) { - contentBundle.putLong(KEY_CONTENT_RUN_LENGTH, mRunLength); - } - - builder.getExtras().putBundle(EXTRA_CONTENT_INFO_EXTENDER, contentBundle); - return builder; - } - - /** - * Sets the content types associated with the notification content. The first tag entry will - * be considered the primary type for the content and will be used for ranking purposes. - * Other secondary type tags may be provided, if applicable, and may be used for filtering - * purposes. - * - * @param types Array of predefined type tags (see the <code>CONTENT_TYPE_*</code> - * constants) that describe the content referred to by a notification. - */ - public ContentInfoExtender setContentTypes(String[] types) { - mTypes = types; - return this; - } - - /** - * Returns an array containing the content types that describe the content associated with - * the notification. The first tag entry is considered the primary type for the content, and - * is used for content ranking purposes. - * - * @return An array of predefined type tags (see the <code>CONTENT_TYPE_*</code> constants) - * that describe the content associated with the notification. - * @see ContentInfoExtender#setContentTypes - */ - public String[] getContentTypes() { - return mTypes; - } - - /** - * Returns the primary content type tag for the content associated with the notification. - * - * @return A predefined type tag (see the <code>CONTENT_TYPE_*</code> constants) indicating - * the primary type for the content associated with the notification. - * @see ContentInfoExtender#setContentTypes - */ - public String getPrimaryContentType() { - if (mTypes == null || mTypes.length == 0) { - return null; - } - return mTypes[0]; - } - - /** - * Sets the content genres associated with the notification content. These genres may be - * used for content ranking. Genres are open ended String tags. - * <p> - * Some examples: "comedy", "action", "dance", "electronica", "racing", etc. - * - * @param genres Array of genre string tags that describe the content referred to by a - * notification. - */ - public ContentInfoExtender setGenres(String[] genres) { - mGenres = genres; - return this; - } - - /** - * Returns an array containing the content genres that describe the content associated with - * the notification. - * - * @return An array of genre tags that describe the content associated with the - * notification. - * @see ContentInfoExtender#setGenres - */ - public String[] getGenres() { - return mGenres; - } - - /** - * Sets the pricing and availability information for the content associated with the - * notification. The provided information will indicate the access model for the content - * (free, rental, purchase or subscription) and the price value (if not free). - * - * @param priceType Pricing type for this content. Must be one of the predefined pricing - * type tags (see the <code>CONTENT_PRICING_*</code> constants). - * @param priceValue A string containing a representation of the content price in the - * current locale and currency. - * @return This object for method chaining. - */ - public ContentInfoExtender setPricingInformation(String priceType, String priceValue) { - mPricingType = priceType; - mPricingValue = priceValue; - return this; - } - - /** - * Gets the pricing type for the content associated with the notification. - * - * @return A predefined tag indicating the pricing type for the content (see the <code> - * CONTENT_PRICING_*</code> constants). - * @see ContentInfoExtender#setPricingInformation - */ - public String getPricingType() { - return mPricingType; - } - - /** - * Gets the price value (when applicable) for the content associated with a notification. - * The value will be provided as a String containing the price in the appropriate currency - * for the current locale. - * - * @return A string containing a representation of the content price in the current locale - * and currency. - * @see ContentInfoExtender#setPricingInformation - */ - public String getPricingValue() { - if (mPricingType == null || CONTENT_PRICING_FREE.equals(mPricingType)) { - return null; - } - return mPricingValue; - } - - /** - * Sets the availability status for the content associated with the notification. This - * status indicates whether the referred content is ready to be consumed on the device, or - * if the user must first purchase, rent, subscribe to, or download the content. - * - * @param contentStatus The status value for this content. Must be one of the predefined - * content status values (see the <code>CONTENT_STATUS_*</code> constants). - */ - public ContentInfoExtender setStatus(int contentStatus) { - mContentStatus = contentStatus; - return this; - } - - /** - * Returns status value for the content associated with the notification. This status - * indicates whether the referred content is ready to be consumed on the device, or if the - * user must first purchase, rent, subscribe to, or download the content. - * - * @return The status value for this content, or -1 is a valid status has not been specified - * (see the <code>CONTENT_STATUS_*</code> for the defined valid status values). - * @see ContentInfoExtender#setStatus - */ - public int getStatus() { - return mContentStatus; - } - - /** - * Sets the maturity level rating for the content associated with the notification. - * - * @param maturityRating A tag indicating the maturity level rating for the content. This - * tag must be one of the predefined maturity rating tags (see the <code> - * CONTENT_MATURITY_*</code> constants). - */ - public ContentInfoExtender setMaturityRating(String maturityRating) { - mMaturityRating = maturityRating; - return this; - } - - /** - * Returns the maturity level rating for the content associated with the notification. - * - * @return returns a predefined tag indicating the maturity level rating for the content - * (see the <code> CONTENT_MATURITY_*</code> constants). - * @see ContentInfoExtender#setMaturityRating - */ - public String getMaturityRating() { - return mMaturityRating; - } - - /** - * Sets the running time (when applicable) for the content associated with the notification. - * - * @param length The runing time, in seconds, of the content associated with the - * notification. - */ - public ContentInfoExtender setRunningTime(long length) { - mRunLength = length; - return this; - } - - /** - * Returns the running time for the content associated with the notification. - * - * @return The running time, in seconds, of the content associated with the notification. - * @see ContentInfoExtender#setRunningTime - */ - public long getRunningTime() { - return mRunLength; - } - } - - /** * Get an array of Notification objects from a parcelable array bundle field. * Update the bundle to have a typed array so fetches in the future don't need * to do an array copy. diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index b1a5d21..fa11221 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -165,8 +165,6 @@ public class SearchDialog extends Dialog { setContentView(com.android.internal.R.layout.search_bar); // get the view elements for local access - SearchBar searchBar = (SearchBar) findViewById(com.android.internal.R.id.search_bar); - searchBar.setSearchDialog(this); mSearchView = (SearchView) findViewById(com.android.internal.R.id.search_view); mSearchView.setIconified(false); mSearchView.setOnCloseListener(mOnCloseListener); @@ -618,8 +616,6 @@ public class SearchDialog extends Dialog { */ public static class SearchBar extends LinearLayout { - private SearchDialog mSearchDialog; - public SearchBar(Context context, AttributeSet attrs) { super(context, attrs); } @@ -628,18 +624,6 @@ public class SearchDialog extends Dialog { super(context); } - public void setSearchDialog(SearchDialog searchDialog) { - mSearchDialog = searchDialog; - } - - /** - * Don't allow action modes in a SearchBar, it looks silly. - */ - @Override - public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { - return null; - } - @Override public ActionMode startActionModeForChild( View child, ActionMode.Callback callback, int type) { diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 207519c..31d1ab7 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -63,12 +63,20 @@ public class StatusBarManager { | DISABLE_SYSTEM_INFO | DISABLE_RECENT | DISABLE_HOME | DISABLE_BACK | DISABLE_CLOCK | DISABLE_SEARCH; + /** + * Flag to disable quick settings. + * + * Setting this flag disables quick settings completely, but does not disable expanding the + * notification shade. + */ + public static final int DISABLE2_QUICK_SETTINGS = 0x00000001; + public static final int DISABLE2_NONE = 0x00000000; - public static final int DISABLE2_MASK = 0x00000000; + public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS; @IntDef(flag = true, - value = {DISABLE2_NONE, DISABLE2_MASK}) + value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS}) @Retention(RetentionPolicy.SOURCE) public @interface Disable2Flags {} diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index aea413d..470804d 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -231,7 +231,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * device owner app. * * <p>The broadcast will be limited to the {@link DeviceAdminReceiver} component specified in - * the (@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME) field + * the {@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME} field * of the original intent or NFC bump that started the provisioning process. You will generally * handle this in {@link DeviceAdminReceiver#onReadyForUserInitialization}. * @@ -450,9 +450,9 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * * <p>It is not assumed that the device initializer is finished when it returns from * this call, as it may do additional setup asynchronously. The device initializer must call - * {DevicePolicyManager#setUserEnabled(ComponentName admin)} when it has finished any additional - * setup (such as adding an account by using the {@link AccountManager}) in order for the user - * to be functional. + * {@link DevicePolicyManager#setUserEnabled(ComponentName admin)} when it has finished any + * additional setup (such as adding an account by using the {@link AccountManager}) in order for + * the user to be functional. * * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ed814c3..8009b6c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -412,7 +412,7 @@ public class DevicePolicyManager { = "android.app.action.MANAGED_PROFILE_PROVISIONED"; /** - * A boolean extra indicating whether device encryption is required as part of Device Owner + * A boolean extra indicating whether device encryption can be skipped as part of Device Owner * provisioning. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner @@ -807,6 +807,24 @@ public class DevicePolicyManager { public static final String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED"; + /** + * Permission policy to prompt user for new permission requests for runtime permissions. + * Already granted or denied permissions are not affected by this. + */ + public static final int PERMISSION_POLICY_PROMPT = 0; + + /** + * Permission policy to always grant new permission requests for runtime permissions. + * Already granted or denied permissions are not affected by this. + */ + public static final int PERMISSION_POLICY_AUTO_GRANT = 1; + + /** + * Permission policy to always deny new permission requests for runtime permissions. + * Already granted or denied permissions are not affected by this. + */ + public static final int PERMISSION_POLICY_AUTO_DENY = 2; + /** * Return true if the given administrator component is currently @@ -2908,7 +2926,7 @@ public class DevicePolicyManager { * the user has already been set up. */ @SystemApi - public boolean setActiveProfileOwner(ComponentName admin, String ownerName) + public boolean setActiveProfileOwner(ComponentName admin, @Deprecated String ownerName) throws IllegalArgumentException { if (mService != null) { try { @@ -2974,8 +2992,8 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if admin is null, the package isn't installed, or the * preconditions mentioned are not met. */ - public boolean setProfileOwner(ComponentName admin, String ownerName, int userHandle) - throws IllegalArgumentException { + public boolean setProfileOwner(ComponentName admin, @Deprecated String ownerName, + int userHandle) throws IllegalArgumentException { if (admin == null) { throw new NullPointerException("admin cannot be null"); } @@ -4276,14 +4294,14 @@ public class DevicePolicyManager { * being disabled. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param enabled New state of the keyguard. + * @param disabled {@code true} disables the keyguard, {@code false} reenables it. * * @return {@code false} if attempting to disable the keyguard while a lock password was in - * place. {@code true} otherwise." + * place. {@code true} otherwise. */ - public boolean setKeyguardEnabledState(ComponentName admin, boolean enabled) { + public boolean setKeyguardDisabled(ComponentName admin, boolean disabled) { try { - return mService.setKeyguardEnabledState(admin, enabled); + return mService.setKeyguardDisabled(admin, disabled); } catch (RemoteException re) { Log.w(TAG, "Failed talking with device policy service", re); return false; @@ -4291,18 +4309,22 @@ public class DevicePolicyManager { } /** - * Called by device owner to set the enabled state of the status bar. Disabling the status - * bar blocks notifications, quick settings and other screen overlays that allow escaping from + * Called by device owner to disable the status bar. Disabling the status bar blocks + * notifications, quick settings and other screen overlays that allow escaping from * a single use device. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param enabled New state of the status bar. + * @param disabled {@code true} disables the status bar, {@code false} reenables it. + * + * @return {@code false} if attempting to disable the status bar failed. + * {@code true} otherwise. */ - public void setStatusBarEnabledState(ComponentName admin, boolean enabled) { + public boolean setStatusBarDisabled(ComponentName admin, boolean disabled) { try { - mService.setStatusBarEnabledState(admin, enabled); + return mService.setStatusBarDisabled(admin, disabled); } catch (RemoteException re) { Log.w(TAG, "Failed talking with device policy service", re); + return false; } } @@ -4342,4 +4364,58 @@ public class DevicePolicyManager { Log.w(TAG, "Failed talking with device policy service", re); } } + + /** + * Called by profile or device owners to set the default response for future runtime permission + * requests by applications. The policy can allow for normal operation which prompts the + * user to grant a permission, or can allow automatic granting or denying of runtime + * permission requests by an application. This also applies to new permissions declared by app + * updates. + * @param admin Which profile or device owner this request is associated with. + * @param policy One of the policy constants {@link #PERMISSION_POLICY_PROMPT}, + * {@link #PERMISSION_POLICY_AUTO_GRANT} and {@link #PERMISSION_POLICY_AUTO_DENY}. + */ + public void setPermissionPolicy(ComponentName admin, int policy) { + try { + mService.setPermissionPolicy(admin, policy); + } catch (RemoteException re) { + Log.w(TAG, "Failed talking with device policy service", re); + } + } + + /** + * Returns the current runtime permission policy set by the device or profile owner. The + * default is {@link #PERMISSION_POLICY_PROMPT}. + * @param admin Which profile or device owner this request is associated with. + * @return the current policy for future permission requests. + */ + public int getPermissionPolicy(ComponentName admin) { + try { + return mService.getPermissionPolicy(admin); + } catch (RemoteException re) { + return PERMISSION_POLICY_PROMPT; + } + } + + /** + * Grants or revokes a runtime permission to a specific application so that the user + * does not have to be prompted. This might affect all permissions in a group that the + * runtime permission belongs to. This method can only be called by a profile or device + * owner. + * @param admin Which profile or device owner this request is associated with. + * @param packageName The application to grant or revoke a permission to. + * @param permission The permission to grant or revoke. + * @param granted Whether or not to grant the permission. If false, all permissions in the + * associated permission group will be denied. + * @return whether the permission was successfully granted or revoked + */ + public boolean setPermissionGranted(ComponentName admin, String packageName, + String permission, boolean granted) { + try { + return mService.setPermissionGranted(admin, packageName, permission, granted); + } catch (RemoteException re) { + Log.w(TAG, "Failed talking with device policy service", re); + return false; + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index a678c51..e81e7c1 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -224,9 +224,14 @@ interface IDevicePolicyManager { void setSystemUpdatePolicy(in ComponentName who, in PersistableBundle policy); PersistableBundle getSystemUpdatePolicy(); - boolean setKeyguardEnabledState(in ComponentName admin, boolean enabled); - void setStatusBarEnabledState(in ComponentName who, boolean enabled); + boolean setKeyguardDisabled(in ComponentName admin, boolean disabled); + boolean setStatusBarDisabled(in ComponentName who, boolean disabled); boolean getDoNotAskCredentialsOnBoot(); void notifyPendingSystemUpdate(in long updateReceivedTime); + + void setPermissionPolicy(in ComponentName admin, int policy); + int getPermissionPolicy(in ComponentName admin); + boolean setPermissionGranted(in ComponentName admin, String packageName, String permission, + boolean granted); } diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index d8556a2..6fca0de 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -33,15 +33,21 @@ import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.system.StructStat; +import android.util.ArraySet; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.HashSet; +import java.util.Collection; import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CountDownLatch; +import org.xmlpull.v1.XmlPullParserException; + /** * Provides the central interface between an * application and Android's data backup infrastructure. An application that wishes @@ -164,7 +170,6 @@ public abstract class BackupAgent extends ContextWrapper { * to do one-time initialization before the actual backup or restore operation * is begun. * <p> - * Agents do not need to override this method. */ public void onCreate() { } @@ -268,19 +273,41 @@ public abstract class BackupAgent extends ContextWrapper { * listed above. Apps only need to override this method if they need to impose special * limitations on which files are being stored beyond the control that * {@link #getNoBackupFilesDir()} offers. + * Alternatively they can provide an xml resource to specify what data to include or exclude. + * * * @param data A structured wrapper pointing to the backup destination. * @throws IOException * * @see Context#getNoBackupFilesDir() + * @see ApplicationInfo#fullBackupContent * @see #fullBackupFile(File, FullBackupDataOutput) * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) */ public void onFullBackup(FullBackupDataOutput data) throws IOException { - ApplicationInfo appInfo = getApplicationInfo(); + FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this); + if (!backupScheme.isFullBackupContentEnabled()) { + return; + } + + Map<String, Set<String>> manifestIncludeMap; + ArraySet<String> manifestExcludeSet; + try { + manifestIncludeMap = + backupScheme.maybeParseAndGetCanonicalIncludePaths(); + manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths(); + } catch (IOException | XmlPullParserException e) { + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, + "Exception trying to parse fullBackupContent xml file!" + + " Aborting full backup.", e); + } + return; + } + + final String packageName = getPackageName(); + final ApplicationInfo appInfo = getApplicationInfo(); - // Note that we don't need to think about the no_backup dir because it's outside - // all of the ones we will be traversing String rootDir = new File(appInfo.dataDir).getCanonicalPath(); String filesDir = getFilesDir().getCanonicalPath(); String nobackupDir = getNoBackupFilesDir().getCanonicalPath(); @@ -292,34 +319,49 @@ public abstract class BackupAgent extends ContextWrapper { ? new File(appInfo.nativeLibraryDir).getCanonicalPath() : null; - // Filters, the scan queue, and the set of resulting entities - HashSet<String> filterSet = new HashSet<String>(); - String packageName = getPackageName(); + // Maintain a set of excluded directories so that as we traverse the tree we know we're not + // going places we don't expect, and so the manifest includes can't take precedence over + // what the framework decides is not to be included. + final ArraySet<String> traversalExcludeSet = new ArraySet<String>(); - // Okay, start with the app's root tree, but exclude all of the canonical subdirs + // Add the directories we always exclude. + traversalExcludeSet.add(cacheDir); + traversalExcludeSet.add(codeCacheDir); + traversalExcludeSet.add(nobackupDir); if (libDir != null) { - filterSet.add(libDir); + traversalExcludeSet.add(libDir); } - filterSet.add(cacheDir); - filterSet.add(codeCacheDir); - filterSet.add(databaseDir); - filterSet.add(sharedPrefsDir); - filterSet.add(filesDir); - filterSet.add(nobackupDir); - fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data); - - // Now do the same for the files dir, db dir, and shared prefs dir - filterSet.add(rootDir); - filterSet.remove(filesDir); - fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet, data); - - filterSet.add(filesDir); - filterSet.remove(databaseDir); - fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data); - - filterSet.add(databaseDir); - filterSet.remove(sharedPrefsDir); - fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data); + + traversalExcludeSet.add(databaseDir); + traversalExcludeSet.add(sharedPrefsDir); + traversalExcludeSet.add(filesDir); + + // Root dir first. + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + traversalExcludeSet.add(rootDir); + + // Data dir next. + traversalExcludeSet.remove(filesDir); + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.DATA_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + traversalExcludeSet.add(filesDir); + + // Database directory. + traversalExcludeSet.remove(databaseDir); + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + traversalExcludeSet.add(databaseDir); + + // SharedPrefs. + traversalExcludeSet.remove(sharedPrefsDir); + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + traversalExcludeSet.add(sharedPrefsDir); // getExternalFilesDir() location associated with this app. Technically there should // not be any files here if the app does not properly have permission to access @@ -331,8 +373,36 @@ public abstract class BackupAgent extends ContextWrapper { if (Process.myUid() != Process.SYSTEM_UID) { File efLocation = getExternalFilesDir(null); if (efLocation != null) { - fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, - efLocation.getCanonicalPath(), null, data); + applyXmlFiltersAndDoFullBackupForDomain( + packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap, + manifestExcludeSet, traversalExcludeSet, data); + } + + } + } + + /** + * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>. + * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path + * is a directory. + */ + private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken, + Map<String, Set<String>> includeMap, + ArraySet<String> filterSet, + ArraySet<String> traversalExcludeSet, + FullBackupDataOutput data) + throws IOException { + if (includeMap == null || includeMap.size() == 0) { + // Do entire sub-tree for the provided token. + fullBackupFileTree(packageName, domainToken, + FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken), + filterSet, traversalExcludeSet, data); + } else if (includeMap.get(domainToken) != null) { + // This will be null if the xml parsing didn't yield any rules for + // this domain (there may still be rules for other domains). + for (String includeFile : includeMap.get(domainToken)) { + fullBackupFileTree(packageName, domainToken, includeFile, filterSet, + traversalExcludeSet, data); } } } @@ -430,21 +500,31 @@ public abstract class BackupAgent extends ContextWrapper { // without transmitting any file data. if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain + " rootpath=" + rootpath); - + FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output); } /** * Scan the dir tree (if it actually exists) and process each entry we find. If the - * 'excludes' parameter is non-null, it is consulted each time a new file system entity + * 'excludes' parameters are non-null, they are consulted each time a new file system entity * is visited to see whether that entity (and its subtree, if appropriate) should be * omitted from the backup process. * + * @param systemExcludes An optional list of excludes. * @hide */ - protected final void fullBackupFileTree(String packageName, String domain, String rootPath, - HashSet<String> excludes, FullBackupDataOutput output) { - File rootFile = new File(rootPath); + protected final void fullBackupFileTree(String packageName, String domain, String startingPath, + ArraySet<String> manifestExcludes, + ArraySet<String> systemExcludes, + FullBackupDataOutput output) { + // Pull out the domain and set it aside to use when making the tarball. + String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain); + if (domainPath == null) { + // Should never happen. + return; + } + + File rootFile = new File(startingPath); if (rootFile.exists()) { LinkedList<File> scanQueue = new LinkedList<File>(); scanQueue.add(rootFile); @@ -456,7 +536,10 @@ public abstract class BackupAgent extends ContextWrapper { filePath = file.getCanonicalPath(); // prune this subtree? - if (excludes != null && excludes.contains(filePath)) { + if (manifestExcludes != null && manifestExcludes.contains(filePath)) { + continue; + } + if (systemExcludes != null && systemExcludes.contains(filePath)) { continue; } @@ -475,14 +558,20 @@ public abstract class BackupAgent extends ContextWrapper { } } catch (IOException e) { if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file); + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file); + } continue; } catch (ErrnoException e) { if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e); + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e); + } continue; } // Finally, back this file up (or measure it) before proceeding - FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, output); + FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output); } } } @@ -516,10 +605,91 @@ public abstract class BackupAgent extends ContextWrapper { public void onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime) throws IOException { + FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this); + if (!bs.isFullBackupContentEnabled()) { + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, + "onRestoreFile \"" + destination.getCanonicalPath() + + "\" : fullBackupContent not enabled for " + getPackageName()); + } + return; + } + Map<String, Set<String>> includes = null; + ArraySet<String> excludes = null; + final String destinationCanonicalPath = destination.getCanonicalPath(); + try { + includes = bs.maybeParseAndGetCanonicalIncludePaths(); + excludes = bs.maybeParseAndGetCanonicalExcludePaths(); + } catch (XmlPullParserException e) { + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, + "onRestoreFile \"" + destinationCanonicalPath + + "\" : Exception trying to parse fullBackupContent xml file!" + + " Aborting onRestoreFile.", e); + } + return; + } + + if (excludes != null && + isFileSpecifiedInPathList(destination, excludes)) { + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, + "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in" + + " excludes; skipping."); + } + return; + } + + if (includes != null && !includes.isEmpty()) { + // Rather than figure out the <include/> domain based on the path (a lot of code, and + // it's a small list), we'll go through and look for it. + boolean explicitlyIncluded = false; + for (Set<String> domainIncludes : includes.values()) { + explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes); + if (explicitlyIncluded) { + break; + } + } + if (!explicitlyIncluded) { + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, + "onRestoreFile: Trying to restore \"" + + destinationCanonicalPath + "\" but it isn't specified" + + " in the included files; skipping."); + } + return; + } + } FullBackup.restoreFile(data, size, type, mode, mtime, destination); } /** + * @return True if the provided file is either directly in the provided list, or the provided + * file is within a directory in the list. + */ + private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList) + throws IOException { + for (String canonicalPath : canonicalPathList) { + File fileFromList = new File(canonicalPath); + if (fileFromList.isDirectory()) { + if (file.isDirectory()) { + // If they are both directories check exact equals. + return file.equals(fileFromList); + } else { + // O/w we have to check if the file is within the directory from the list. + return file.getCanonicalPath().startsWith(canonicalPath); + } + } else { + if (file.equals(fileFromList)) { + // Need to check the explicit "equals" so we don't end up with substrings. + return true; + } + } + } + return false; + } + + /** * Only specialized platform agents should overload this entry point to support * restores to crazy non-app locations. * @hide @@ -533,31 +703,9 @@ public abstract class BackupAgent extends ContextWrapper { + " domain=" + domain + " relpath=" + path + " mode=" + mode + " mtime=" + mtime); - // Parse out the semantic domains into the correct physical location - if (domain.equals(FullBackup.DATA_TREE_TOKEN)) { - basePath = getFilesDir().getCanonicalPath(); - } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) { - basePath = getDatabasePath("foo").getParentFile().getCanonicalPath(); - } else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) { - basePath = new File(getApplicationInfo().dataDir).getCanonicalPath(); - } else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) { - basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath(); - } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) { - basePath = getCacheDir().getCanonicalPath(); - } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) { - // make sure we can try to restore here before proceeding - if (Process.myUid() != Process.SYSTEM_UID) { - File efLocation = getExternalFilesDir(null); - if (efLocation != null) { - basePath = getExternalFilesDir(null).getCanonicalPath(); - mode = -1; // < 0 is a token to skip attempting a chmod() - } - } - } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) { - basePath = getNoBackupFilesDir().getCanonicalPath(); - } else { - // Not a supported location - Log.i(TAG, "Unrecognized domain " + domain); + basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain); + if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) { + mode = -1; // < 0 is a token to skip attempting a chmod() } // Now that we've figured out where the data goes, send it on its way diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 259884e..7718a36 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -16,16 +16,31 @@ package android.app.backup; -import android.os.ParcelFileDescriptor; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.XmlResourceParser; +import android.os.*; +import android.os.Process; import android.system.ErrnoException; import android.system.Os; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + +import org.xmlpull.v1.XmlPullParser; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.xmlpull.v1.XmlPullParserException; /** * Global constant definitions et cetera related to the full-backup-to-fd * binary format. Nothing in this namespace is part of any API; it's all @@ -35,6 +50,8 @@ import java.io.IOException; */ public class FullBackup { static final String TAG = "FullBackup"; + /** Enable this log tag to get verbose information while parsing the client xml. */ + static final String TAG_XML_PARSER = "BackupXmlParserLogging"; public static final String APK_TREE_TOKEN = "a"; public static final String OBB_TREE_TOKEN = "obb"; @@ -60,6 +77,27 @@ public class FullBackup { static public native int backupToTar(String packageName, String domain, String linkdomain, String rootpath, String path, FullBackupDataOutput output); + private static final Map<String, BackupScheme> kPackageBackupSchemeMap = + new ArrayMap<String, BackupScheme>(); + + static synchronized BackupScheme getBackupScheme(Context context) { + BackupScheme backupSchemeForPackage = + kPackageBackupSchemeMap.get(context.getPackageName()); + if (backupSchemeForPackage == null) { + backupSchemeForPackage = new BackupScheme(context); + kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage); + } + return backupSchemeForPackage; + } + + public static BackupScheme getBackupSchemeForTest(Context context) { + BackupScheme testing = new BackupScheme(context); + testing.mExcludes = new ArraySet(); + testing.mIncludes = new ArrayMap(); + return testing; + } + + /** * Copy data from a socket to the given File location on permanent storage. The * modification time and access mode of the resulting file will be set if desired, @@ -106,6 +144,8 @@ public class FullBackup { if (!parent.exists()) { // in practice this will only be for the default semantic directories, // and using the default mode for those is appropriate. + // This can also happen for the case where a parent directory has been + // excluded, but a file within that directory has been included. parent.mkdirs(); } out = new FileOutputStream(outFile); @@ -154,4 +194,363 @@ public class FullBackup { outFile.setLastModified(mtime); } } + + @VisibleForTesting + public static class BackupScheme { + private final File FILES_DIR; + private final File DATABASE_DIR; + private final File ROOT_DIR; + private final File SHAREDPREF_DIR; + private final File EXTERNAL_DIR; + private final File CACHE_DIR; + private final File NOBACKUP_DIR; + + final int mFullBackupContent; + final PackageManager mPackageManager; + final String mPackageName; + + /** + * Parse out the semantic domains into the correct physical location. + */ + String tokenToDirectoryPath(String domainToken) { + try { + if (domainToken.equals(FullBackup.DATA_TREE_TOKEN)) { + return FILES_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.DATABASE_TREE_TOKEN)) { + return DATABASE_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.ROOT_TREE_TOKEN)) { + return ROOT_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) { + return SHAREDPREF_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.CACHE_TREE_TOKEN)) { + return CACHE_DIR.getCanonicalPath(); + } else if (domainToken.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) { + if (EXTERNAL_DIR != null) { + return EXTERNAL_DIR.getCanonicalPath(); + } else { + return null; + } + } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) { + return NOBACKUP_DIR.getCanonicalPath(); + } + // Not a supported location + Log.i(TAG, "Unrecognized domain " + domainToken); + return null; + } catch (IOException e) { + Log.i(TAG, "Error reading directory for domain: " + domainToken); + return null; + } + + } + /** + * A map of domain -> list of canonical file names in that domain that are to be included. + * We keep track of the domain so that we can go through the file system in order later on. + */ + Map<String, Set<String>> mIncludes; + /**e + * List that will be populated with the canonical names of each file or directory that is + * to be excluded. + */ + ArraySet<String> mExcludes; + + BackupScheme(Context context) { + mFullBackupContent = context.getApplicationInfo().fullBackupContent; + mPackageManager = context.getPackageManager(); + mPackageName = context.getPackageName(); + FILES_DIR = context.getFilesDir(); + DATABASE_DIR = context.getDatabasePath("foo").getParentFile(); + ROOT_DIR = new File(context.getApplicationInfo().dataDir); + SHAREDPREF_DIR = context.getSharedPrefsFile("foo").getParentFile(); + CACHE_DIR = context.getCacheDir(); + NOBACKUP_DIR = context.getNoBackupFilesDir(); + if (android.os.Process.myUid() != Process.SYSTEM_UID) { + EXTERNAL_DIR = context.getExternalFilesDir(null); + } else { + EXTERNAL_DIR = null; + } + } + + boolean isFullBackupContentEnabled() { + if (mFullBackupContent < 0) { + // android:fullBackupContent="false", bail. + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"false\""); + } + return false; + } + return true; + } + + /** + * @return A mapping of domain -> canonical paths within that domain. Each of these paths + * specifies a file that the client has explicitly included in their backup set. If this + * map is empty we will back up the entire data directory (including managed external + * storage). + */ + public synchronized Map<String, Set<String>> maybeParseAndGetCanonicalIncludePaths() + throws IOException, XmlPullParserException { + if (mIncludes == null) { + maybeParseBackupSchemeLocked(); + } + return mIncludes; + } + + /** + * @return A set of canonical paths that are to be excluded from the backup/restore set. + */ + public synchronized ArraySet<String> maybeParseAndGetCanonicalExcludePaths() + throws IOException, XmlPullParserException { + if (mExcludes == null) { + maybeParseBackupSchemeLocked(); + } + return mExcludes; + } + + private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException { + // This not being null is how we know that we've tried to parse the xml already. + mIncludes = new ArrayMap<String, Set<String>>(); + mExcludes = new ArraySet<String>(); + + if (mFullBackupContent == 0) { + // android:fullBackupContent="true" which means that we'll do everything. + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"true\""); + } + } else { + // android:fullBackupContent="@xml/some_resource". + if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(FullBackup.TAG_XML_PARSER, + "android:fullBackupContent - found xml resource"); + } + XmlResourceParser parser = null; + try { + parser = mPackageManager + .getResourcesForApplication(mPackageName) + .getXml(mFullBackupContent); + parseBackupSchemeFromXmlLocked(parser, mExcludes, mIncludes); + } catch (PackageManager.NameNotFoundException e) { + // Throw it as an IOException + throw new IOException(e); + } finally { + if (parser != null) { + parser.close(); + } + } + } + } + + @VisibleForTesting + public void parseBackupSchemeFromXmlLocked(XmlPullParser parser, + Set<String> excludes, + Map<String, Set<String>> includes) + throws IOException, XmlPullParserException { + int event = parser.getEventType(); // START_DOCUMENT + while (event != XmlPullParser.START_TAG) { + event = parser.next(); + } + + if (!"full-backup-content".equals(parser.getName())) { + throw new XmlPullParserException("Xml file didn't start with correct tag" + + " (<full-backup-content>). Found \"" + parser.getName() + "\""); + } + + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "\n"); + Log.v(TAG_XML_PARSER, "===================================================="); + Log.v(TAG_XML_PARSER, "Found valid fullBackupContent; parsing xml resource."); + Log.v(TAG_XML_PARSER, "===================================================="); + Log.v(TAG_XML_PARSER, ""); + } + + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + switch (event) { + case XmlPullParser.START_TAG: + validateInnerTagContents(parser); + final String domainFromXml = parser.getAttributeValue(null, "domain"); + final File domainDirectory = + getDirectoryForCriteriaDomain(domainFromXml); + if (domainDirectory == null) { + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "...parsing \"" + parser.getName() + "\": " + + "domain=\"" + domainFromXml + "\" invalid; skipping"); + } + break; + } + final File canonicalFile = + extractCanonicalFile(domainDirectory, + parser.getAttributeValue(null, "path")); + if (canonicalFile == null) { + break; + } + + Set<String> activeSet = parseCurrentTagForDomain( + parser, excludes, includes, domainFromXml); + activeSet.add(canonicalFile.getCanonicalPath()); + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "...parsed " + canonicalFile.getCanonicalPath() + + " for domain \"" + domainFromXml + "\""); + } + + // Special case journal files (not dirs) for sqlite database. frowny-face. + // Note that for a restore, the file is never a directory (b/c it doesn't + // exist). We have no way of knowing a priori whether or not to expect a + // dir, so we add the -journal anyway to be safe. + if ("database".equals(domainFromXml) && !canonicalFile.isDirectory()) { + final String canonicalJournalPath = + canonicalFile.getCanonicalPath() + "-journal"; + activeSet.add(canonicalJournalPath); + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "...automatically generated " + + canonicalJournalPath + ". Ignore if nonexistant."); + } + } + } + } + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "\n"); + Log.v(TAG_XML_PARSER, "Xml resource parsing complete."); + Log.v(TAG_XML_PARSER, "Final tally."); + Log.v(TAG_XML_PARSER, "Includes:"); + if (includes.isEmpty()) { + Log.v(TAG_XML_PARSER, " ...nothing specified (This means the entirety of app" + + " data minus excludes)"); + } else { + for (Map.Entry<String, Set<String>> entry : includes.entrySet()) { + Log.v(TAG_XML_PARSER, " domain=" + entry.getKey()); + for (String includeData : entry.getValue()) { + Log.v(TAG_XML_PARSER, " " + includeData); + } + } + } + + Log.v(TAG_XML_PARSER, "Excludes:"); + if (excludes.isEmpty()) { + Log.v(TAG_XML_PARSER, " ...nothing to exclude."); + } else { + for (String excludeData : excludes) { + Log.v(TAG_XML_PARSER, " " + excludeData); + } + } + + Log.v(TAG_XML_PARSER, " "); + Log.v(TAG_XML_PARSER, "===================================================="); + Log.v(TAG_XML_PARSER, "\n"); + } + } + + private Set<String> parseCurrentTagForDomain(XmlPullParser parser, + Set<String> excludes, + Map<String, Set<String>> includes, + String domain) + throws XmlPullParserException { + if ("include".equals(parser.getName())) { + final String domainToken = getTokenForXmlDomain(domain); + Set<String> includeSet = includes.get(domainToken); + if (includeSet == null) { + includeSet = new ArraySet<String>(); + includes.put(domainToken, includeSet); + } + return includeSet; + } else if ("exclude".equals(parser.getName())) { + return excludes; + } else { + // Unrecognised tag => hard failure. + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "Invalid tag found in xml \"" + + parser.getName() + "\"; aborting operation."); + } + throw new XmlPullParserException("Unrecognised tag in backup" + + " criteria xml (" + parser.getName() + ")"); + } + } + + /** + * Map xml specified domain (human-readable, what clients put in their manifest's xml) to + * BackupAgent internal data token. + * @return null if the xml domain was invalid. + */ + private String getTokenForXmlDomain(String xmlDomain) { + if ("root".equals(xmlDomain)) { + return FullBackup.ROOT_TREE_TOKEN; + } else if ("file".equals(xmlDomain)) { + return FullBackup.DATA_TREE_TOKEN; + } else if ("database".equals(xmlDomain)) { + return FullBackup.DATABASE_TREE_TOKEN; + } else if ("sharedpref".equals(xmlDomain)) { + return FullBackup.SHAREDPREFS_TREE_TOKEN; + } else if ("external".equals(xmlDomain)) { + return FullBackup.MANAGED_EXTERNAL_TREE_TOKEN; + } else { + return null; + } + } + + /** + * + * @param domain Directory where the specified file should exist. Not null. + * @param filePathFromXml parsed from xml. Not sanitised before calling this function so may be + * null. + * @return The canonical path of the file specified or null if no such file exists. + */ + private File extractCanonicalFile(File domain, String filePathFromXml) { + if (filePathFromXml == null) { + // Allow things like <include domain="sharedpref"/> + filePathFromXml = ""; + } + if (filePathFromXml.contains("..")) { + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "...resolved \"" + domain.getPath() + " " + filePathFromXml + + "\", but the \"..\" path is not permitted; skipping."); + } + return null; + } + if (filePathFromXml.contains("//")) { + if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { + Log.v(TAG_XML_PARSER, "...resolved \"" + domain.getPath() + " " + filePathFromXml + + "\", which contains the invalid \"//\" sequence; skipping."); + } + return null; + } + return new File(domain, filePathFromXml); + } + + /** + * @param domain parsed from xml. Not sanitised before calling this function so may be null. + * @return The directory relevant to the domain specified. + */ + private File getDirectoryForCriteriaDomain(String domain) { + if (TextUtils.isEmpty(domain)) { + return null; + } + if ("file".equals(domain)) { + return FILES_DIR; + } else if ("database".equals(domain)) { + return DATABASE_DIR; + } else if ("root".equals(domain)) { + return ROOT_DIR; + } else if ("sharedpref".equals(domain)) { + return SHAREDPREF_DIR; + } else if ("external".equals(domain)) { + return EXTERNAL_DIR; + } else { + return null; + } + } + + /** + * Let's be strict about the type of xml the client can write. If we see anything untoward, + * throw an XmlPullParserException. + */ + private void validateInnerTagContents(XmlPullParser parser) + throws XmlPullParserException { + if (parser.getAttributeCount() > 2) { + throw new XmlPullParserException("At most 2 tag attributes allowed for \"" + + parser.getName() + "\" tag (\"domain\" & \"path\"."); + } + if (!"include".equals(parser.getName()) && !"exclude".equals(parser.getName())) { + throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" + + " \"<exclude/>. You provided \"" + parser.getName() + "\""); + } + } + } } diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index 17cff5c..32951d9 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -32,4 +32,5 @@ interface ITrustManager { void reportKeyguardShowingChanged(); boolean isDeviceLocked(int userId); boolean isDeviceSecure(int userId); + boolean hasUserAuthenticatedSinceBoot(int userId); } diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index b5c5317..8cab565 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -147,6 +147,23 @@ public class TrustManager { } } + /** + * Checks whether the specified user has been authenticated since the last boot. + * + * @param userId the user id of the user to check for + * @return true if the user has authenticated since boot, false otherwise + * + * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + */ + public boolean hasUserAuthenticatedSinceBoot(int userId) { + try { + return mService.hasUserAuthenticatedSinceBoot(userId); + } catch (RemoteException e) { + onError(e); + return false; + } + } + private void onError(Exception e) { Log.e(TAG, "Error while calling TrustManagerService", e); } diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 00248cc..1205708 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -1053,6 +1053,20 @@ public class AppWidgetManager { } } + /** + * @hide + */ + public boolean isBoundWidgetPackage(String packageName, int userId) { + if (mService == null) { + return false; + } + try { + return mService.isBoundWidgetPackage(packageName, userId); + } catch (RemoteException re) { + throw new RuntimeException("system server dead?", re); + } + } + private boolean bindAppWidgetIdIfAllowed(int appWidgetId, int profileId, ComponentName provider, Bundle options) { if (mService == null) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 3044a94..0a77868 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -34,6 +34,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.app.ActivityThread; import android.os.SystemProperties; +import android.provider.Settings; import android.os.Binder; import android.util.Log; import android.util.Pair; @@ -389,7 +390,7 @@ public final class BluetoothAdapter { * @hide */ public static final String ACTION_BLE_STATE_CHANGED = - "anrdoid.bluetooth.adapter.action.BLE_STATE_CHANGED"; + "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; /** * Broadcast Action: The notifys Bluetooth ACL connected event. This will be @@ -632,24 +633,6 @@ public final class BluetoothAdapter { } /** - * Returns true if LE only mode is enabled, that is apps - * have authorization to turn only BT ON and the calling - * app has privilage to do so - */ - private boolean isLEAlwaysOnEnabled() { - boolean ret = false; - if (SystemProperties.getBoolean("ro.bluetooth.blealwayson", true) == true) { - Log.v(TAG, "LE always on mode is enabled"); - // TODO: System API authorization check - ret = true; - } else { - Log.v(TAG, "LE always on mode is disabled"); - ret = false; - } - return ret; - } - - /** * Turns off Bluetooth LE which was earlier turned on by calling EnableBLE(). * * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition @@ -676,7 +659,7 @@ public final class BluetoothAdapter { * @hide */ public boolean disableBLE() { - if (isLEAlwaysOnEnabled() != true) return false; + if (!isBleScanAlwaysAvailable()) return false; int state = getLeState(); if (state == BluetoothAdapter.STATE_ON) { @@ -738,7 +721,7 @@ public final class BluetoothAdapter { * @hide */ public boolean enableBLE() { - if (isLEAlwaysOnEnabled() != true) return false; + if (!isBleScanAlwaysAvailable()) return false; if (isLeEnabled() == true) { if (DBG) Log.d(TAG, "enableBLE(): BT is already enabled..!"); @@ -1243,8 +1226,12 @@ public final class BluetoothAdapter { */ @SystemApi public boolean isBleScanAlwaysAvailable() { - // TODO: implement after Settings UI change. - return false; + try { + return mManagerService.isBleScanAlwaysAvailable(); + } catch (RemoteException e) { + Log.e(TAG, "remote expection when calling isBleScanAlwaysAvailable", e); + return false; + } } /** @@ -1705,7 +1692,8 @@ public final class BluetoothAdapter { * @param context Context of the application * @param listener The service Listener for connection callbacks. * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEALTH}, - * {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#A2DP}. + * {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}. + * {@link BluetoothProfile#GATT} or {@link BluetoothProfile#GATT_SERVER}. * @return true on success, false on error */ public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, diff --git a/core/java/android/bluetooth/BluetoothHealthCallback.java b/core/java/android/bluetooth/BluetoothHealthCallback.java index baf2ade..128376f 100644 --- a/core/java/android/bluetooth/BluetoothHealthCallback.java +++ b/core/java/android/bluetooth/BluetoothHealthCallback.java @@ -17,6 +17,7 @@ package android.bluetooth; +import android.annotation.BinderThread; import android.os.ParcelFileDescriptor; import android.util.Log; @@ -39,6 +40,7 @@ public abstract class BluetoothHealthCallback { * {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_SUCCESS} or * {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_FAILURE} */ + @BinderThread public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config, int status) { Log.d(TAG, "onHealthAppConfigurationStatusChange: " + config + "Status: " + status); @@ -58,6 +60,7 @@ public abstract class BluetoothHealthCallback { * @param channelId The id associated with the channel. This id will be used * in future calls like when disconnecting the channel. */ + @BinderThread public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config, BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd, int channelId) { diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java index 7b4c6f9..014cb22 100644 --- a/core/java/android/bluetooth/BluetoothSap.java +++ b/core/java/android/bluetooth/BluetoothSap.java @@ -28,13 +28,41 @@ import android.os.IBinder; import android.os.ServiceManager; import android.util.Log; - +/** + * This class provides the APIs to control the Bluetooth SIM + * Access Profile (SAP). + * + * <p>BluetoothSap is a proxy object for controlling the Bluetooth + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothSap proxy object. + * + * <p>Each method is protected with its appropriate permission. + * @hide + */ public final class BluetoothSap implements BluetoothProfile { private static final String TAG = "BluetoothSap"; private static final boolean DBG = true; private static final boolean VDBG = false; + /** + * Intent used to broadcast the change in connection state of the profile. + * + * <p>This intent will have 4 extras: + * <ul> + * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> + * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * </ul> + * + * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * @hide + */ public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; @@ -43,12 +71,22 @@ public final class BluetoothSap implements BluetoothProfile { private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; - /** There was an error trying to obtain the state */ - public static final int STATE_ERROR = -1; + /** + * There was an error trying to obtain the state. + * @hide + */ + public static final int STATE_ERROR = -1; - public static final int RESULT_FAILURE = 0; + /** + * Connection state change succceeded. + * @hide + */ public static final int RESULT_SUCCESS = 1; - /** Connection canceled before completion. */ + + /** + * Connection canceled before completion. + * @hide + */ public static final int RESULT_CANCELED = 2; final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = @@ -124,6 +162,7 @@ public final class BluetoothSap implements BluetoothProfile { * Other public functions of BluetoothSap will return default error * results once close() has been called. Multiple invocations of close() * are ok. + * @hide */ public synchronized void close() { IBluetoothManager mgr = mAdapter.getBluetoothManager(); @@ -152,6 +191,7 @@ public final class BluetoothSap implements BluetoothProfile { * Get the current state of the BluetoothSap service. * @return One of the STATE_ return codes, or STATE_ERROR if this proxy * object is currently not connected to the Sap service. + * @hide */ public int getState() { if (VDBG) log("getState()"); @@ -171,6 +211,7 @@ public final class BluetoothSap implements BluetoothProfile { * @return The remote Bluetooth device, or null if not in connected or * connecting state, or if this proxy object is not connected to * the Sap service. + * @hide */ public BluetoothDevice getClient() { if (VDBG) log("getClient()"); @@ -189,6 +230,7 @@ public final class BluetoothSap implements BluetoothProfile { * Returns true if the specified Bluetooth device is connected. * Returns false if not connected, or if this proxy object is not * currently connected to the Sap service. + * @hide */ public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); @@ -206,6 +248,7 @@ public final class BluetoothSap implements BluetoothProfile { /** * Initiate connection. Initiation of outgoing connections is not * supported for SAP server. + * @hide */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")" + "not supported for SAPS"); @@ -218,6 +261,7 @@ public final class BluetoothSap implements BluetoothProfile { * @param device Remote Bluetooth Device * @return false on error, * true otherwise + * @hide */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); @@ -238,6 +282,7 @@ public final class BluetoothSap implements BluetoothProfile { * Get the list of connected devices. Currently at most one. * * @return list of connected devices + * @hide */ public List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); @@ -257,6 +302,7 @@ public final class BluetoothSap implements BluetoothProfile { * Get the list of devices matching specified states. Currently at most one. * * @return list of matching devices + * @hide */ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); @@ -276,6 +322,7 @@ public final class BluetoothSap implements BluetoothProfile { * Get connection state of device * * @return device connection state + * @hide */ public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); @@ -300,6 +347,7 @@ public final class BluetoothSap implements BluetoothProfile { * @param device Paired bluetooth device * @param priority * @return true if priority is set, false on error + * @hide */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); @@ -325,6 +373,7 @@ public final class BluetoothSap implements BluetoothProfile { * * @param device Bluetooth device * @return priority of the device + * @hide */ public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 5702d11..5cf2300 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -91,9 +91,13 @@ public final class BluetoothSocket implements Closeable { public static final int MAX_RFCOMM_CHANNEL = 30; /*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF; - /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */ + /** RFCOMM socket */ public static final int TYPE_RFCOMM = 1; + + /** SCO socket */ public static final int TYPE_SCO = 2; + + /** L2CAP socket */ public static final int TYPE_L2CAP = 3; /*package*/ static final int EBADFD = 77; @@ -578,8 +582,8 @@ public final class BluetoothSocket implements Closeable { } /** - * Get the type of the underlying connection - * @return one of TYPE_ + * Get the type of the underlying connection. + * @return one of {@link #TYPE_RFCOMM}, {@link #TYPE_SCO} or {@link #TYPE_L2CAP} */ public int getConnectionType() { return mType; diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl index 8d1ce99..0b81ee8 100644 --- a/core/java/android/bluetooth/IBluetoothManager.aidl +++ b/core/java/android/bluetooth/IBluetoothManager.aidl @@ -44,6 +44,8 @@ interface IBluetoothManager String getAddress(); String getName(); + + boolean isBleScanAlwaysAvailable(); int updateBleAppCount(IBinder b, boolean enable); boolean isBleAppPresent(); } diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java index 27b96bd..61b2e78 100644 --- a/core/java/android/bluetooth/le/ScanCallback.java +++ b/core/java/android/bluetooth/le/ScanCallback.java @@ -53,8 +53,10 @@ public abstract class ScanCallback { /** * Callback when a BLE advertisement has been found. * - * @param callbackType Determines how this callback was triggered. Could be of - * {@link ScanSettings#CALLBACK_TYPE_ALL_MATCHES} + * @param callbackType Determines how this callback was triggered. Could be one of + * {@link ScanSettings#CALLBACK_TYPE_ALL_MATCHES}, + * {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH} or + * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST} * @param result A Bluetooth LE scan result. */ public void onScanResult(int callbackType, ScanResult result) { diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java index f103cae..4eeb577 100644 --- a/core/java/android/bluetooth/le/ScanSettings.java +++ b/core/java/android/bluetooth/le/ScanSettings.java @@ -59,17 +59,13 @@ public final class ScanSettings implements Parcelable { /** * A result callback is only triggered for the first advertisement packet received that matches * the filter criteria. - * @hide */ - @SystemApi public static final int CALLBACK_TYPE_FIRST_MATCH = 2; /** * Receive a callback when advertisements are no longer received from a device that has been * previously reported by a first match callback. - * @hide */ - @SystemApi public static final int CALLBACK_TYPE_MATCH_LOST = 4; @@ -78,21 +74,18 @@ public final class ScanSettings implements Parcelable { */ /** * Match one advertisement per filter - * @hide */ public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1; /** * Match few advertisement per filter, depends on current capability and availibility of * the resources in hw - * @hide */ public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2; /** * Match as many advertisement per filter as hw could allow, depends on current * capability and availibility of the resources in hw - * @hide */ public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3; @@ -100,14 +93,12 @@ public final class ScanSettings implements Parcelable { /** * In Aggressive mode, hw will determine a match sooner even with feeble signal strength * and few number of sightings/match in a duration. - * @hide */ public static final int MATCH_MODE_AGGRESSIVE = 1; /** * For sticky mode, higher threshold of signal strength and sightings is required * before reporting by hw - * @hide */ public static final int MATCH_MODE_STICKY = 2; @@ -187,7 +178,7 @@ public final class ScanSettings implements Parcelable { mScanResultType = scanResultType; mReportDelayMillis = reportDelayMillis; mNumOfMatchesPerFilter = numOfMatchesPerFilter; - mMatchMode = numOfMatchesPerFilter; + mMatchMode = matchMode; } private ScanSettings(Parcel in) { @@ -236,7 +227,7 @@ public final class ScanSettings implements Parcelable { private int mScanResultType = SCAN_RESULT_TYPE_FULL; private long mReportDelayMillis = 0; private int mMatchMode = MATCH_MODE_AGGRESSIVE; - private int mNumOfMatchesPerFilter = MATCH_NUM_ONE_ADVERTISEMENT; + private int mNumOfMatchesPerFilter = MATCH_NUM_MAX_ADVERTISEMENT; /** * Set scan mode for Bluetooth LE scan. * @@ -324,7 +315,6 @@ public final class ScanSettings implements Parcelable { * {@link ScanSettings#MATCH_NUM_FEW_ADVERTISEMENT} or * {@link ScanSettings#MATCH_NUM_MAX_ADVERTISEMENT} * @throws IllegalArgumentException If the {@code matchMode} is invalid. - * @hide */ public Builder setNumOfMatches(int numOfMatches) { if (numOfMatches < MATCH_NUM_ONE_ADVERTISEMENT @@ -342,7 +332,6 @@ public final class ScanSettings implements Parcelable { * {@link ScanSettings#MATCH_MODE_AGGRESSIVE} or * {@link ScanSettings#MATCH_MODE_STICKY} * @throws IllegalArgumentException If the {@code matchMode} is invalid. - * @hide */ public Builder setMatchMode(int matchMode) { if (matchMode < MATCH_MODE_AGGRESSIVE diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index fd65d56..72e701d 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -211,7 +211,27 @@ public abstract class ContentProvider implements ComponentCallbacks2 { // We do not call ContentProvider#query with a modified where clause since // the implementation is not guaranteed to be backed by a SQL database, hence // it may not handle properly the tautology where clause we would have created. - return new MatrixCursor(projection, 0); + if (projection != null) { + return new MatrixCursor(projection, 0); + } + + // Null projection means all columns but we have no idea which they are. + // However, the caller may be expecting to access them my index. Hence, + // we have to execute the query as if allowed to get a cursor with the + // columns. We then use the column names to return an empty cursor. + Cursor cursor = ContentProvider.this.query(uri, projection, selection, + selectionArgs, sortOrder, CancellationSignal.fromTransport( + cancellationSignal)); + + // Create a projection for all columns. + final int columnCount = cursor.getCount(); + String[] allColumns = new String[columnCount]; + for (int i = 0; i < columnCount; i++) { + allColumns[i] = cursor.getColumnName(i); + } + + // Return an empty cursor for all columns. + return new MatrixCursor(allColumns, 0); } final String original = setCallingPackage(callingPkg); try { diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index f2e7fc4..4769bd0 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -593,7 +593,8 @@ final class ContentProviderProxy implements IContentProvider DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply); int has = reply.readInt(); - ParcelFileDescriptor fd = has != 0 ? reply.readFileDescriptor() : null; + ParcelFileDescriptor fd = has != 0 ? ParcelFileDescriptor.CREATOR + .createFromParcel(reply) : null; return fd; } finally { data.recycle(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5eacce3..8687c6b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -252,6 +252,21 @@ public abstract class Context { public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080; /** + * @hide Flag for {@link #bindService}: Like {@link #BIND_FOREGROUND_SERVICE}, + * but only applies while the device is awake. + */ + public static final int BIND_FOREGROUND_SERVICE_WHILE_AWAKE = 0x02000000; + + /** + * @hide Flag for {@link #bindService}: For only the case where the binding + * is coming from the system, set the process state to FOREGROUND_SERVICE + * instead of the normal maximum of IMPORTANT_FOREGROUND. That is, this is + * saying that the process shouldn't participate in the normal power reduction + * modes (removing network access etc). + */ + public static final int BIND_FOREGROUND_SERVICE = 0x04000000; + + /** * @hide Flag for {@link #bindService}: Treat the binding as hosting * an activity, an unbinding as the activity going in the background. * That is, when unbinding, the process when empty will go on the activity @@ -374,24 +389,30 @@ public abstract class Context { } /** - * Return a localized string from the application's package's + * Returns a localized string from the application's package's * default string table. * * @param resId Resource id for the string + * @return The string data associated with the resource, stripped of styled + * text information. */ + @NonNull public final String getString(@StringRes int resId) { return getResources().getString(resId); } /** - * Return a localized formatted string from the application's package's + * Returns a localized formatted string from the application's package's * default string table, substituting the format arguments as defined in * {@link java.util.Formatter} and {@link java.lang.String#format}. * * @param resId Resource id for the format string - * @param formatArgs The format arguments that will be used for substitution. + * @param formatArgs The format arguments that will be used for + * substitution. + * @return The string data associated with the resource, formatted and + * stripped of styled text information. */ - + @NonNull public final String getString(@StringRes int resId, Object... formatArgs) { return getResources().getString(resId, formatArgs); } diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 16f6b1e..43cc63b 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -570,13 +570,16 @@ public class ActivityInfo extends ComponentInfo Configuration.NATIVE_CONFIG_DENSITY, // DENSITY Configuration.NATIVE_CONFIG_LAYOUTDIR, // LAYOUT DIRECTION }; - /** @hide + + /** * Convert Java change bits to native. + * + * @hide */ public static int activityInfoConfigToNative(int input) { int output = 0; - for (int i=0; i<CONFIG_NATIVE_BITS.length; i++) { - if ((input&(1<<i)) != 0) { + for (int i = 0; i < CONFIG_NATIVE_BITS.length; i++) { + if ((input & (1 << i)) != 0) { output |= CONFIG_NATIVE_BITS[i]; } } @@ -584,6 +587,21 @@ public class ActivityInfo extends ComponentInfo } /** + * Convert native change bits to Java. + * + * @hide + */ + public static int activityInfoConfigNativeToJava(int input) { + int output = 0; + for (int i = 0; i < CONFIG_NATIVE_BITS.length; i++) { + if ((input & CONFIG_NATIVE_BITS[i]) != 0) { + output |= (1 << i); + } + } + return output; + } + + /** * @hide * Unfortunately some developers (OpenFeint I am looking at you) have * compared the configChanges bit field against absolute values, so if we diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 6c32873..707ef30 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -96,6 +96,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public String backupAgentName; /** + * An optional attribute that indicates the app supports automatic backup of app data. + * <p>0 is the default and means the app's entire data folder + managed external storage will + * be backed up; + * Any negative value indicates the app does not support full-data backup, though it may still + * want to participate via the traditional key/value backup API; + * A positive number specifies an xml resource in which the application has defined its backup + * include/exclude criteria. + * <p>If android:allowBackup is set to false, this attribute is ignored. + * + * @see {@link android.content.Context#getNoBackupFilesDir} + * @see {@link #FLAG_ALLOW_BACKUP} + */ + public int fullBackupContent = 0; + + /** * The default extra UI options for activities in this application. * Set from the {@link android.R.attr#uiOptions} attribute in the * activity's manifest. @@ -686,6 +701,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { pw.println(prefix + "uiOptions=0x" + Integer.toHexString(uiOptions)); } pw.println(prefix + "supportsRtl=" + (hasRtlSupport() ? "true" : "false")); + if (fullBackupContent > 0) { + pw.println(prefix + "fullBackupContent=@xml/" + fullBackupContent); + } else { + pw.println(prefix + "fullBackupContent=" + (fullBackupContent < 0 ? "false" : "true")); + } super.dumpBack(pw, prefix); } @@ -763,6 +783,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { uiOptions = orig.uiOptions; backupAgentName = orig.backupAgentName; hardwareAccelerated = orig.hardwareAccelerated; + fullBackupContent = orig.fullBackupContent; } @@ -816,6 +837,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(descriptionRes); dest.writeInt(uiOptions); dest.writeInt(hardwareAccelerated ? 1 : 0); + dest.writeInt(fullBackupContent); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -868,6 +890,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { descriptionRes = source.readInt(); uiOptions = source.readInt(); hardwareAccelerated = source.readInt() != 0; + fullBackupContent = source.readInt(); } /** diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index ae59bfc..94b0223 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -58,6 +58,7 @@ import android.content.IntentSender; * {@hide} */ interface IPackageManager { + boolean isPackageFrozen(String packageName); boolean isPackageAvailable(String packageName, int userId); PackageInfo getPackageInfo(String packageName, int flags, int userId); int getPackageUid(String packageName, int userId); @@ -95,9 +96,9 @@ interface IPackageManager { void removePermission(String name); - boolean grantPermission(String packageName, String permissionName, int userId); + void grantPermission(String packageName, String permissionName, int userId); - boolean revokePermission(String packageName, String permissionName, int userId); + void revokePermission(String packageName, String permissionName, int userId); boolean isProtectedBroadcast(String actionName); diff --git a/core/java/android/content/pm/IPackageMoveObserver.aidl b/core/java/android/content/pm/IPackageMoveObserver.aidl index 50ab3b5..86189fc 100644 --- a/core/java/android/content/pm/IPackageMoveObserver.aidl +++ b/core/java/android/content/pm/IPackageMoveObserver.aidl @@ -17,11 +17,13 @@ package android.content.pm; +import android.os.Bundle; + /** * Callback for moving package resources from the Package Manager. * @hide */ oneway interface IPackageMoveObserver { - void onStarted(int moveId, String title); + void onCreated(int moveId, in Bundle extras); void onStatusChanged(int moveId, int status, long estMillis); } diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java index e50b0ff..96000dd 100644 --- a/core/java/android/content/pm/IntentFilterVerificationInfo.java +++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java @@ -48,19 +48,18 @@ public final class IntentFilterVerificationInfo implements Parcelable { private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_STATUS = "status"; - private ArrayList<String> mDomains; + private ArraySet<String> mDomains = new ArraySet<>(); private String mPackageName; private int mMainStatus; public IntentFilterVerificationInfo() { mPackageName = null; - mDomains = new ArrayList<>(); mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; } public IntentFilterVerificationInfo(String packageName, ArrayList<String> domains) { mPackageName = packageName; - mDomains = domains; + mDomains.addAll(domains); mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; } @@ -73,14 +72,6 @@ public final class IntentFilterVerificationInfo implements Parcelable { readFromParcel(source); } - public ArrayList<String> getDomains() { - return mDomains; - } - - public ArraySet<String> getDomainsSet() { - return new ArraySet<>(mDomains); - } - public String getPackageName() { return mPackageName; } @@ -98,6 +89,14 @@ public final class IntentFilterVerificationInfo implements Parcelable { } } + public ArraySet<String> getDomains() { + return mDomains; + } + + public void setDomains(ArrayList<String> list) { + mDomains = new ArraySet<>(list); + } + public String getDomainsString() { StringBuilder sb = new StringBuilder(); for (String str : mDomains) { @@ -145,7 +144,6 @@ public final class IntentFilterVerificationInfo implements Parcelable { } mMainStatus = status; - mDomains = new ArrayList<>(); int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -201,15 +199,16 @@ public final class IntentFilterVerificationInfo implements Parcelable { private void readFromParcel(Parcel source) { mPackageName = source.readString(); mMainStatus = source.readInt(); - mDomains = new ArrayList<>(); - source.readStringList(mDomains); + ArrayList<String> list = new ArrayList<>(); + source.readStringList(list); + mDomains.addAll(list); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mPackageName); dest.writeInt(mMainStatus); - dest.writeStringList(mDomains); + dest.writeStringList(new ArrayList<>(mDomains)); } public static final Creator<IntentFilterVerificationInfo> CREATOR = @@ -221,5 +220,4 @@ public final class IntentFilterVerificationInfo implements Parcelable { return new IntentFilterVerificationInfo[size]; } }; - } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e1c271d..7ff6ec3 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -209,7 +209,14 @@ public abstract class PackageManager { * matching. This is a synonym for including the CATEGORY_DEFAULT in your * supplied Intent. */ - public static final int MATCH_DEFAULT_ONLY = 0x00010000; + public static final int MATCH_DEFAULT_ONLY = 0x00010000; + + /** + * Querying flag: if set and if the platform is doing any filtering of the results, then + * the filtering will not happen. This is a synonym for saying that all results should + * be returned. + */ + public static final int MATCH_ALL = 0x00020000; /** * Flag for {@link addCrossProfileIntentFilter}: if this flag is set: @@ -2637,6 +2644,8 @@ public abstract class PackageManager { * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}. * + * You can also set {@link #MATCH_ALL} for preventing the filtering of the results. + * * @return A List<ResolveInfo> containing one entry for each matching * Activity. These are ordered from best to worst match -- that * is, the first item in the list is what is returned by @@ -2658,6 +2667,8 @@ public abstract class PackageManager { * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}. * + * You can also set {@link #MATCH_ALL} for preventing the filtering of the results. + * * @return A List<ResolveInfo> containing one entry for each matching * Activity. These are ordered from best to worst match -- that * is, the first item in the list is what is returned by @@ -4201,7 +4212,7 @@ public abstract class PackageManager { /** {@hide} */ public static abstract class MoveCallback { - public abstract void onStarted(int moveId, String title); + public void onCreated(int moveId, Bundle extras) {} public abstract void onStatusChanged(int moveId, int status, long estMillis); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index fed9261..acc27c3 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2421,8 +2421,8 @@ public class PackageParser { if (allowBackup) { ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP; - // backupAgent, killAfterRestore, and restoreAnyVersion are only relevant - // if backup is possible for the given application. + // backupAgent, killAfterRestore, fullBackupContent and restoreAnyVersion are only + // relevant if backup is possible for the given application. String backupAgent = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestApplication_backupAgent, Configuration.NATIVE_CONFIG_VERSION); @@ -2449,6 +2449,20 @@ public class PackageParser { ai.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY; } } + + TypedValue v = sa.peekValue( + com.android.internal.R.styleable.AndroidManifestApplication_fullBackupContent); + if (v != null && (ai.fullBackupContent = v.resourceId) == 0) { + if (DEBUG_BACKUP) { + Slog.v(TAG, "fullBackupContent specified as boolean=" + + (v.data == 0 ? "false" : "true")); + } + // "false" => -1, "true" => 0 + ai.fullBackupContent = (v.data == 0 ? -1 : 0); + } + if (DEBUG_BACKUP) { + Slog.v(TAG, "fullBackupContent=" + ai.fullBackupContent + " for " + pkgName); + } } TypedValue v = sa.peekValue( @@ -4315,9 +4329,6 @@ public class PackageParser { // Additional data supplied by callers. public Object mExtras; - // Whether an operation is currently pending on this package - public boolean mOperationPending; - // Applications hardware preferences public ArrayList<ConfigurationInfo> configPreferences = null; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index a176593..525059f 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -789,6 +789,7 @@ public final class AssetManager implements AutoCloseable { TypedValue outValue, boolean resolve); /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix); + /*package*/ native static final int getThemeChangingConfigurations(long theme); private native final long openXmlAssetNative(int cookie, String fileName); diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index fdafb04..14bfac5 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -271,7 +271,7 @@ public class ColorStateList implements Parcelable { final TypedArray a = Resources.obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem); final int[] themeAttrs = a.extractThemeAttrs(); - final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, 0); + final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, Color.MAGENTA); final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f); changingConfigurations |= a.getChangingConfigurations(); @@ -296,7 +296,9 @@ public class ColorStateList implements Parcelable { } stateSpec = StateSet.trimStateSet(stateSpec, j); - // Apply alpha modulation. + // Apply alpha modulation. If we couldn't resolve the color or + // alpha yet, the default values leave us enough information to + // modulate again during applyTheme(). final int color = modulateColorAlpha(baseColor, alphaMod); if (listSize == 0 || stateSpec.length == 0) { defaultColor = color; @@ -365,14 +367,31 @@ public class ColorStateList implements Parcelable { if (themeAttrsList[i] != null) { final TypedArray a = t.resolveAttributes(themeAttrsList[i], R.styleable.ColorStateListItem); + + final float defaultAlphaMod; + if (themeAttrsList[i][R.styleable.ColorStateListItem_color] != 0) { + // If the base color hasn't been resolved yet, the current + // color's alpha channel is either full-opacity (if we + // haven't resolved the alpha modulation yet) or + // pre-modulated. Either is okay as a default value. + defaultAlphaMod = Color.alpha(mColors[i]) / 255.0f; + } else { + // Otherwise, the only correct default value is 1. Even if + // nothing is resolved during this call, we can apply this + // multiple times without losing of information. + defaultAlphaMod = 1.0f; + } + final int baseColor = a.getColor( R.styleable.ColorStateListItem_color, mColors[i]); final float alphaMod = a.getFloat( - R.styleable.ColorStateListItem_alpha, 1.0f); - + R.styleable.ColorStateListItem_alpha, defaultAlphaMod); mColors[i] = modulateColorAlpha(baseColor, alphaMod); + + // Account for any configuration changes. mChangingConfigurations |= a.getChangingConfigurations(); + // Extract the theme attributes, if any. themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]); if (themeAttrsList[i] != null) { hasUnresolvedAttrs = true; diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 334d180..ae41b69 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -393,10 +393,11 @@ public class Resources { * @throws NotFoundException Throws NotFoundException if the given ID does not exist. * * @return String The string data associated with the resource, - * stripped of styled text information. + * stripped of styled text information. */ + @NonNull public String getString(@StringRes int id) throws NotFoundException { - CharSequence res = getText(id); + final CharSequence res = getText(id); if (res != null) { return res.toString(); } @@ -421,11 +422,11 @@ public class Resources { * @throws NotFoundException Throws NotFoundException if the given ID does not exist. * * @return String The string data associated with the resource, - * stripped of styled text information. + * stripped of styled text information. */ - public String getString(@StringRes int id, Object... formatArgs) - throws NotFoundException { - String raw = getString(id); + @NonNull + public String getString(@StringRes int id, Object... formatArgs) throws NotFoundException { + final String raw = getString(id); return String.format(mConfiguration.locale, raw, formatArgs); } @@ -1730,6 +1731,19 @@ public class Resources { } /** + * Returns a bit mask of configuration changes that will impact this + * theme (and thus require completely reloading it). + * + * @return a bit mask of configuration changes, as defined by + * {@link ActivityInfo} + * @see ActivityInfo + */ + public int getChangingConfigurations() { + final int nativeChangingConfig = AssetManager.getThemeChangingConfigurations(mTheme); + return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); + } + + /** * Print contents of this theme out to the log. For debugging only. * * @param priority The log priority to use. diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index d88594d..1fc69c0 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -460,9 +460,8 @@ public class Camera { mEventHandler = null; } - String packageName = ActivityThread.currentPackageName(); - - return native_setup(new WeakReference<Camera>(this), cameraId, halVersion, packageName); + return native_setup(new WeakReference<Camera>(this), cameraId, halVersion, + ActivityThread.currentOpPackageName()); } private int cameraInitNormal(int cameraId) { diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl index 9bc2f46..7b96e20 100644 --- a/core/java/android/hardware/ICameraService.aidl +++ b/core/java/android/hardware/ICameraService.aidl @@ -38,13 +38,13 @@ interface ICameraService int getCameraInfo(int cameraId, out CameraInfo info); int connect(ICameraClient client, int cameraId, - String clientPackageName, + String opPackageName, int clientUid, // Container for an ICamera object out BinderHolder device); int connectDevice(ICameraDeviceCallbacks callbacks, int cameraId, - String clientPackageName, + String opPackageName, int clientUid, // Container for an ICameraDeviceUser object out BinderHolder device); @@ -69,7 +69,7 @@ interface ICameraService int connectLegacy(ICameraClient client, int cameraId, int halVersion, - String clientPackageName, + String opPackageName, int clientUid, // Container for an ICamera object out BinderHolder device); diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 11037fd..22a9e9c 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -41,16 +41,19 @@ import java.util.List; */ public class SystemSensorManager extends SensorManager { private static native void nativeClassInit(); - private static native int nativeGetNextSensor(Sensor sensor, int next); - private static native int nativeEnableDataInjection(boolean enable); + private static native long nativeCreate(String opPackageName); + private static native int nativeGetNextSensor(long nativeInstance, Sensor sensor, int next); + private static native int nativeEnableDataInjection(long nativeInstance, boolean enable); private static boolean sSensorModuleInitialized = false; - private static final Object sSensorModuleLock = new Object(); - private static final ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>(); - private static final SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>(); private static InjectEventQueue mInjectEventQueue = null; private static boolean mDataInjectionMode = false; + private final Object mLock = new Object(); + + private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>(); + private final SparseArray<Sensor> mHandleToSensor = new SparseArray<>(); + // Listener list private final HashMap<SensorEventListener, SensorEventQueue> mSensorListeners = new HashMap<SensorEventListener, SensorEventQueue>(); @@ -60,44 +63,44 @@ public class SystemSensorManager extends SensorManager { // Looper associated with the context in which this instance was created. private final Looper mMainLooper; private final int mTargetSdkLevel; - private final String mPackageName; + private final Context mContext; private final boolean mHasDataInjectionPermissions; + private final long mNativeInstance; /** {@hide} */ public SystemSensorManager(Context context, Looper mainLooper) { mMainLooper = mainLooper; mTargetSdkLevel = context.getApplicationInfo().targetSdkVersion; - mPackageName = context.getPackageName(); - synchronized(sSensorModuleLock) { + mContext = context; + mNativeInstance = nativeCreate(context.getOpPackageName()); + + synchronized(mLock) { if (!sSensorModuleInitialized) { sSensorModuleInitialized = true; - nativeClassInit(); - - // initialize the sensor list - final ArrayList<Sensor> fullList = sFullSensorsList; - int i = 0; - do { - Sensor sensor = new Sensor(); - i = nativeGetNextSensor(sensor, i); - if (i>=0) { - //Log.d(TAG, "found sensor: " + sensor.getName() + - // ", handle=" + sensor.getHandle()); - fullList.add(sensor); - sHandleToSensor.append(sensor.getHandle(), sensor); - } - } while (i>0); } mHasDataInjectionPermissions = context.checkSelfPermission( Manifest.permission.HARDWARE_TEST) == PackageManager.PERMISSION_GRANTED; } + + // initialize the sensor list + int i = 0; + while(true) { + Sensor sensor = new Sensor(); + i = nativeGetNextSensor(mNativeInstance, sensor, i); + if (i <= 0) { + break; + } + mFullSensorsList.add(sensor); + mHandleToSensor.append(sensor.getHandle(), sensor); + } } /** @hide */ @Override protected List<Sensor> getFullSensorList() { - return sFullSensorsList; + return mFullSensorsList; } @@ -232,8 +235,8 @@ public class SystemSensorManager extends SensorManager { throw new SecurityException("Permission denial. Calling enableDataInjection without " + Manifest.permission.HARDWARE_TEST); } - synchronized (sSensorModuleLock) { - int ret = nativeEnableDataInjection(enable); + synchronized (mLock) { + int ret = nativeEnableDataInjection(mNativeInstance, enable); // The HAL does not support injection. Ignore. if (ret != 0) { Log.e(TAG, "HAL does not support data injection"); @@ -255,7 +258,7 @@ public class SystemSensorManager extends SensorManager { throw new SecurityException("Permission denial. Calling injectSensorData without " + Manifest.permission.HARDWARE_TEST); } - synchronized (sSensorModuleLock) { + synchronized (mLock) { if (!mDataInjectionMode) { Log.e(TAG, "Data injection mode not activated before calling injectSensorData"); return false; @@ -284,15 +287,17 @@ public class SystemSensorManager extends SensorManager { * SensorManager instance. */ private static abstract class BaseEventQueue { - private native long nativeInitBaseEventQueue(WeakReference<BaseEventQueue> eventQWeak, - MessageQueue msgQ, float[] scratch, String packageName, int mode); + private static native long nativeInitBaseEventQueue(long nativeManager, + WeakReference<BaseEventQueue> eventQWeak, MessageQueue msgQ, float[] scratch, + String packageName, int mode, String opPackageName); private static native int nativeEnableSensor(long eventQ, int handle, int rateUs, int maxBatchReportLatencyUs); private static native int nativeDisableSensor(long eventQ, int handle); private static native void nativeDestroySensorEventQueue(long eventQ); private static native int nativeFlushSensor(long eventQ); private static native int nativeInjectSensorData(long eventQ, int handle, - float[] values,int accuracy, long timestamp); + float[] values,int accuracy, long timestamp); + private long nSensorEventQueue; private final SparseBooleanArray mActiveSensors = new SparseBooleanArray(); protected final SparseIntArray mSensorAccuracies = new SparseIntArray(); @@ -305,8 +310,9 @@ public class SystemSensorManager extends SensorManager { protected static final int OPERATING_MODE_DATA_INJECTION = 1; BaseEventQueue(Looper looper, SystemSensorManager manager, int mode) { - nSensorEventQueue = nativeInitBaseEventQueue(new WeakReference<BaseEventQueue>(this), - looper.getQueue(), mScratch, manager.mPackageName, mode); + nSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance, + new WeakReference<>(this), looper.getQueue(), mScratch, + manager.mContext.getPackageName(), mode, manager.mContext.getOpPackageName()); mCloseGuard.open("dispose"); mManager = manager; } @@ -339,7 +345,7 @@ public class SystemSensorManager extends SensorManager { for (int i=0 ; i<mActiveSensors.size(); i++) { if (mActiveSensors.valueAt(i) == true) { int handle = mActiveSensors.keyAt(i); - Sensor sensor = sHandleToSensor.get(handle); + Sensor sensor = mManager.mHandleToSensor.get(handle); if (sensor != null) { disableSensor(sensor); mActiveSensors.put(handle, false); @@ -452,7 +458,7 @@ public class SystemSensorManager extends SensorManager { @Override protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy, long timestamp) { - final Sensor sensor = sHandleToSensor.get(handle); + final Sensor sensor = mManager.mHandleToSensor.get(handle); SensorEvent t = null; synchronized (mSensorsEvents) { t = mSensorsEvents.get(handle); @@ -481,7 +487,7 @@ public class SystemSensorManager extends SensorManager { @SuppressWarnings("unused") protected void dispatchFlushCompleteEvent(int handle) { if (mListener instanceof SensorEventListener2) { - final Sensor sensor = sHandleToSensor.get(handle); + final Sensor sensor = mManager.mHandleToSensor.get(handle); ((SensorEventListener2)mListener).onFlushCompleted(sensor); } return; @@ -519,7 +525,7 @@ public class SystemSensorManager extends SensorManager { @Override protected void dispatchSensorEvent(int handle, float[] values, int accuracy, long timestamp) { - final Sensor sensor = sHandleToSensor.get(handle); + final Sensor sensor = mManager.mHandleToSensor.get(handle); TriggerEvent t = null; synchronized (mTriggerEvents) { t = mTriggerEvents.get(handle); @@ -546,7 +552,7 @@ public class SystemSensorManager extends SensorManager { } } - static final class InjectEventQueue extends BaseEventQueue { + final class InjectEventQueue extends BaseEventQueue { public InjectEventQueue(Looper looper, SystemSensorManager manager) { super(looper, manager, OPERATING_MODE_DATA_INJECTION); } diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 0cf8df1..aeddf03 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -114,6 +114,11 @@ public abstract class CameraCaptureSession implements AutoCloseable { * the Surface provided to prepare must not be used as a target of a CaptureRequest submitted * to this session.</p> * + * <p>{@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY} + * devices cannot pre-allocate output buffers; for those devices, + * {@link StateCallback#onSurfacePrepared} will be immediately called, and no preallocation is + * done.</p> + * * @param surface the output Surface for which buffers should be pre-allocated. Must be one of * the output Surfaces used to create this session. * diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 87a1ca9..19e821c 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -603,10 +603,9 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>List of available high speed video size and fps range configurations * supported by the camera device, in the format of (width, height, fps_min, fps_max).</p> - * <p>When HIGH_SPEED_VIDEO is supported in {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes}, - * this metadata will list the supported high speed video size and fps range - * configurations. All the sizes listed in this configuration will be a subset - * of the sizes reported by StreamConfigurationMap#getOutputSizes for processed + * <p>When HIGH_SPEED_VIDEO is supported in {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes}, this metadata + * will list the supported high speed video size and fps range configurations. All the sizes + * listed in this configuration will be a subset of the sizes reported by {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes } for processed * non-stalling formats.</p> * <p>For the high speed video use case, where the application will set * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the application must @@ -1116,11 +1115,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * into the 3 stream types as below:</p> * <ul> * <li>Processed (but stalling): any non-RAW format with a stallDurations > 0. - * Typically JPEG format (ImageFormat#JPEG).</li> - * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10, ImageFormat#RAW12, - * and ImageFormat#RAW_OPAQUE.</li> + * Typically {@link android.graphics.ImageFormat#JPEG JPEG format}.</li> + * <li>Raw formats: {@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}, {@link android.graphics.ImageFormat#RAW10 RAW10}, or {@link android.graphics.ImageFormat#RAW12 RAW12}.</li> * <li>Processed (but not-stalling): any non-RAW format without a stall duration. - * Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li> + * Typically {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888}, + * {@link android.graphics.ImageFormat#NV21 NV21}, or + * {@link android.graphics.ImageFormat#YV12 YV12}.</li> * </ul> * <p><b>Range of valid values:</b><br></p> * <p>For processed (and stalling) format streams, >= 1.</p> @@ -1148,10 +1148,9 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * be any <code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> * <p>In particular, a <code>RAW</code> format is typically one of:</p> * <ul> - * <li>ImageFormat#RAW_SENSOR</li> - * <li>ImageFormat#RAW10</li> - * <li>ImageFormat#RAW12</li> - * <li>Opaque <code>RAW</code></li> + * <li>{@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}</li> + * <li>{@link android.graphics.ImageFormat#RAW10 RAW10}</li> + * <li>{@link android.graphics.ImageFormat#RAW12 RAW12}</li> * </ul> * <p>LEGACY mode devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> LEGACY) * never support raw streams.</p> @@ -1180,13 +1179,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Processed (but not-stalling) is defined as any non-RAW format without a stall duration. * Typically:</p> * <ul> - * <li>ImageFormat#YUV_420_888</li> - * <li>ImageFormat#NV21</li> - * <li>ImageFormat#YV12</li> - * <li>Implementation-defined formats, i.e. StreamConfiguration#isOutputSupportedFor(Class)</li> + * <li>{@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888}</li> + * <li>{@link android.graphics.ImageFormat#NV21 NV21}</li> + * <li>{@link android.graphics.ImageFormat#YV12 YV12}</li> + * <li>Implementation-defined formats, i.e. {@link android.hardware.camera2.params.StreamConfigurationMap#isOutputSupportedFor(Class) }</li> * </ul> - * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with - * a processed format -- it will return 0 for a non-stalling stream.</p> + * <p>For full guarantees, query {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } with a + * processed format -- it will return 0 for a non-stalling stream.</p> * <p>LEGACY devices will support at least 2 processing/non-stalling streams.</p> * <p><b>Range of valid values:</b><br></p> * <p>>= 3 @@ -1212,10 +1211,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * the camera device. Using more streams simultaneously may require more hardware and * CPU resources that will consume more power. The image format for this kind of an output stream can * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> - * <p>A processed and stalling format is defined as any non-RAW format with a stallDurations > 0. - * Typically only the <code>JPEG</code> format (ImageFormat#JPEG) is a stalling format.</p> - * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with - * a processed format -- it will return a non-0 value for a stalling stream.</p> + * <p>A processed and stalling format is defined as any non-RAW format with a stallDurations + * > 0. Typically only the {@link android.graphics.ImageFormat#JPEG JPEG format} is a + * stalling format.</p> + * <p>For full guarantees, query {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } with a + * processed format -- it will return a non-0 value for a stalling stream.</p> * <p>LEGACY devices will support up to 1 processing/stalling stream.</p> * <p><b>Range of valid values:</b><br></p> * <p>>= 1</p> @@ -1232,10 +1232,9 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>The maximum numbers of any type of input streams * that can be configured and used simultaneously by a camera device.</p> * <p>When set to 0, it means no input stream is supported.</p> - * <p>The image format for a input stream can be any supported - * format returned by StreamConfigurationMap#getInputFormats. When using an - * input stream, there must be at least one output stream - * configured to to receive the reprocessed images.</p> + * <p>The image format for a input stream can be any supported format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }. When using an + * input stream, there must be at least one output stream configured to to receive the + * reprocessed images.</p> * <p>When an input stream and some output streams are used in a reprocessing request, * only the input buffer will be used to produce these output stream buffers, and a * new sensor image will not be captured.</p> @@ -1352,7 +1351,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>A list of all keys that the camera device has available - * to use with CaptureRequest.</p> + * to use with {@link android.hardware.camera2.CaptureRequest }.</p> * <p>Attempting to set a key into a CaptureRequest that is not * listed here will result in an invalid request and will be rejected * by the camera device.</p> @@ -1370,7 +1369,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>A list of all keys that the camera device has available - * to use with CaptureResult.</p> + * to use with {@link android.hardware.camera2.CaptureResult }.</p> * <p>Attempting to get a key from a CaptureResult that is not * listed here will always return a <code>null</code> value. Getting a key from * a CaptureResult that is listed here will generally never return a <code>null</code> @@ -1396,7 +1395,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>A list of all keys that the camera device has available - * to use with CameraCharacteristics.</p> + * to use with {@link android.hardware.camera2.CameraCharacteristics }.</p> * <p>This entry follows the same rules as * android.request.availableResultKeys (except that it applies for * CameraCharacteristics instead of CaptureResult). See above for more @@ -1535,34 +1534,31 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </thead> * <tbody> * <tr> - * <td align="left">PRIVATE (ImageFormat#PRIVATE)</td> - * <td align="left">JPEG</td> + * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td> + * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td> * <td align="left">OPAQUE_REPROCESSING</td> * </tr> * <tr> - * <td align="left">PRIVATE</td> - * <td align="left">YUV_420_888</td> + * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td> + * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td> * <td align="left">OPAQUE_REPROCESSING</td> * </tr> * <tr> - * <td align="left">YUV_420_888</td> - * <td align="left">JPEG</td> + * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td> + * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td> * <td align="left">YUV_REPROCESSING</td> * </tr> * <tr> - * <td align="left">YUV_420_888</td> - * <td align="left">YUV_420_888</td> + * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td> + * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td> * <td align="left">YUV_REPROCESSING</td> * </tr> * </tbody> * </table> - * <p>PRIVATE refers to a device-internal format that is not directly application-visible. - * A PRIVATE input surface can be acquired by - * ImageReader.newOpaqueInstance(width, height, maxImages). - * For a OPAQUE_REPROCESSING-capable camera device, using the PRIVATE format - * as either input or output will never hurt maximum frame rate (i.e. - * StreamConfigurationMap#getOutputStallDuration(format, size) is always 0), - * where format is ImageFormat#PRIVATE.</p> + * <p>PRIVATE refers to a device-internal format that is not directly application-visible. A + * PRIVATE input surface can be acquired by {@link android.media.ImageReader#newOpaqueInstance }.</p> + * <p>For a OPAQUE_REPROCESSING-capable camera device, using the PRIVATE format as either input + * or output will never hurt maximum frame rate (i.e. {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration getOutputStallDuration(ImageFormat.PRIVATE, size)} is always 0),</p> * <p>Attempting to configure an input stream with output streams not * listed as available in this map is not valid.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> @@ -1680,7 +1676,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * android.scaler.availableStallDurations for more details about * calculating the max frame rate.</p> * <p>(Keep in sync with - * StreamConfigurationMap#getOutputMinFrameDuration)</p> + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration })</p> * <p><b>Units</b>: (format, width, height, ns) x n</p> * <p>This key is available on all devices.</p> * @@ -1692,7 +1688,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>This lists the maximum stall duration for each - * format/size combination.</p> + * output format/size combination.</p> * <p>A stall duration is how much extra time would get added * to the normal minimum frame duration for a repeating request * that has streams with non-zero stall.</p> @@ -1734,12 +1730,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * ignored).</p> * <p>The following formats may always have a stall duration:</p> * <ul> - * <li>ImageFormat#JPEG</li> - * <li>ImageFormat#RAW_SENSOR</li> + * <li>{@link android.graphics.ImageFormat#JPEG }</li> + * <li>{@link android.graphics.ImageFormat#RAW_SENSOR }</li> * </ul> * <p>The following formats will never have a stall duration:</p> * <ul> - * <li>ImageFormat#YUV_420_888</li> + * <li>{@link android.graphics.ImageFormat#YUV_420_888 }</li> + * <li>{@link android.graphics.ImageFormat#RAW10 }</li> * </ul> * <p>All other formats may or may not have an allowed stall duration on * a per-capability basis; refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} @@ -1747,7 +1744,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} for more information about * calculating the max frame rate (absent stalls).</p> * <p>(Keep up to date with - * StreamConfigurationMap#getOutputStallDuration(int, Size) )</p> + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } )</p> * <p><b>Units</b>: (format, width, height, ns) x n</p> * <p>This key is available on all devices.</p> * @@ -1786,57 +1783,57 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </thead> * <tbody> * <tr> - * <td align="center">JPEG</td> + * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td> * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td> * <td align="center">Any</td> * <td align="center"></td> * </tr> * <tr> - * <td align="center">JPEG</td> + * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td> * <td align="center">1920x1080 (1080p)</td> * <td align="center">Any</td> * <td align="center">if 1080p <= activeArraySize</td> * </tr> * <tr> - * <td align="center">JPEG</td> + * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td> * <td align="center">1280x720 (720)</td> * <td align="center">Any</td> * <td align="center">if 720p <= activeArraySize</td> * </tr> * <tr> - * <td align="center">JPEG</td> + * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td> * <td align="center">640x480 (480p)</td> * <td align="center">Any</td> * <td align="center">if 480p <= activeArraySize</td> * </tr> * <tr> - * <td align="center">JPEG</td> + * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td> * <td align="center">320x240 (240p)</td> * <td align="center">Any</td> * <td align="center">if 240p <= activeArraySize</td> * </tr> * <tr> - * <td align="center">YUV_420_888</td> + * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td> * <td align="center">all output sizes available for JPEG</td> * <td align="center">FULL</td> * <td align="center"></td> * </tr> * <tr> - * <td align="center">YUV_420_888</td> + * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td> * <td align="center">all output sizes available for JPEG, up to the maximum video size</td> * <td align="center">LIMITED</td> * <td align="center"></td> * </tr> * <tr> - * <td align="center">IMPLEMENTATION_DEFINED</td> + * <td align="center">{@link android.graphics.ImageFormat#PRIVATE }</td> * <td align="center">same as YUV_420_888</td> * <td align="center">Any</td> * <td align="center"></td> * </tr> * </tbody> * </table> - * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} for additional - * mandatory stream configurations on a per-capability basis.</p> + * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} and {@link android.hardware.camera2.CameraDevice#createCaptureSession } for additional mandatory + * stream configurations on a per-capability basis.</p> * <p>This key is available on all devices.</p> * * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL @@ -1973,8 +1970,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Attempting to use frame durations beyond the maximum will result in the frame * duration being clipped to the maximum. See that control for a full definition of frame * durations.</p> - * <p>Refer to StreamConfigurationMap#getOutputMinFrameDuration(int,Size) for the minimum - * frame duration values.</p> + * <p>Refer to {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration } + * for the minimum frame duration values.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * For FULL capability devices @@ -2634,6 +2631,41 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Integer>("android.sync.maxLatency", int.class); /** + * <p>The maximal camera capture pipeline stall (in unit of frame count) introduced by a + * reprocess capture request.</p> + * <p>The key describes the maximal interference that one reprocess (input) request + * can introduce to the camera simultaneous streaming of regular (output) capture + * requests, including repeating requests.</p> + * <p>When a reprocessing capture request is submitted while a camera output repeating request + * (e.g. preview) is being served by the camera device, it may preempt the camera capture + * pipeline for at least one frame duration so that the camera device is unable to process + * the following capture request in time for the next sensor start of exposure boundary. + * When this happens, the application may observe a capture time gap (longer than one frame + * duration) between adjacent capture output frames, which usually exhibits as preview + * glitch if the repeating request output targets include a preview surface. This key gives + * the worst-case number of frame stall introduced by one reprocess request with any kind of + * formats/sizes combination.</p> + * <p>If this key reports 0, it means a reprocess request doesn't introduce any glitch to the + * ongoing camera repeating request outputs, as if this reprocess request is never issued.</p> + * <p>This key is supported if the camera device supports OPAQUE or YUV reprocessing ( + * i.e. {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains OPAQUE_REPROCESSING or + * YUV_REPROCESSING).</p> + * <p><b>Units</b>: Number of frames.</p> + * <p><b>Range of valid values:</b><br> + * <= 4</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + @PublicKey + public static final Key<Integer> REPROCESS_MAX_CAPTURE_STALL = + new Key<Integer>("android.reprocess.maxCaptureStall", int.class); + + /** * <p>The available depth dataspace stream * configurations that this camera device supports * (i.e. format, width, height, output/input stream).</p> @@ -2672,8 +2704,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and * android.scaler.availableStallDurations for more details about * calculating the max frame rate.</p> - * <p>(Keep in sync with - * StreamConfigurationMap#getOutputMinFrameDuration)</p> + * <p>(Keep in sync with {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration })</p> * <p><b>Units</b>: (format, width, height, ns) x n</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Limited capability</b> - @@ -2689,7 +2720,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>This lists the maximum stall duration for each - * format/size combination for depth streams.</p> + * output format/size combination for depth streams.</p> * <p>A stall duration is how much extra time would get added * to the normal minimum frame duration for a repeating request * that has streams with non-zero stall.</p> diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 51b326b..e9564b3 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -54,6 +54,7 @@ public abstract class CameraDevice implements AutoCloseable { * means that high frame rate is given priority over the highest-quality * post-processing. These requests would normally be used with the * {@link CameraCaptureSession#setRepeatingRequest} method. + * This template is guaranteed to be supported on all camera devices. * * @see #createCaptureRequest */ @@ -63,6 +64,7 @@ public abstract class CameraDevice implements AutoCloseable { * Create a request suitable for still image capture. Specifically, this * means prioritizing image quality over frame rate. These requests would * commonly be used with the {@link CameraCaptureSession#capture} method. + * This template is guaranteed to be supported on all camera devices. * * @see #createCaptureRequest */ @@ -73,6 +75,7 @@ public abstract class CameraDevice implements AutoCloseable { * that a stable frame rate is used, and post-processing is set for * recording quality. These requests would commonly be used with the * {@link CameraCaptureSession#setRepeatingRequest} method. + * This template is guaranteed to be supported on all camera devices. * * @see #createCaptureRequest */ @@ -84,6 +87,9 @@ public abstract class CameraDevice implements AutoCloseable { * disrupting the ongoing recording. These requests would commonly be used * with the {@link CameraCaptureSession#capture} method while a request based on * {@link #TEMPLATE_RECORD} is is in use with {@link CameraCaptureSession#setRepeatingRequest}. + * This template is guaranteed to be supported on all camera devices except + * legacy devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL} + * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}) * * @see #createCaptureRequest */ @@ -93,6 +99,11 @@ public abstract class CameraDevice implements AutoCloseable { * Create a request suitable for zero shutter lag still capture. This means * means maximizing image quality without compromising preview frame rate. * AE/AWB/AF should be on auto mode. + * This template is guaranteed to be supported on camera devices that support the + * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING OPAQUE_REPROCESSING} + * capability or the + * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING YUV_REPROCESSING} + * capability. * * @see #createCaptureRequest */ @@ -105,6 +116,9 @@ public abstract class CameraDevice implements AutoCloseable { * quality. The manual capture parameters (exposure, sensitivity, and so on) * are set to reasonable defaults, but should be overriden by the * application depending on the intended use case. + * This template is guaranteed to be supported on camera devices that support the + * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR MANUAL_SENSOR} + * capability. * * @see #createCaptureRequest */ @@ -473,12 +487,14 @@ public abstract class CameraDevice implements AutoCloseable { * settings as desired, instead.</p> * * @param templateType An enumeration selecting the use case for this - * request; one of the CameraDevice.TEMPLATE_ values. + * request; one of the CameraDevice.TEMPLATE_ values. Not all template + * types are supported on every device. See the documentation for each + * template type for details. * @return a builder for a capture request, initialized with default * settings for that template, and no output streams * - * @throws IllegalArgumentException if the templateType is not in the list - * of supported templates. + * @throws IllegalArgumentException if the templateType is not supported by + * this device. * @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 @@ -564,7 +580,8 @@ public abstract class CameraDevice implements AutoCloseable { * indicating that the camera device is in use already. * * <p> - * This error can be produced when opening the camera fails. + * This error can be produced when opening the camera fails due to the camera + * being used by a higher-priority camera API client. * </p> * * @see #onError @@ -662,7 +679,7 @@ public abstract class CameraDevice implements AutoCloseable { * {@link CameraAccessException}. The disconnection could be due to a * change in security policy or permissions; the physical disconnection * of a removable camera device; or the camera being needed for a - * higher-priority use case.</p> + * higher-priority camera API client.</p> * * <p>There may still be capture callbacks that are invoked * after this method is called, or new image buffers that are delivered @@ -672,8 +689,9 @@ public abstract class CameraDevice implements AutoCloseable { * about the disconnection.</p> * * <p>You should clean up the camera with {@link CameraDevice#close} after - * this happens, as it is not recoverable until opening the camera again - * after it becomes {@link CameraManager.AvailabilityCallback#onCameraAvailable available}. + * this happens, as it is not recoverable until the camera can be opened + * again. For most use cases, this will be when the camera again becomes + * {@link CameraManager.AvailabilityCallback#onCameraAvailable available}. * </p> * * @param camera the device that has been disconnected diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 1a00a05..9327f00 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -77,8 +77,8 @@ public final class CameraManager { } /** - * Return the list of currently connected camera devices by - * identifier. + * Return the list of currently connected camera devices by identifier, including + * cameras that may be in use by other camera API clients. * * <p>Non-removable cameras use integers starting at 0 for their * identifiers, while removable cameras have a unique identifier for each @@ -103,6 +103,11 @@ public final class CameraManager { * <p>The first time a callback is registered, it is immediately called * with the availability status of all currently known camera devices.</p> * + * <p>{@link AvailabilityCallback#onCameraUnavailable(String)} will be called whenever a camera + * device is opened by any camera API client. As of API level 23, other camera API clients may + * still be able to open such a camera device, evicting the existing client if they have higher + * priority than the existing client of a camera device. See open() for more details.</p> + * * <p>Since this callback will be registered with the camera service, remember to unregister it * once it is no longer needed; otherwise the callback will continue to receive events * indefinitely and it may prevent other resources from being released. Specifically, the @@ -259,14 +264,14 @@ public final class CameraManager { } /** - * Helper for openning a connection to a camera with the given ID. + * Helper for opening a connection to a camera with the given ID. * * @param cameraId The unique identifier of the camera device to open * @param callback The callback for the camera. Must not be null. * @param handler The handler to invoke the callback on. Must not be null. * * @throws CameraAccessException if the camera is disabled by device policy, - * or too many camera devices are already open, or the cameraId does not match + * too many camera devices are already open, or the cameraId does not match * any currently available camera device. * * @throws SecurityException if the application does not have permission to @@ -309,7 +314,7 @@ public final class CameraManager { "Camera service is currently unavailable"); } cameraService.connectDevice(callbacks, id, - mContext.getPackageName(), USE_CALLING_UID, holder); + mContext.getOpPackageName(), USE_CALLING_UID, holder); cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); } else { // Use legacy camera implementation for HAL1 devices @@ -330,7 +335,8 @@ public final class CameraManager { deviceImpl.setRemoteFailure(e); if (e.getReason() == CameraAccessException.CAMERA_DISABLED || - e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) { + e.getReason() == CameraAccessException.CAMERA_DISCONNECTED || + e.getReason() == CameraAccessException.CAMERA_IN_USE) { // Per API docs, these failures call onError and throw throw e.asChecked(); } @@ -369,7 +375,19 @@ public final class CameraManager { * <p>Use {@link #getCameraIdList} to get the list of available camera * devices. Note that even if an id is listed, open may fail if the device * is disconnected between the calls to {@link #getCameraIdList} and - * {@link #openCamera}.</p> + * {@link #openCamera}, or if a higher-priority camera API client begins using the + * camera device.</p> + * + * <p>As of API level 23, devices for which the + * {@link AvailabilityCallback#onCameraUnavailable(String)} callback has been called due to the + * device being in use by a lower-priority, background camera API client can still potentially + * be opened by calling this method when the calling camera API client has a higher priority + * than the current camera API client using this device. In general, if the top, foreground + * activity is running within your application process, your process will be given the highest + * priority when accessing the camera, and this method will succeed even if the camera device is + * in use by another camera API client. Any lower-priority application that loses control of the + * camera in this way will receive an + * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.</p> * * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up @@ -401,7 +419,7 @@ public final class CameraManager { * {@code null} to use the current thread's {@link android.os.Looper looper}. * * @throws CameraAccessException if the camera is disabled by device policy, - * or the camera has become or was disconnected. + * has been disconnected, or is being used by a higher-priority camera API client. * * @throws IllegalArgumentException if cameraId or the callback was null, * or the cameraId does not match any currently or previously available @@ -477,8 +495,7 @@ public final class CameraManager { } /** - * A callback for camera devices becoming available or - * unavailable to open. + * A callback for camera devices becoming available or unavailable to open. * * <p>Cameras become available when they are no longer in use, or when a new * removable camera is connected. They become unavailable when some diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index e3f1d73..ca9439b 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -461,22 +461,21 @@ public abstract class CameraMetadata<TKey> { * <p>The camera device supports the Zero Shutter Lag reprocessing use case.</p> * <ul> * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li> - * <li>ImageFormat#PRIVATE is supported as an output/input format, that is, - * ImageFormat#PRIVATE is included in the lists of formats returned by - * StreamConfigurationMap#getInputFormats and - * StreamConfigurationMap#getOutputFormats.</li> - * <li>StreamConfigurationMap#getValidOutputFormatsForInput returns non empty int[] for - * each supported input format returned by StreamConfigurationMap#getInputFormats.</li> - * <li>Each size returned by StreamConfigurationMap#getInputSizes(ImageFormat#PRIVATE) - * is also included in StreamConfigurationMap#getOutputSizes(ImageFormat#PRIVATE)</li> - * <li>Using ImageFormat#PRIVATE does not cause a frame rate drop - * relative to the sensor's maximum capture rate (at that - * resolution).</li> - * <li>ImageFormat#PRIVATE will be reprocessable into both YUV_420_888 - * and JPEG formats.</li> + * <li>{@link android.graphics.ImageFormat#PRIVATE } is supported as an output/input format, + * that is, {@link android.graphics.ImageFormat#PRIVATE } is included in the lists of + * formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li> + * <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput } + * returns non empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li> + * <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.PRIVATE)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.PRIVATE)}</li> + * <li>Using {@link android.graphics.ImageFormat#PRIVATE } does not cause a frame rate drop + * relative to the sensor's maximum capture rate (at that resolution).</li> + * <li>{@link android.graphics.ImageFormat#PRIVATE } will be reprocessable into both + * {@link android.graphics.ImageFormat#YUV_420_888 } and + * {@link android.graphics.ImageFormat#JPEG } formats.</li> * <li>The maximum available resolution for OPAQUE streams * (both input/output) will match the maximum available * resolution of JPEG streams.</li> + * <li>Static metadata {@link CameraCharacteristics#REPROCESS_MAX_CAPTURE_STALL android.reprocess.maxCaptureStall}.</li> * <li>Only below controls are effective for reprocessing requests and * will be present in capture results, other controls in reprocess * requests will be ignored by the camera device.<ul> @@ -489,6 +488,7 @@ public abstract class CameraMetadata<TKey> { * * @see CaptureRequest#EDGE_MODE * @see CaptureRequest#NOISE_REDUCTION_MODE + * @see CameraCharacteristics#REPROCESS_MAX_CAPTURE_STALL * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ @@ -569,25 +569,25 @@ public abstract class CameraMetadata<TKey> { * following:</p> * <ul> * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li> - * <li>YUV_420_888 is supported as an output/input format, that is, + * <li>{@link android.graphics.ImageFormat#YUV_420_888 } is supported as an output/input format, that is, * YUV_420_888 is included in the lists of formats returned by - * StreamConfigurationMap#getInputFormats and - * StreamConfigurationMap#getOutputFormats.</li> - * <li>StreamConfigurationMap#getValidOutputFormatsForInput returns non empty int[] for - * each supported input format returned by StreamConfigurationMap#getInputFormats.</li> - * <li>Each size returned by StreamConfigurationMap#getInputSizes(YUV_420_888) - * is also included in StreamConfigurationMap#getOutputSizes(YUV_420_888)</li> - * <li>Using YUV_420_888 does not cause a frame rate drop + * {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li> + * <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput } + * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li> + * <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(YUV_420_888)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(YUV_420_888)}</li> + * <li>Using {@link android.graphics.ImageFormat#YUV_420_888 } does not cause a frame rate drop * relative to the sensor's maximum capture rate (at that resolution).</li> - * <li>YUV_420_888 will be reprocessable into both YUV_420_888 - * and JPEG formats.</li> - * <li>The maximum available resolution for YUV_420_888 streams - * (both input/output) will match the maximum available - * resolution of JPEG streams.</li> - * <li>Only the below controls are effective for reprocessing requests and will be - * present in capture results. The reprocess requests are from the original capture - * results that are assocaited with the intermidate YUV_420_888 output buffers. - * All other controls in the reprocess requests will be ignored by the camera device.<ul> + * <li>{@link android.graphics.ImageFormat#YUV_420_888 } will be reprocessable into both + * {@link android.graphics.ImageFormat#YUV_420_888 } and {@link android.graphics.ImageFormat#JPEG } formats.</li> + * <li>The maximum available resolution for {@link android.graphics.ImageFormat#YUV_420_888 } streams (both input/output) will match the + * maximum available resolution of {@link android.graphics.ImageFormat#JPEG } streams.</li> + * <li>Static metadata {@link CameraCharacteristics#REPROCESS_MAX_CAPTURE_STALL android.reprocess.maxCaptureStall}.</li> + * <li>Only the below controls are effective for reprocessing requests and will be present + * in capture results. The reprocess requests are from the original capture results that + * are associated with the intermediate {@link android.graphics.ImageFormat#YUV_420_888 } + * output buffers. All other controls in the reprocess requests will be ignored by the + * camera device.<ul> * <li>android.jpeg.*</li> * <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li> * <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li> @@ -599,6 +599,7 @@ public abstract class CameraMetadata<TKey> { * @see CaptureRequest#EDGE_MODE * @see CaptureRequest#NOISE_REDUCTION_MODE * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR + * @see CameraCharacteristics#REPROCESS_MAX_CAPTURE_STALL * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ @@ -608,11 +609,13 @@ public abstract class CameraMetadata<TKey> { * <p>The camera device can produce depth measurements from its field of view.</p> * <p>This capability requires the camera device to support the following:</p> * <ul> - * <li>DEPTH16 is supported as an output format.</li> - * <li>DEPTH_POINT_CLOUD is optionally supported as an output format.</li> - * <li>This camera device, and all camera devices with the same android.lens.info.facing, - * will list the following calibration entries in both CameraCharacteristics and - * CaptureResults:<ul> + * <li>{@link android.graphics.ImageFormat#DEPTH16 } is supported as an output format.</li> + * <li>{@link android.graphics.ImageFormat#DEPTH_POINT_CLOUD } is optionally supported as an + * output format.</li> + * <li>This camera device, and all camera devices with the same {@link CameraCharacteristics#LENS_FACING android.lens.facing}, + * will list the following calibration entries in both + * {@link android.hardware.camera2.CameraCharacteristics } and + * {@link android.hardware.camera2.CaptureResult }:<ul> * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li> * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li> * <li>android.lens.intrinsicCalibration</li> @@ -627,13 +630,14 @@ public abstract class CameraMetadata<TKey> { * <p>Generally, depth output operates at a slower frame rate than standard color capture, * so the DEPTH16 and DEPTH_POINT_CLOUD formats will commonly have a stall duration that * should be accounted for (see - * android.hardware.camera2.StreamConfigurationMap#getOutputStallDuration). On a device - * that supports both depth and color-based output, to enable smooth preview, using a - * repeating burst is recommended, where a depth-output target is only included once - * every N frames, where N is the ratio between preview output rate and depth output + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }). + * On a device that supports both depth and color-based output, to enable smooth preview, + * using a repeating burst is recommended, where a depth-output target is only included + * once every N frames, where N is the ratio between preview output rate and depth output * rate, including depth stall time.</p> * * @see CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE + * @see CameraCharacteristics#LENS_FACING * @see CameraCharacteristics#LENS_POSE_ROTATION * @see CameraCharacteristics#LENS_POSE_TRANSLATION * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES @@ -707,7 +711,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as - * android.os.SystemClock#elapsedRealtimeNanos(), + * {@link android.os.SystemClock#elapsedRealtimeNanos }, * and they can be compared to other timestamps using that base.</p> * * @see CaptureResult#SENSOR_TIMESTAMP @@ -866,7 +870,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>Every frame has the requests immediately applied.</p> * <p>Furthermore for all results, - * <code>android.sync.frameNumber == CaptureResult#getFrameNumber()</code></p> + * <code>android.sync.frameNumber == {@link android.hardware.camera2.CaptureResult#getFrameNumber }</code></p> * <p>Changing controls over multiple requests one after another will * produce results that have those controls applied atomically * each frame.</p> diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 19d17b1..ab6ce91 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1275,8 +1275,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>This control (except for MANUAL) is only effective if * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p> * <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} - * contains OPAQUE_REPROCESSING. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} - * contains MANUAL_SENSOR. Other intent values are always supported.</p> + * contains OPAQUE_REPROCESSING or YUV_REPROCESSING. MANUAL will be supported if + * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR. Other intent values are + * always supported.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li> @@ -2039,8 +2040,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * cannot process more than 1 capture at a time.</li> * </ul> * <p>The necessary information for the application, given the model above, - * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field - * using StreamConfigurationMap#getOutputMinFrameDuration(int, Size). + * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field using + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }. * These are used to determine the maximum frame rate / minimum frame * duration that is possible for a given stream configuration.</p> * <p>Specifically, the application can use the following rules to @@ -2049,21 +2050,19 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <ol> * <li>Let the set of currently configured input/output streams * be called <code>S</code>.</li> - * <li>Find the minimum frame durations for each stream in <code>S</code>, by - * looking it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using - * StreamConfigurationMap#getOutputMinFrameDuration(int, Size) (with - * its respective size/format). Let this set of frame durations be called - * <code>F</code>.</li> + * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking + * it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration } + * (with its respective size/format). Let this set of frame durations be + * called <code>F</code>.</li> * <li>For any given request <code>R</code>, the minimum frame duration allowed * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams * used in <code>R</code> be called <code>S_r</code>.</li> * </ol> - * <p>If none of the streams in <code>S_r</code> have a stall time (listed in - * StreamConfigurationMap#getOutputStallDuration(int,Size) using its - * respective size/format), then the frame duration in - * <code>F</code> determines the steady state frame rate that the application will - * get if it uses <code>R</code> as a repeating request. Let this special kind - * of request be called <code>Rsimple</code>.</p> + * <p>If none of the streams in <code>S_r</code> have a stall time (listed in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } + * using its respective size/format), then the frame duration in <code>F</code> + * determines the steady state frame rate that the application will get + * if it uses <code>R</code> as a repeating request. Let this special kind of + * request be called <code>Rsimple</code>.</p> * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved * by a single capture of a new request <code>Rstall</code> (which has at least * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the @@ -2071,7 +2070,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * if all buffers from the previous <code>Rstall</code> have already been * delivered.</p> * <p>For more details about stalling, see - * StreamConfigurationMap#getOutputStallDuration(int,Size).</p> + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> * <p><b>Units</b>: Nanoseconds</p> @@ -2647,8 +2646,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p><b>Range of valid values:</b><br> * >= 1.0</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * * @see CaptureRequest#EDGE_MODE + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CaptureRequest#NOISE_REDUCTION_MODE * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index ef5d75c..3dc8970 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -1698,8 +1698,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>This control (except for MANUAL) is only effective if * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p> * <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} - * contains OPAQUE_REPROCESSING. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} - * contains MANUAL_SENSOR. Other intent values are always supported.</p> + * contains OPAQUE_REPROCESSING or YUV_REPROCESSING. MANUAL will be supported if + * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR. Other intent values are + * always supported.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li> @@ -2885,8 +2886,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * cannot process more than 1 capture at a time.</li> * </ul> * <p>The necessary information for the application, given the model above, - * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field - * using StreamConfigurationMap#getOutputMinFrameDuration(int, Size). + * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field using + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }. * These are used to determine the maximum frame rate / minimum frame * duration that is possible for a given stream configuration.</p> * <p>Specifically, the application can use the following rules to @@ -2895,21 +2896,19 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <ol> * <li>Let the set of currently configured input/output streams * be called <code>S</code>.</li> - * <li>Find the minimum frame durations for each stream in <code>S</code>, by - * looking it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using - * StreamConfigurationMap#getOutputMinFrameDuration(int, Size) (with - * its respective size/format). Let this set of frame durations be called - * <code>F</code>.</li> + * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking + * it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration } + * (with its respective size/format). Let this set of frame durations be + * called <code>F</code>.</li> * <li>For any given request <code>R</code>, the minimum frame duration allowed * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams * used in <code>R</code> be called <code>S_r</code>.</li> * </ol> - * <p>If none of the streams in <code>S_r</code> have a stall time (listed in - * StreamConfigurationMap#getOutputStallDuration(int,Size) using its - * respective size/format), then the frame duration in - * <code>F</code> determines the steady state frame rate that the application will - * get if it uses <code>R</code> as a repeating request. Let this special kind - * of request be called <code>Rsimple</code>.</p> + * <p>If none of the streams in <code>S_r</code> have a stall time (listed in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } + * using its respective size/format), then the frame duration in <code>F</code> + * determines the steady state frame rate that the application will get + * if it uses <code>R</code> as a repeating request. Let this special kind of + * request be called <code>Rsimple</code>.</p> * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved * by a single capture of a new request <code>Rstall</code> (which has at least * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the @@ -2917,7 +2916,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * if all buffers from the previous <code>Rstall</code> have already been * delivered.</p> * <p>For more details about stalling, see - * StreamConfigurationMap#getOutputStallDuration(int,Size).</p> + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> * <p><b>Units</b>: Nanoseconds</p> @@ -2979,11 +2978,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * and are monotonically increasing. They can be compared with the * timestamps for other captures from the same camera device, but are * not guaranteed to be comparable to any other time source.</p> - * <p>When {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE android.sensor.info.timestampSource} <code>==</code> REALTIME, - * the timestamps measure time in the same timebase as - * android.os.SystemClock#elapsedRealtimeNanos(), and they can be - * compared to other timestamps from other subsystems that are using - * that base.</p> + * <p>When {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE android.sensor.info.timestampSource} <code>==</code> REALTIME, the + * timestamps measure time in the same timebase as {@link android.os.SystemClock#elapsedRealtimeNanos }, and they can + * be compared to other timestamps from other subsystems that + * are using that base.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * > 0</p> @@ -3141,7 +3139,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * >= 0 and < - * StreamConfigurationMap#getOutputMinFrameDuration(int, Size).</p> + * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Limited capability</b> - * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the @@ -3966,8 +3964,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p><b>Range of valid values:</b><br> * >= 1.0</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * * @see CaptureRequest#EDGE_MODE + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CaptureRequest#NOISE_REDUCTION_MODE * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index abe26ea..edad00f 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -202,6 +202,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { private static final int CAMERA_IDLE = 1; private static final int CAPTURE_STARTED = 2; private static final int RESULT_RECEIVED = 3; + private static final int PREPARED = 4; private final HandlerThread mHandlerThread; private Handler mHandler; @@ -253,7 +254,9 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { @Override public void onPrepared(int streamId) { - // TODO + Message msg = getHandler().obtainMessage(PREPARED, + /*arg1*/ streamId, /*arg2*/ 0); + getHandler().sendMessage(msg); } @Override @@ -301,6 +304,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { mCallbacks.onResultReceived(result, resultExtras); break; } + case PREPARED: { + int streamId = msg.arg1; + mCallbacks.onPrepared(streamId); + break; + } default: throw new IllegalArgumentException( "Unknown callback message " + msg.what); @@ -631,7 +639,9 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { return CameraBinderDecorator.ENODEV; } - // TODO: Implement and fire callback + // LEGACY doesn't support actual prepare, just signal success right away + mCameraCallbacks.onPrepared(streamId); + return CameraBinderDecorator.NO_ERROR; } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index adab9be..02793f1 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -182,6 +182,10 @@ public abstract class DisplayManagerInternal { // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter). public float screenAutoBrightnessAdjustment; + // Set to true if screenBrightness and screenAutoBrightnessAdjustment were both + // set by the user as opposed to being programmatically controlled by apps. + public boolean brightnessSetByUser; + // If true, enables automatic brightness control. public boolean useAutoBrightness; @@ -229,6 +233,7 @@ public abstract class DisplayManagerInternal { useProximitySensor = other.useProximitySensor; screenBrightness = other.screenBrightness; screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment; + brightnessSetByUser = other.brightnessSetByUser; useAutoBrightness = other.useAutoBrightness; blockScreenOn = other.blockScreenOn; lowPowerMode = other.lowPowerMode; @@ -249,6 +254,7 @@ public abstract class DisplayManagerInternal { && useProximitySensor == other.useProximitySensor && screenBrightness == other.screenBrightness && screenAutoBrightnessAdjustment == other.screenAutoBrightnessAdjustment + && brightnessSetByUser == other.brightnessSetByUser && useAutoBrightness == other.useAutoBrightness && blockScreenOn == other.blockScreenOn && lowPowerMode == other.lowPowerMode @@ -268,6 +274,7 @@ public abstract class DisplayManagerInternal { + ", useProximitySensor=" + useProximitySensor + ", screenBrightness=" + screenBrightness + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment + + ", brightnessSetByUser=" + brightnessSetByUser + ", useAutoBrightness=" + useAutoBrightness + ", blockScreenOn=" + blockScreenOn + ", lowPowerMode=" + lowPowerMode diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 2257b0a..cf96145 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -42,6 +42,7 @@ import java.util.HashMap; import java.util.List; import javax.crypto.Cipher; +import javax.crypto.Mac; /** * A class that coordinates access to the fingerprint hardware. @@ -195,18 +196,26 @@ public class FingerprintManager { /** * A wrapper class for the crypto objects supported by FingerprintManager. Currently the - * framework supports {@link Signature} and {@link Cipher} objects. + * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. */ public static class CryptoObject { - public CryptoObject(Signature signature) { + public CryptoObject(@NonNull Signature signature) { mSignature = signature; mCipher = null; + mMac = null; } - public CryptoObject(Cipher cipher) { + public CryptoObject(@NonNull Cipher cipher) { mCipher = cipher; mSignature = null; + mMac = null; + } + + public CryptoObject(@NonNull Mac mac) { + mMac = mac; + mCipher = null; + mSignature = null; } /** @@ -222,6 +231,12 @@ public class FingerprintManager { public Cipher getCipher() { return mCipher; } /** + * Get {@link Mac} object. + * @return {@link Mac} object or null if this doesn't contain one. + */ + public Mac getMac() { return mMac; } + + /** * @hide * @return the opId associated with this object or 0 if none */ @@ -230,12 +245,15 @@ public class FingerprintManager { return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mSignature); } else if (mCipher != null) { return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCipher); + } else if (mMac != null) { + return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mMac); } return 0; } private final Signature mSignature; private final Cipher mCipher; + private final Mac mMac; }; /** @@ -416,7 +434,8 @@ public class FingerprintManager { mAuthenticationCallback = callback; mCryptoObject = crypto; long sessionId = crypto != null ? crypto.getOpId() : 0; - mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags); + mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, + mContext.getOpPackageName()); } catch (RemoteException e) { Log.w(TAG, "Remote exception while authenticating: ", e); if (callback != null) { @@ -537,7 +556,7 @@ public class FingerprintManager { */ public List<Fingerprint> getEnrolledFingerprints(int userId) { if (mService != null) try { - return mService.getEnrolledFingerprints(userId); + return mService.getEnrolledFingerprints(userId, mContext.getOpPackageName()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in getEnrolledFingerprints: ", e); } @@ -561,7 +580,8 @@ public class FingerprintManager { */ public boolean hasEnrolledFingerprints() { if (mService != null) try { - return mService.hasEnrolledFingerprints(UserHandle.myUserId()); + return mService.hasEnrolledFingerprints(UserHandle.myUserId(), + mContext.getOpPackageName()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in getEnrolledFingerprints: ", e); } @@ -577,7 +597,7 @@ public class FingerprintManager { if (mService != null) { try { long deviceId = 0; /* TODO: plumb hardware id to FPMS */ - return mService.isHardwareDetected(deviceId); + return mService.isHardwareDetected(deviceId, mContext.getOpPackageName()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in isFingerprintHardwareDetected(): ", e); } @@ -596,7 +616,7 @@ public class FingerprintManager { public long getAuthenticatorId() { if (mService != null) { try { - return mService.getAuthenticatorId(); + return mService.getAuthenticatorId(mContext.getOpPackageName()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in getAuthenticatorId(): ", e); } @@ -606,7 +626,13 @@ public class FingerprintManager { return 0; } - private Handler mHandler = new Handler() { + private Handler mHandler; + + private class MyHandler extends Handler { + private MyHandler(Context context) { + super(context.getMainLooper()); + } + public void handleMessage(android.os.Message msg) { switch(msg.what) { case MSG_ENROLL_RESULT: @@ -691,6 +717,7 @@ public class FingerprintManager { if (mService == null) { Slog.v(TAG, "FingerprintManagerService was null"); } + mHandler = new MyHandler(context); } private int getCurrentUserId() { @@ -718,7 +745,7 @@ public class FingerprintManager { private void cancelAuthentication(CryptoObject cryptoObject) { if (mService != null) try { - mService.cancelAuthentication(mToken); + mService.cancelAuthentication(mToken, mContext.getOpPackageName()); } catch (RemoteException e) { if (DEBUG) Log.w(TAG, "Remote exception while canceling enrollment"); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index c5ec08c..0484806 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -27,10 +27,10 @@ import java.util.List; interface IFingerprintService { // Authenticate the given sessionId with a fingerprint void authenticate(IBinder token, long sessionId, int groupId, - IFingerprintServiceReceiver receiver, int flags); + IFingerprintServiceReceiver receiver, int flags, String opPackageName); // Cancel authentication for the given sessionId - void cancelAuthentication(IBinder token); + void cancelAuthentication(IBinder token, String opPackageName); // Start fingerprint enrollment void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, @@ -46,16 +46,16 @@ interface IFingerprintService { void rename(int fingerId, int groupId, String name); // Get a list of enrolled fingerprints in the given group. - List<Fingerprint> getEnrolledFingerprints(int groupId); + List<Fingerprint> getEnrolledFingerprints(int groupId, String opPackageName); // Determine if HAL is loaded and ready - boolean isHardwareDetected(long deviceId); + boolean isHardwareDetected(long deviceId, String opPackageName); // Get a pre-enrollment authentication token long preEnroll(IBinder token); // Determine if a user has at least one enrolled fingerprint - boolean hasEnrolledFingerprints(int groupId); + boolean hasEnrolledFingerprints(int groupId, String opPackageName); // Gets the number of hardware devices // int getHardwareDeviceCount(); @@ -64,5 +64,5 @@ interface IFingerprintService { // long getHardwareDevice(int i); // Gets the authenticator ID for fingerprint - long getAuthenticatorId(); + long getAuthenticatorId(String opPackageName); } diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java index 1a42319..410d550 100644 --- a/core/java/android/hardware/usb/UsbDevice.java +++ b/core/java/android/hardware/usb/UsbDevice.java @@ -45,6 +45,7 @@ public class UsbDevice implements Parcelable { private final String mName; private final String mManufacturerName; private final String mProductName; + private final String mVersion; private final String mSerialNumber; private final int mVendorId; private final int mProductId; @@ -62,7 +63,7 @@ public class UsbDevice implements Parcelable { */ public UsbDevice(String name, int vendorId, int productId, int Class, int subClass, int protocol, - String manufacturerName, String productName, String serialNumber) { + String manufacturerName, String productName, String version, String serialNumber) { mName = name; mVendorId = vendorId; mProductId = productId; @@ -71,6 +72,7 @@ public class UsbDevice implements Parcelable { mProtocol = protocol; mManufacturerName = manufacturerName; mProductName = productName; + mVersion = version; mSerialNumber = serialNumber; } @@ -104,6 +106,15 @@ public class UsbDevice implements Parcelable { } /** + * Returns the version number of the device. + * + * @return the device version + */ + public String getVersion() { + return mVersion; + } + + /** * Returns the serial number of the device. * * @return the serial number name @@ -263,7 +274,7 @@ public class UsbDevice implements Parcelable { ",mVendorId=" + mVendorId + ",mProductId=" + mProductId + ",mClass=" + mClass + ",mSubclass=" + mSubclass + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + ",mProductName=" + mProductName + - ",mSerialNumber=" + mSerialNumber + ",mConfigurations=["); + ",mVersion=" + mVersion + ",mSerialNumber=" + mSerialNumber + ",mConfigurations=["); for (int i = 0; i < mConfigurations.length; i++) { builder.append("\n"); builder.append(mConfigurations[i].toString()); @@ -283,10 +294,11 @@ public class UsbDevice implements Parcelable { int protocol = in.readInt(); String manufacturerName = in.readString(); String productName = in.readString(); + String version = in.readString(); String serialNumber = in.readString(); Parcelable[] configurations = in.readParcelableArray(UsbInterface.class.getClassLoader()); UsbDevice device = new UsbDevice(name, vendorId, productId, clasz, subClass, protocol, - manufacturerName, productName, serialNumber); + manufacturerName, productName, version, serialNumber); device.setConfigurations(configurations); return device; } @@ -309,6 +321,7 @@ public class UsbDevice implements Parcelable { parcel.writeInt(mProtocol); parcel.writeString(mManufacturerName); parcel.writeString(mProductName); + parcel.writeString(mVersion); parcel.writeString(mSerialNumber); parcel.writeParcelableArray(mConfigurations, 0); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index c531e7e..d8c3361 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -341,7 +341,8 @@ public class ConnectivityManager { * one. This is used by applications needing to talk to the carrier's * Multimedia Messaging Service servers. * - * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * @deprecated Applications should instead use + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability. */ public static final int TYPE_MOBILE_MMS = 2; @@ -351,7 +352,8 @@ public class ConnectivityManager { * one. This is used by applications needing to talk to the carrier's * Secure User Plane Location servers for help locating the device. * - * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * @deprecated Applications should instead use + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability. */ public static final int TYPE_MOBILE_SUPL = 3; @@ -367,7 +369,8 @@ public class ConnectivityManager { * same network interface as {@link #TYPE_MOBILE} but the routing setup * is different. * - * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * @deprecated Applications should instead use + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that * uses the {@link NetworkCapabilities#TRANSPORT_CELLULAR} transport. */ public static final int TYPE_MOBILE_HIPRI = 5; @@ -910,7 +913,8 @@ public class ConnectivityManager { * implementation+feature combination, except that the value {@code -1} * always indicates failure. * - * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. + * @deprecated Deprecated in favor of the cleaner + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} API. * @removed */ public int startUsingNetworkFeature(int networkType, String feature) { @@ -958,7 +962,7 @@ public class ConnectivityManager { * implementation+feature combination, except that the value {@code -1} * always indicates failure. * - * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. + * @deprecated Deprecated in favor of the cleaner {@link unregisterNetworkCallback} API. * @removed */ public int stopUsingNetworkFeature(int networkType, String feature) { @@ -1236,8 +1240,9 @@ public class ConnectivityManager { * @param hostAddress the IP address of the host to which the route is desired * @return {@code true} on success, {@code false} on failure * - * @deprecated Deprecated in favor of the {@link #requestNetwork}, - * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} api. + * @deprecated Deprecated in favor of the + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API. * @removed */ public boolean requestRouteToHost(int networkType, int hostAddress) { @@ -1256,7 +1261,7 @@ public class ConnectivityManager { * @return {@code true} on success, {@code false} on failure * @hide * @deprecated Deprecated in favor of the {@link #requestNetwork} and - * {@link #bindProcessToNetwork} api. + * {@link #bindProcessToNetwork} API. * @removed */ public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { @@ -2144,14 +2149,22 @@ public class ConnectivityManager { public static final int CANCELED = 8; /** - * @hide - * Called whenever the framework connects to a network that it may use to - * satisfy this request + * Called when the framework connects to a new network to evaluate whether it satisfies this + * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable} + * callback. There is no guarantee that this new network will satisfy any requests, or that + * the network will stay connected for longer than the time necessary to evaluate it. + * <p> + * Most applications <b>should not</b> act on this callback, and should instead use + * {@link #onAvailable}. This callback is intended for use by applications that can assist + * the framework in properly evaluating the network — for example, an application that + * can automatically log in to a captive portal without user intervention. + * + * @param network The {@link Network} of the network that is being evaluated. */ public void onPreCheck(Network network) {} /** - * Called when the framework connects and has declared new network ready for use. + * Called when the framework connects and has declared a new network ready for use. * This callback may be called more than once if the {@link Network} that is * satisfying the request changes. * @@ -2251,116 +2264,82 @@ public class ConnectivityManager { @Override public void handleMessage(Message message) { Log.d(TAG, "CM callback handler got msg " + message.what); + NetworkRequest request = (NetworkRequest) getObject(message, NetworkRequest.class); + Network network = (Network) getObject(message, Network.class); switch (message.what) { case CALLBACK_PRECHECK: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - callbacks.onPreCheck((Network)getObject(message, Network.class)); - } else { - Log.e(TAG, "callback not found for PRECHECK message"); + NetworkCallback callback = getCallback(request, "PRECHECK"); + if (callback != null) { + callback.onPreCheck(network); } break; } case CALLBACK_AVAILABLE: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - callbacks.onAvailable((Network)getObject(message, Network.class)); - } else { - Log.e(TAG, "callback not found for AVAILABLE message"); + NetworkCallback callback = getCallback(request, "AVAILABLE"); + if (callback != null) { + callback.onAvailable(network); } break; } case CALLBACK_LOSING: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - callbacks.onLosing((Network)getObject(message, Network.class), - message.arg1); - } else { - Log.e(TAG, "callback not found for LOSING message"); + NetworkCallback callback = getCallback(request, "LOSING"); + if (callback != null) { + callback.onLosing(network, message.arg1); } break; } case CALLBACK_LOST: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - callbacks.onLost((Network)getObject(message, Network.class)); - } else { - Log.e(TAG, "callback not found for LOST message"); + NetworkCallback callback = getCallback(request, "LOST"); + if (callback != null) { + callback.onLost(network); } break; } case CALLBACK_UNAVAIL: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = null; - synchronized(mCallbackMap) { - callbacks = mCallbackMap.get(request); - } - if (callbacks != null) { - callbacks.onUnavailable(); - } else { - Log.e(TAG, "callback not found for UNAVAIL message"); + NetworkCallback callback = getCallback(request, "UNAVAIL"); + if (callback != null) { + callback.onUnavailable(); } break; } case CALLBACK_CAP_CHANGED: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - Network network = (Network)getObject(message, Network.class); + NetworkCallback callback = getCallback(request, "CAP_CHANGED"); + if (callback != null) { NetworkCapabilities cap = (NetworkCapabilities)getObject(message, NetworkCapabilities.class); - callbacks.onCapabilitiesChanged(network, cap); - } else { - Log.e(TAG, "callback not found for CAP_CHANGED message"); + callback.onCapabilitiesChanged(network, cap); } break; } case CALLBACK_IP_CHANGED: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - Network network = (Network)getObject(message, Network.class); + NetworkCallback callback = getCallback(request, "IP_CHANGED"); + if (callback != null) { LinkProperties lp = (LinkProperties)getObject(message, LinkProperties.class); - callbacks.onLinkPropertiesChanged(network, lp); - } else { - Log.e(TAG, "callback not found for IP_CHANGED message"); + callback.onLinkPropertiesChanged(network, lp); } break; } case CALLBACK_RELEASED: { - NetworkRequest req = (NetworkRequest)getObject(message, NetworkRequest.class); - NetworkCallback callbacks = null; + NetworkCallback callback = null; synchronized(mCallbackMap) { - callbacks = mCallbackMap.remove(req); + callback = mCallbackMap.remove(request); } - if (callbacks != null) { + if (callback != null) { synchronized(mRefCount) { if (mRefCount.decrementAndGet() == 0) { getLooper().quit(); } } } else { - Log.e(TAG, "callback not found for CANCELED message"); + Log.e(TAG, "callback not found for RELEASED message"); } break; } case CALLBACK_EXIT: { - Log.d(TAG, "Listener quiting"); + Log.d(TAG, "Listener quitting"); getLooper().quit(); break; } @@ -2374,10 +2353,16 @@ public class ConnectivityManager { private Object getObject(Message msg, Class c) { return msg.getData().getParcelable(c.getSimpleName()); } - private NetworkCallback getCallbacks(NetworkRequest req) { + + private NetworkCallback getCallback(NetworkRequest req, String name) { + NetworkCallback callback; synchronized(mCallbackMap) { - return mCallbackMap.get(req); + callback = mCallbackMap.get(req); + } + if (callback == null) { + Log.e(TAG, "callback not found for " + name + " message"); } + return callback; } } @@ -2586,10 +2571,8 @@ public class ConnectivityManager { * method assumes that the caller has previously called {@link #registerNetworkCallback} to * listen for network changes. * - * @param network{@link Network} specifying which network you're interested. + * @param network {@link Network} specifying which network you're interested. * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. - * - * @hide */ public boolean requestBandwidthUpdate(Network network) { try { @@ -2601,10 +2584,10 @@ public class ConnectivityManager { /** * Unregisters callbacks about and possibly releases networks originating from - * {@link #requestNetwork} and {@link #registerNetworkCallback} calls. If the - * given {@code NetworkCallback} had previously been used with {@code #requestNetwork}, - * any networks that had been connected to only to satisfy that request will be - * disconnected. + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and {@link #registerNetworkCallback} + * calls. If the given {@code NetworkCallback} had previously been used with + * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request + * will be disconnected. * * @param networkCallback The {@link NetworkCallback} used when making the request. */ diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index c722fbc..7f5f377 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -38,8 +38,6 @@ interface INetworkPolicyManager { boolean isUidForeground(int uid); - int[] getPowerSaveAppIdWhitelist(); - void registerListener(INetworkPolicyListener listener); void unregisterListener(INetworkPolicyListener listener); diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 8c8bfab..ab70485 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -37,6 +37,11 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public NetworkCapabilities() { + clearAll(); + mNetworkCapabilities = + (1 << NET_CAPABILITY_NOT_RESTRICTED) | + (1 << NET_CAPABILITY_TRUSTED) | + (1 << NET_CAPABILITY_NOT_VPN); } public NetworkCapabilities(NetworkCapabilities nc) { @@ -50,11 +55,21 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Completely clears the contents of this object, removing even the capabilities that are set + * by default when the object is constructed. + * @hide + */ + public void clearAll() { + mNetworkCapabilities = mTransportTypes = 0; + mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = 0; + mNetworkSpecifier = null; + } + + /** * Represents the network's capabilities. If any are specified they will be satisfied * by any Network that matches all of them. */ - private long mNetworkCapabilities = (1 << NET_CAPABILITY_NOT_RESTRICTED) | - (1 << NET_CAPABILITY_TRUSTED) | (1 << NET_CAPABILITY_NOT_VPN); + private long mNetworkCapabilities; /** * Indicates this is a network that has the ability to reach the diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index bc03637..ecc3fb4 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -41,6 +41,7 @@ import java.util.HashSet; */ public class NetworkPolicyManager { + /* POLICY_* are masks and can be ORed */ /** No specific network policy, use system default. */ public static final int POLICY_NONE = 0x0; /** Reject network usage on metered networks when application in background. */ @@ -48,10 +49,17 @@ public class NetworkPolicyManager { /** Allow network use (metered or not) in the background in battery save mode. */ public static final int POLICY_ALLOW_BACKGROUND_BATTERY_SAVE = 0x2; + /* RULE_* are not masks and they must be exclusive */ /** All network traffic should be allowed. */ public static final int RULE_ALLOW_ALL = 0x0; /** Reject traffic on metered networks. */ public static final int RULE_REJECT_METERED = 0x1; + /** Reject traffic on all networks. */ + public static final int RULE_REJECT_ALL = 0x2; + + public static final int FIREWALL_RULE_DEFAULT = 0; + public static final int FIREWALL_RULE_ALLOW = 1; + public static final int FIREWALL_RULE_DENY = 2; private static final boolean ALLOW_PLATFORM_APP_POLICY = true; @@ -80,7 +88,7 @@ public class NetworkPolicyManager { * Set policy flags for specific UID. * * @param policy {@link #POLICY_NONE} or combination of flags like - * {@link #POLICY_REJECT_METERED_BACKGROUND}, {@link #POLICY_ALLOW_BACKGROUND_BATTERY_SAVE}. + * {@link #POLICY_REJECT_METERED_BACKGROUND} or {@link #POLICY_ALLOW_BACKGROUND_BATTERY_SAVE}. */ public void setUidPolicy(int uid, int policy) { try { @@ -129,14 +137,6 @@ public class NetworkPolicyManager { } } - public int[] getPowerSaveAppIdWhitelist() { - try { - return mService.getPowerSaveAppIdWhitelist(); - } catch (RemoteException e) { - return new int[0]; - } - } - public void registerListener(INetworkPolicyListener listener) { try { mService.registerListener(listener); @@ -330,6 +330,8 @@ public class NetworkPolicyManager { fout.write("["); if ((rules & RULE_REJECT_METERED) != 0) { fout.write("REJECT_METERED"); + } else if ((rules & RULE_REJECT_ALL) != 0) { + fout.write("REJECT_ALL"); } fout.write("]"); } diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index f305b2a..4a8dfbc 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1657,7 +1657,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { /** * Searches the query string for the first value with the given key. * - * <p><strong>Warning:</strong> Prior to Ice Cream Sandwich, this decoded + * <p><strong>Warning:</strong> Prior to Jelly Bean, this decoded * the '+' character as '+' rather than ' '. * * @param key which will be encoded diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 47e8e69..243ddf7 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -16,6 +16,9 @@ package android.os; +import android.annotation.MainThread; +import android.annotation.WorkerThread; + import java.util.ArrayDeque; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; @@ -350,6 +353,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #onPostExecute * @see #publishProgress */ + @WorkerThread protected abstract Result doInBackground(Params... params); /** @@ -358,6 +362,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #onPostExecute * @see #doInBackground */ + @MainThread protected void onPreExecute() { } @@ -374,6 +379,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #onCancelled(Object) */ @SuppressWarnings({"UnusedDeclaration"}) + @MainThread protected void onPostExecute(Result result) { } @@ -387,6 +393,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #doInBackground */ @SuppressWarnings({"UnusedDeclaration"}) + @MainThread protected void onProgressUpdate(Progress... values) { } @@ -405,6 +412,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #isCancelled() */ @SuppressWarnings({"UnusedParameters"}) + @MainThread protected void onCancelled(Result result) { onCancelled(); } @@ -421,6 +429,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #cancel(boolean) * @see #isCancelled() */ + @MainThread protected void onCancelled() { } @@ -535,6 +544,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) * @see #execute(Runnable) */ + @MainThread public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } @@ -572,6 +582,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * * @see #execute(Object[]) */ + @MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { @@ -604,6 +615,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #execute(Object[]) * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) */ + @MainThread public static void execute(Runnable runnable) { sDefaultExecutor.execute(runnable); } @@ -622,6 +634,7 @@ public abstract class AsyncTask<Params, Progress, Result> { * @see #onProgressUpdate * @see #doInBackground */ + @WorkerThread protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 5e9b8c1..74699fd 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -211,8 +211,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } else if (obj instanceof Parcelable[]) { Parcelable[] array = (Parcelable[]) obj; for (int n = array.length - 1; n >= 0; n--) { - if ((array[n].describeContents() - & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) { + Parcelable p = array[n]; + if (p != null && ((p.describeContents() + & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) { fdFound = true; break; } @@ -221,7 +222,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { SparseArray<? extends Parcelable> array = (SparseArray<? extends Parcelable>) obj; for (int n = array.size() - 1; n >= 0; n--) { - if ((array.valueAt(n).describeContents() + Parcelable p = array.valueAt(n); + if (p != null && (p.describeContents() & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) { fdFound = true; break; diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 2eb97f1..8e0584a 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -20,6 +20,7 @@ import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; +import android.text.TextUtils; import android.util.Log; import java.io.File; @@ -242,6 +243,15 @@ public class Environment { return DATA_DIRECTORY; } + /** {@hide} */ + public static File getDataAppDirectory(String volumeUuid) { + if (TextUtils.isEmpty(volumeUuid)) { + return new File("/data/app"); + } else { + return new File("/mnt/expand/" + volumeUuid + "/app"); + } + } + /** * Return the primary external storage directory. This directory may not * currently be accessible if it has been mounted by the user on their @@ -758,7 +768,6 @@ public class Environment { * @hide */ public static File maybeTranslateEmulatedPathToInternal(File path) { - // TODO: bring back this optimization - return path; + return StorageManager.maybeTranslateEmulatedPathToInternal(path); } } diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 931cd3e..021e5e4 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -390,7 +390,7 @@ public class FileUtils { * attacks. */ public static boolean contains(File dir, File file) { - if (file == null) return false; + if (dir == null || file == null) return false; String dirPath = dir.getAbsolutePath(); String filePath = file.getAbsolutePath(); diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl new file mode 100644 index 0000000..3cb29ff --- /dev/null +++ b/core/java/android/os/IDeviceIdleController.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.os; + +/** @hide */ +interface IDeviceIdleController { + void addPowerSaveWhitelistApp(String name); + void removePowerSaveWhitelistApp(String name); + String[] getSystemPowerWhitelist(); + String[] getFullPowerWhitelist(); + int[] getAppIdWhitelist(); +} diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index f93550a..b29e8d0 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -342,7 +342,7 @@ interface INetworkManagementService void setFirewallInterfaceRule(String iface, boolean allow); void setFirewallEgressSourceRule(String addr, boolean allow); void setFirewallEgressDestRule(String addr, int port, boolean allow); - void setFirewallUidRule(int uid, boolean allow); + void setFirewallUidRule(int uid, int rule); /** * Set all packets from users in ranges to go through VPN specified by netId. diff --git a/core/java/android/os/IPermissionController.aidl b/core/java/android/os/IPermissionController.aidl index 73a68f1..5e8590a 100644 --- a/core/java/android/os/IPermissionController.aidl +++ b/core/java/android/os/IPermissionController.aidl @@ -20,4 +20,6 @@ package android.os; /** @hide */ interface IPermissionController { boolean checkPermission(String permission, int pid, int uid); + String[] getPackagesForUid(int uid); + boolean isRuntimePermission(String permission); } diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 8c1f44f..1273772 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -502,7 +502,25 @@ public final class Parcel { * {@SystemApi} */ public final void writeBlob(byte[] b) { - nativeWriteBlob(mNativePtr, b, 0, (b != null) ? b.length : 0); + writeBlob(b, 0, (b != null) ? b.length : 0); + } + + /** + * Write a blob of data into the parcel at the current {@link #dataPosition}, + * growing {@link #dataCapacity} if needed. + * @param b Bytes to place into the parcel. + * @param offset Index of first byte to be written. + * @param len Number of bytes to write. + * {@hide} + * {@SystemApi} + */ + public final void writeBlob(byte[] b, int offset, int len) { + if (b == null) { + writeInt(-1); + return; + } + Arrays.checkOffsetAndCount(b.length, offset, len); + nativeWriteBlob(mNativePtr, b, offset, len); } /** diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 01c9a21..1d9d7d2 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -920,6 +920,14 @@ public final class PowerManager { = "android.os.action.DEVICE_IDLE_MODE_CHANGED"; /** + * @hide Intent that is broadcast when the set of power save whitelist apps has changed. + * This broadcast is only sent to registered receivers. + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_POWER_SAVE_WHITELIST_CHANGED + = "android.os.action.POWER_SAVE_WHITELIST_CHANGED"; + + /** * Intent that is broadcast when the state of {@link #isPowerSaveMode()} is about to change. * This broadcast is only sent to registered receivers. * diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 355ec8c..009649f 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -634,6 +634,9 @@ public class Process { if ((debugFlags & Zygote.DEBUG_ENABLE_JIT) != 0) { argsForZygote.add("--enable-jit"); } + if ((debugFlags & Zygote.DEBUG_GENERATE_CFI) != 0) { + argsForZygote.add("--generate-cfi"); + } if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) { argsForZygote.add("--enable-assert"); } diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 16e0bf7..fcde3f4 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -942,6 +942,24 @@ public interface IMountService extends IInterface { } @Override + public VolumeRecord[] getVolumeRecords(int _flags) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + VolumeRecord[] _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(_flags); + mRemote.transact(Stub.TRANSACTION_getVolumeRecords, _data, _reply, 0); + _reply.readException(); + _result = _reply.createTypedArray(VolumeRecord.CREATOR); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + @Override public void mount(String volId) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); @@ -1033,12 +1051,12 @@ public interface IMountService extends IInterface { } @Override - public void setVolumeNickname(String volId, String nickname) throws RemoteException { + public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(volId); + _data.writeString(fsUuid); _data.writeString(nickname); mRemote.transact(Stub.TRANSACTION_setVolumeNickname, _data, _reply, 0); _reply.readException(); @@ -1049,12 +1067,12 @@ public interface IMountService extends IInterface { } @Override - public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException { + public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(volId); + _data.writeString(fsUuid); _data.writeInt(flags); _data.writeInt(mask); mRemote.transact(Stub.TRANSACTION_setVolumeUserFlags, _data, _reply, 0); @@ -1066,6 +1084,21 @@ public interface IMountService extends IInterface { } @Override + public void forgetVolume(String fsUuid) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(fsUuid); + mRemote.transact(Stub.TRANSACTION_forgetVolume, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override public String getPrimaryStorageUuid() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); @@ -1192,20 +1225,22 @@ public interface IMountService extends IInterface { static final int TRANSACTION_getDisks = IBinder.FIRST_CALL_TRANSACTION + 44; static final int TRANSACTION_getVolumes = IBinder.FIRST_CALL_TRANSACTION + 45; + static final int TRANSACTION_getVolumeRecords = IBinder.FIRST_CALL_TRANSACTION + 46; - static final int TRANSACTION_mount = IBinder.FIRST_CALL_TRANSACTION + 46; - static final int TRANSACTION_unmount = IBinder.FIRST_CALL_TRANSACTION + 47; - static final int TRANSACTION_format = IBinder.FIRST_CALL_TRANSACTION + 48; + static final int TRANSACTION_mount = IBinder.FIRST_CALL_TRANSACTION + 47; + static final int TRANSACTION_unmount = IBinder.FIRST_CALL_TRANSACTION + 48; + static final int TRANSACTION_format = IBinder.FIRST_CALL_TRANSACTION + 49; - static final int TRANSACTION_partitionPublic = IBinder.FIRST_CALL_TRANSACTION + 49; - static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 50; - static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 51; + static final int TRANSACTION_partitionPublic = IBinder.FIRST_CALL_TRANSACTION + 50; + static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 51; + static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 52; - static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52; - static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53; + static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 53; + static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 54; + static final int TRANSACTION_forgetVolume = IBinder.FIRST_CALL_TRANSACTION + 55; - static final int TRANSACTION_getPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 54; - static final int TRANSACTION_setPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 55; + static final int TRANSACTION_getPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 56; + static final int TRANSACTION_setPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 57; /** * Cast an IBinder object into an IMountService interface, generating a @@ -1647,6 +1682,14 @@ public interface IMountService extends IInterface { reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; } + case TRANSACTION_getVolumeRecords: { + data.enforceInterface(DESCRIPTOR); + int _flags = data.readInt(); + VolumeRecord[] volumes = getVolumeRecords(_flags); + reply.writeNoException(); + reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } case TRANSACTION_mount: { data.enforceInterface(DESCRIPTOR); String volId = data.readString(); @@ -1707,6 +1750,13 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_forgetVolume: { + data.enforceInterface(DESCRIPTOR); + String fsUuid = data.readString(); + forgetVolume(fsUuid); + reply.writeNoException(); + return true; + } case TRANSACTION_getPrimaryStorageUuid: { data.enforceInterface(DESCRIPTOR); String volumeUuid = getPrimaryStorageUuid(); @@ -2012,6 +2062,7 @@ public interface IMountService extends IInterface { public DiskInfo[] getDisks() throws RemoteException; public VolumeInfo[] getVolumes(int flags) throws RemoteException; + public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException; public void mount(String volId) throws RemoteException; public void unmount(String volId) throws RemoteException; @@ -2021,8 +2072,9 @@ public interface IMountService extends IInterface { public void partitionPrivate(String diskId) throws RemoteException; public void partitionMixed(String diskId, int ratio) throws RemoteException; - public void setVolumeNickname(String volId, String nickname) throws RemoteException; - public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException; + public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException; + public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException; + public void forgetVolume(String fsUuid) throws RemoteException; public String getPrimaryStorageUuid() throws RemoteException; public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java index fcb4779..c958fb0 100644 --- a/core/java/android/os/storage/IMountServiceListener.java +++ b/core/java/android/os/storage/IMountServiceListener.java @@ -91,10 +91,17 @@ public interface IMountServiceListener extends IInterface { reply.writeNoException(); return true; } - case TRANSACTION_onVolumeMetadataChanged: { + case TRANSACTION_onVolumeRecordChanged: { data.enforceInterface(DESCRIPTOR); - final VolumeInfo vol = (VolumeInfo) data.readParcelable(null); - onVolumeMetadataChanged(vol); + final VolumeRecord rec = (VolumeRecord) data.readParcelable(null); + onVolumeRecordChanged(rec); + reply.writeNoException(); + return true; + } + case TRANSACTION_onVolumeForgotten: { + data.enforceInterface(DESCRIPTOR); + final String fsUuid = data.readString(); + onVolumeForgotten(fsUuid); reply.writeNoException(); return true; } @@ -192,13 +199,29 @@ public interface IMountServiceListener extends IInterface { } @Override - public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException { + public void onVolumeRecordChanged(VolumeRecord rec) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeParcelable(vol, 0); - mRemote.transact(Stub.TRANSACTION_onVolumeMetadataChanged, _data, _reply, + _data.writeParcelable(rec, 0); + mRemote.transact(Stub.TRANSACTION_onVolumeRecordChanged, _data, _reply, + android.os.IBinder.FLAG_ONEWAY); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void onVolumeForgotten(String fsUuid) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(fsUuid); + mRemote.transact(Stub.TRANSACTION_onVolumeForgotten, _data, _reply, android.os.IBinder.FLAG_ONEWAY); _reply.readException(); } finally { @@ -228,8 +251,9 @@ public interface IMountServiceListener extends IInterface { static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2); - static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3); - static final int TRANSACTION_onDiskScanned = (IBinder.FIRST_CALL_TRANSACTION + 4); + static final int TRANSACTION_onVolumeRecordChanged = (IBinder.FIRST_CALL_TRANSACTION + 3); + static final int TRANSACTION_onVolumeForgotten = (IBinder.FIRST_CALL_TRANSACTION + 4); + static final int TRANSACTION_onDiskScanned = (IBinder.FIRST_CALL_TRANSACTION + 5); } /** @@ -252,8 +276,8 @@ public interface IMountServiceListener extends IInterface { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) throws RemoteException; - - public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException; + public void onVolumeRecordChanged(VolumeRecord rec) throws RemoteException; + public void onVolumeForgotten(String fsUuid) throws RemoteException; public void onDiskScanned(DiskInfo disk, int volumeCount) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java index 6a0140e..214c60d 100644 --- a/core/java/android/os/storage/StorageEventListener.java +++ b/core/java/android/os/storage/StorageEventListener.java @@ -41,7 +41,10 @@ public class StorageEventListener { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { } - public void onVolumeMetadataChanged(VolumeInfo vol) { + public void onVolumeRecordChanged(VolumeRecord rec) { + } + + public void onVolumeForgotten(String fsUuid) { } public void onDiskScanned(DiskInfo disk, int volumeCount) { diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 6116aef..3fdabee 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -34,6 +34,7 @@ import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.os.SomeArgs; @@ -79,9 +80,6 @@ public class StorageManager { /** {@hide} */ public static final String UUID_PRIMARY_PHYSICAL = "primary_physical"; - /** {@hide} */ - public static final int FLAG_ALL_METADATA = 1 << 0; - private final Context mContext; private final ContentResolver mResolver; @@ -95,8 +93,9 @@ public class StorageManager { Handler.Callback { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; - private static final int MSG_VOLUME_METADATA_CHANGED = 3; - private static final int MSG_DISK_SCANNED = 4; + private static final int MSG_VOLUME_RECORD_CHANGED = 3; + private static final int MSG_VOLUME_FORGOTTEN = 4; + private static final int MSG_DISK_SCANNED = 5; final StorageEventListener mCallback; final Handler mHandler; @@ -119,8 +118,12 @@ public class StorageManager { mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); args.recycle(); return true; - case MSG_VOLUME_METADATA_CHANGED: - mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + case MSG_VOLUME_RECORD_CHANGED: + mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1); + args.recycle(); + return true; + case MSG_VOLUME_FORGOTTEN: + mCallback.onVolumeForgotten((String) args.arg1); args.recycle(); return true; case MSG_DISK_SCANNED: @@ -156,10 +159,17 @@ public class StorageManager { } @Override - public void onVolumeMetadataChanged(VolumeInfo vol) { + public void onVolumeRecordChanged(VolumeRecord rec) { final SomeArgs args = SomeArgs.obtain(); - args.arg1 = vol; - mHandler.obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); + args.arg1 = rec; + mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget(); + } + + @Override + public void onVolumeForgotten(String fsUuid) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = fsUuid; + mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget(); } @Override @@ -248,8 +258,9 @@ public class StorageManager { } /** {@hide} */ + @Deprecated public static StorageManager from(Context context) { - return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + return context.getSystemService(StorageManager.class); } /** @@ -516,6 +527,18 @@ public class StorageManager { } /** {@hide} */ + public @Nullable VolumeRecord findRecordByUuid(String fsUuid) { + Preconditions.checkNotNull(fsUuid); + // TODO; go directly to service to make this faster + for (VolumeRecord rec : getVolumeRecords()) { + if (Objects.equals(rec.fsUuid, fsUuid)) { + return rec; + } + } + return null; + } + + /** {@hide} */ public @Nullable VolumeInfo findPrivateForEmulated(VolumeInfo emulatedVol) { return findVolumeById(emulatedVol.getId().replace("emulated", "private")); } @@ -526,14 +549,29 @@ public class StorageManager { } /** {@hide} */ + public @Nullable VolumeInfo findVolumeByQualifiedUuid(String volumeUuid) { + if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) { + return findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL); + } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) { + return getPrimaryPhysicalVolume(); + } else { + return findVolumeByUuid(volumeUuid); + } + } + + /** {@hide} */ public @NonNull List<VolumeInfo> getVolumes() { - return getVolumes(0); + try { + return Arrays.asList(mMountService.getVolumes(0)); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /** {@hide} */ - public @NonNull List<VolumeInfo> getVolumes(int flags) { + public @NonNull List<VolumeRecord> getVolumeRecords() { try { - return Arrays.asList(mMountService.getVolumes(flags)); + return Arrays.asList(mMountService.getVolumeRecords(0)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -541,13 +579,25 @@ public class StorageManager { /** {@hide} */ public @Nullable String getBestVolumeDescription(VolumeInfo vol) { - String descrip = vol.getDescription(); - if (vol.disk != null) { - if (TextUtils.isEmpty(descrip)) { - descrip = vol.disk.getDescription(); + if (vol == null) return null; + + // Nickname always takes precedence when defined + if (!TextUtils.isEmpty(vol.fsUuid)) { + final VolumeRecord rec = findRecordByUuid(vol.fsUuid); + if (rec != null && !TextUtils.isEmpty(rec.nickname)) { + return rec.nickname; } } - return descrip; + + if (!TextUtils.isEmpty(vol.getDescription())) { + return vol.getDescription(); + } + + if (vol.disk != null) { + return vol.disk.getDescription(); + } + + return null; } /** {@hide} */ @@ -616,29 +666,62 @@ public class StorageManager { } /** {@hide} */ - public void setVolumeNickname(String volId, String nickname) { + public void wipeAdoptableDisks() { + // We only wipe devices in "adoptable" locations, which are in a + // long-term stable slot/location on the device, where apps have a + // reasonable chance of storing sensitive data. (Apps need to go through + // SAF to write to transient volumes.) + final List<DiskInfo> disks = getDisks(); + for (DiskInfo disk : disks) { + final String diskId = disk.getId(); + if (disk.isAdoptable()) { + Slog.d(TAG, "Found adoptable " + diskId + "; wiping"); + try { + // TODO: switch to explicit wipe command when we have it, + // for now rely on the fact that vfat format does a wipe + mMountService.partitionPublic(diskId); + } catch (Exception e) { + Slog.w(TAG, "Failed to wipe " + diskId + ", but soldiering onward", e); + } + } else { + Slog.d(TAG, "Ignorning non-adoptable disk " + disk.getId()); + } + } + } + + /** {@hide} */ + public void setVolumeNickname(String fsUuid, String nickname) { try { - mMountService.setVolumeNickname(volId, nickname); + mMountService.setVolumeNickname(fsUuid, nickname); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } /** {@hide} */ - public void setVolumeInited(String volId, boolean inited) { + public void setVolumeInited(String fsUuid, boolean inited) { try { - mMountService.setVolumeUserFlags(volId, inited ? VolumeInfo.USER_FLAG_INITED : 0, - VolumeInfo.USER_FLAG_INITED); + mMountService.setVolumeUserFlags(fsUuid, inited ? VolumeRecord.USER_FLAG_INITED : 0, + VolumeRecord.USER_FLAG_INITED); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } /** {@hide} */ - public void setVolumeSnoozed(String volId, boolean snoozed) { + public void setVolumeSnoozed(String fsUuid, boolean snoozed) { try { - mMountService.setVolumeUserFlags(volId, snoozed ? VolumeInfo.USER_FLAG_SNOOZED : 0, - VolumeInfo.USER_FLAG_SNOOZED); + mMountService.setVolumeUserFlags(fsUuid, snoozed ? VolumeRecord.USER_FLAG_SNOOZED : 0, + VolumeRecord.USER_FLAG_SNOOZED); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void forgetVolume(String fsUuid) { + try { + mMountService.forgetVolume(fsUuid); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -804,6 +887,27 @@ public class StorageManager { DEFAULT_FULL_THRESHOLD_BYTES); } + /** {@hide} */ + public static File maybeTranslateEmulatedPathToInternal(File path) { + final IMountService mountService = IMountService.Stub.asInterface( + ServiceManager.getService("mount")); + try { + final VolumeInfo[] vols = mountService.getVolumes(0); + for (VolumeInfo vol : vols) { + if ((vol.getType() == VolumeInfo.TYPE_EMULATED + || vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isMountedReadable()) { + final File internalPath = FileUtils.rewriteAfterRename(vol.getPath(), + vol.getInternalPath(), path); + if (internalPath != null) { + return internalPath; + } + } + } + } catch (RemoteException ignored) { + } + return path; + } + /// Consts to match the password types in cryptfs.h /** @hide */ public static final int CRYPT_TYPE_PASSWORD = 0; diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 4e9cfc7..2622ee0 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -78,9 +78,6 @@ public class VolumeInfo implements Parcelable { public static final int MOUNT_FLAG_PRIMARY = 1 << 0; public static final int MOUNT_FLAG_VISIBLE = 1 << 1; - public static final int USER_FLAG_INITED = 1 << 0; - public static final int USER_FLAG_SNOOZED = 1 << 1; - private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); @@ -132,11 +129,10 @@ public class VolumeInfo implements Parcelable { public String fsUuid; public String fsLabel; public String path; + public String internalPath; /** Framework state */ public final int mtpIndex; - public String nickname; - public int userFlags = 0; public VolumeInfo(String id, int type, DiskInfo disk, int mtpIndex) { this.id = Preconditions.checkNotNull(id); @@ -160,9 +156,8 @@ public class VolumeInfo implements Parcelable { fsUuid = parcel.readString(); fsLabel = parcel.readString(); path = parcel.readString(); + internalPath = parcel.readString(); mtpIndex = parcel.readInt(); - nickname = parcel.readString(); - userFlags = parcel.readInt(); } public static @NonNull String getEnvironmentForState(int state) { @@ -210,10 +205,6 @@ public class VolumeInfo implements Parcelable { return fsUuid; } - public @Nullable String getNickname() { - return nickname; - } - public int getMountUserId() { return mountUserId; } @@ -221,8 +212,6 @@ public class VolumeInfo implements Parcelable { public @Nullable String getDescription() { if (ID_PRIVATE_INTERNAL.equals(id)) { return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); - } else if (!TextUtils.isEmpty(nickname)) { - return nickname; } else if (!TextUtils.isEmpty(fsLabel)) { return fsLabel; } else { @@ -250,14 +239,6 @@ public class VolumeInfo implements Parcelable { return (mountFlags & MOUNT_FLAG_VISIBLE) != 0; } - public boolean isInited() { - return (userFlags & USER_FLAG_INITED) != 0; - } - - public boolean isSnoozed() { - return (userFlags & USER_FLAG_SNOOZED) != 0; - } - public boolean isVisibleToUser(int userId) { if (type == TYPE_PUBLIC && userId == this.mountUserId) { return isVisible(); @@ -269,7 +250,11 @@ public class VolumeInfo implements Parcelable { } public File getPath() { - return new File(path); + return (path != null) ? new File(path) : null; + } + + public File getInternalPath() { + return (internalPath != null) ? new File(internalPath) : null; } public File getPathForUser(int userId) { @@ -356,14 +341,11 @@ public class VolumeInfo implements Parcelable { final Uri uri; if (type == VolumeInfo.TYPE_PUBLIC) { uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid); - } else if (VolumeInfo.ID_EMULATED_INTERNAL.equals(id)) { + } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) { uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, DOCUMENT_ROOT_PRIMARY_EMULATED); - } else if (type == VolumeInfo.TYPE_EMULATED) { - // TODO: build intent once supported - uri = null; } else { - throw new IllegalArgumentException(); + return null; } final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); @@ -393,9 +375,8 @@ public class VolumeInfo implements Parcelable { pw.printPair("fsLabel", fsLabel); pw.println(); pw.printPair("path", path); + pw.printPair("internalPath", internalPath); pw.printPair("mtpIndex", mtpIndex); - pw.printPair("nickname", nickname); - pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags)); pw.decreaseIndent(); pw.println(); } @@ -460,8 +441,7 @@ public class VolumeInfo implements Parcelable { parcel.writeString(fsUuid); parcel.writeString(fsLabel); parcel.writeString(path); + parcel.writeString(internalPath); parcel.writeInt(mtpIndex); - parcel.writeString(nickname); - parcel.writeInt(userFlags); } } diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java new file mode 100644 index 0000000..096e2dd --- /dev/null +++ b/core/java/android/os/storage/VolumeRecord.java @@ -0,0 +1,139 @@ +/* + * 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; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DebugUtils; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Metadata for a storage volume which may not be currently present. + * + * @hide + */ +public class VolumeRecord implements Parcelable { + public static final String EXTRA_FS_UUID = + "android.os.storage.extra.FS_UUID"; + + public static final int USER_FLAG_INITED = 1 << 0; + public static final int USER_FLAG_SNOOZED = 1 << 1; + + public final int type; + public final String fsUuid; + public String nickname; + public int userFlags; + + public VolumeRecord(int type, String fsUuid) { + this.type = type; + this.fsUuid = Preconditions.checkNotNull(fsUuid); + } + + public VolumeRecord(Parcel parcel) { + type = parcel.readInt(); + fsUuid = parcel.readString(); + nickname = parcel.readString(); + userFlags = parcel.readInt(); + } + + public int getType() { + return type; + } + + public String getFsUuid() { + return fsUuid; + } + + public String getNickname() { + return nickname; + } + + public boolean isInited() { + return (userFlags & USER_FLAG_INITED) != 0; + } + + public boolean isSnoozed() { + return (userFlags & USER_FLAG_SNOOZED) != 0; + } + + public void dump(IndentingPrintWriter pw) { + pw.println("VolumeRecord:"); + pw.increaseIndent(); + pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type)); + pw.printPair("fsUuid", fsUuid); + pw.printPair("nickname", nickname); + pw.printPair("userFlags", + DebugUtils.flagsToString(VolumeRecord.class, "USER_FLAG_", userFlags)); + pw.decreaseIndent(); + pw.println(); + } + + @Override + public VolumeRecord clone() { + final Parcel temp = Parcel.obtain(); + try { + writeToParcel(temp, 0); + temp.setDataPosition(0); + return CREATOR.createFromParcel(temp); + } finally { + temp.recycle(); + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof VolumeRecord) { + return Objects.equals(fsUuid, ((VolumeRecord) o).fsUuid); + } else { + return false; + } + } + + @Override + public int hashCode() { + return fsUuid.hashCode(); + } + + public static final Creator<VolumeRecord> CREATOR = new Creator<VolumeRecord>() { + @Override + public VolumeRecord createFromParcel(Parcel in) { + return new VolumeRecord(in); + } + + @Override + public VolumeRecord[] newArray(int size) { + return new VolumeRecord[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(type); + parcel.writeString(fsUuid); + parcel.writeString(nickname); + parcel.writeInt(userFlags); + } +} diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java index 918933b..3319e64 100644 --- a/core/java/android/preference/GenericInflater.java +++ b/core/java/android/preference/GenericInflater.java @@ -376,6 +376,7 @@ abstract class GenericInflater<T, P extends GenericInflater.Parent> { Class clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name); constructor = clazz.getConstructor(mConstructorSignature); + constructor.setAccessible(true); sConstructorMap.put(name, constructor); } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 396cf19..e07e846 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -9062,5 +9062,15 @@ public final class ContactsContract { */ public static final Uri CONTENT_URI = Uri.withAppendedPath(METADATA_AUTHORITY_URI, "metadata_sync"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of contact metadata + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single contact metadata. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata"; } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 00c851b..293cf6f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -919,6 +919,15 @@ public final class Settings { = "android.settings.ZEN_MODE_SCHEDULE_RULE_SETTINGS"; /** + * Activity Action: Show Zen Mode event rule configuration settings. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ZEN_MODE_EVENT_RULE_SETTINGS + = "android.settings.ZEN_MODE_EVENT_RULE_SETTINGS"; + + /** * Activity Action: Show Zen Mode external rule configuration settings. * * @hide diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java index b3a3aad..03248e5 100644 --- a/core/java/android/security/keymaster/KeyCharacteristics.java +++ b/core/java/android/security/keymaster/KeyCharacteristics.java @@ -87,6 +87,28 @@ public class KeyCharacteristics implements Parcelable { return result; } + public Long getLong(int tag) { + if (hwEnforced.containsTag(tag)) { + return hwEnforced.getLong(tag, -1); + } else if (swEnforced.containsTag(tag)) { + return swEnforced.getLong(tag, -1); + } else { + return null; + } + } + + public long getLong(int tag, long defaultValue) { + Long result = getLong(tag); + return (result != null) ? result : defaultValue; + } + + public List<Long> getLongs(int tag) { + List<Long> result = new ArrayList<Long>(); + result.addAll(hwEnforced.getLongs(tag)); + result.addAll(swEnforced.getLongs(tag)); + return result; + } + public Date getDate(int tag) { Date result = hwEnforced.getDate(tag, null); if (result == null) { @@ -105,11 +127,11 @@ public class KeyCharacteristics implements Parcelable { } } - public boolean getBoolean(KeyCharacteristics keyCharacteristics, int tag) { - if (keyCharacteristics.hwEnforced.containsTag(tag)) { - return keyCharacteristics.hwEnforced.getBoolean(tag, false); + public boolean getBoolean(int tag) { + if (hwEnforced.containsTag(tag)) { + return hwEnforced.getBoolean(tag, false); } else { - return keyCharacteristics.swEnforced.getBoolean(tag, false); + return swEnforced.getBoolean(tag, false); } } } diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 40baf9c..0e2b8ba 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -194,6 +194,9 @@ public final class KeymasterDefs { public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50; public static final int KM_ERROR_MISSING_NONCE = -51; public static final int KM_ERROR_INVALID_NONCE = -52; + public static final int KM_ERROR_UNSUPPORTED_CHUNK_LENGTH = -53; + public static final int KM_ERROR_RESCOPABLE_KEY_NOT_USABLE = -54; + public static final int KM_ERROR_CALLER_NONCE_PROHIBITED = -55; public static final int KM_ERROR_UNIMPLEMENTED = -100; public static final int KM_ERROR_VERSION_MISMATCH = -101; public static final int KM_ERROR_UNKNOWN_ERROR = -1000; @@ -235,6 +238,8 @@ public final class KeymasterDefs { sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field"); sErrorCodeToString.put(KM_ERROR_MISSING_NONCE, "Required IV missing"); sErrorCodeToString.put(KM_ERROR_INVALID_NONCE, "Invalid IV"); + sErrorCodeToString.put(KM_ERROR_CALLER_NONCE_PROHIBITED, + "Caller-provided IV not permitted"); sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); } diff --git a/core/java/android/service/carrier/CarrierMessagingService.java b/core/java/android/service/carrier/CarrierMessagingService.java index 0592a84..d7bf10c 100644 --- a/core/java/android/service/carrier/CarrierMessagingService.java +++ b/core/java/android/service/carrier/CarrierMessagingService.java @@ -80,6 +80,11 @@ public abstract class CarrierMessagingService extends Service { */ public static final int DOWNLOAD_STATUS_ERROR = 2; + /** + * Flag to request SMS delivery status report. + */ + public static final int SEND_FLAG_REQUEST_DELIVERY_STATUS = 1; + private final ICarrierMessagingWrapper mWrapper = new ICarrierMessagingWrapper(); /** @@ -103,12 +108,14 @@ public abstract class CarrierMessagingService extends Service { /** * Override this method to intercept text SMSs sent from the device. + * @deprecated Override {@link #onSendTextSms} below instead. * * @param text the text to send * @param subId SMS subscription ID of the SIM * @param destAddress phone number of the recipient of the message * @param callback result callback. Call with a {@link SendSmsResult}. */ + @Deprecated public void onSendTextSms( @NonNull String text, int subId, @NonNull String destAddress, @NonNull ResultCallback<SendSmsResult> callback) { @@ -120,7 +127,25 @@ public abstract class CarrierMessagingService extends Service { } /** + * Override this method to intercept text SMSs sent from the device. + * + * @param text the text to send + * @param subId SMS subscription ID of the SIM + * @param destAddress phone number of the recipient of the message + * @param sendSmsFlag Flag for sending SMS. Acceptable values are 0 and + * {@link #SEND_FLAG_REQUEST_DELIVERY_STATUS}. + * @param callback result callback. Call with a {@link SendSmsResult}. + */ + public void onSendTextSms( + @NonNull String text, int subId, @NonNull String destAddress, + int sendSmsFlag, @NonNull ResultCallback<SendSmsResult> callback) { + // optional + onSendTextSms(text, subId, destAddress, callback); + } + + /** * Override this method to intercept binary SMSs sent from the device. + * @deprecated Override {@link #onSendDataSms} below instead. * * @param data the binary content * @param subId SMS subscription ID of the SIM @@ -128,6 +153,7 @@ public abstract class CarrierMessagingService extends Service { * @param destPort the destination port * @param callback result callback. Call with a {@link SendSmsResult}. */ + @Deprecated public void onSendDataSms(@NonNull byte[] data, int subId, @NonNull String destAddress, int destPort, @NonNull ResultCallback<SendSmsResult> callback) { @@ -139,13 +165,33 @@ public abstract class CarrierMessagingService extends Service { } /** + * Override this method to intercept binary SMSs sent from the device. + * + * @param data the binary content + * @param subId SMS subscription ID of the SIM + * @param destAddress phone number of the recipient of the message + * @param destPort the destination port + * @param sendSmsFlag Flag for sending SMS. Acceptable values are 0 and + * {@link #SEND_FLAG_REQUEST_DELIVERY_STATUS}. + * @param callback result callback. Call with a {@link SendSmsResult}. + */ + public void onSendDataSms(@NonNull byte[] data, int subId, + @NonNull String destAddress, int destPort, int sendSmsFlag, + @NonNull ResultCallback<SendSmsResult> callback) { + // optional + onSendDataSms(data, subId, destAddress, destPort, callback); + } + + /** * Override this method to intercept long SMSs sent from the device. + * @deprecated Override {@link #onSendMultipartTextSms} below instead. * * @param parts a {@link List} of the message parts * @param subId SMS subscription ID of the SIM * @param destAddress phone number of the recipient of the message * @param callback result callback. Call with a {@link SendMultipartSmsResult}. */ + @Deprecated public void onSendMultipartTextSms(@NonNull List<String> parts, int subId, @NonNull String destAddress, @NonNull ResultCallback<SendMultipartSmsResult> callback) { @@ -158,6 +204,23 @@ public abstract class CarrierMessagingService extends Service { } /** + * Override this method to intercept long SMSs sent from the device. + * + * @param parts a {@link List} of the message parts + * @param subId SMS subscription ID of the SIM + * @param destAddress phone number of the recipient of the message + * @param sendSmsFlag Flag for sending SMS. Acceptable values are 0 and + * {@link #SEND_FLAG_REQUEST_DELIVERY_STATUS}. + * @param callback result callback. Call with a {@link SendMultipartSmsResult}. + */ + public void onSendMultipartTextSms(@NonNull List<String> parts, + int subId, @NonNull String destAddress, int sendSmsFlag, + @NonNull ResultCallback<SendMultipartSmsResult> callback) { + // optional + onSendMultipartTextSms(parts, subId, destAddress, callback); + } + + /** * Override this method to intercept MMSs sent from the device. * * @param pduUri the content provider URI of the PDU to send @@ -355,8 +418,9 @@ public abstract class CarrierMessagingService extends Service { @Override public void sendTextSms(String text, int subId, String destAddress, - final ICarrierMessagingCallback callback) { - onSendTextSms(text, subId, destAddress, new ResultCallback<SendSmsResult>() { + int sendSmsFlag, final ICarrierMessagingCallback callback) { + onSendTextSms(text, subId, destAddress, sendSmsFlag, + new ResultCallback<SendSmsResult>() { @Override public void onReceiveResult(final SendSmsResult result) throws RemoteException { callback.onSendSmsComplete(result.getSendStatus(), result.getMessageRef()); @@ -366,8 +430,9 @@ public abstract class CarrierMessagingService extends Service { @Override public void sendDataSms(byte[] data, int subId, String destAddress, int destPort, - final ICarrierMessagingCallback callback) { - onSendDataSms(data, subId, destAddress, destPort, new ResultCallback<SendSmsResult>() { + int sendSmsFlag, final ICarrierMessagingCallback callback) { + onSendDataSms(data, subId, destAddress, destPort, sendSmsFlag, + new ResultCallback<SendSmsResult>() { @Override public void onReceiveResult(final SendSmsResult result) throws RemoteException { callback.onSendSmsComplete(result.getSendStatus(), result.getMessageRef()); @@ -377,8 +442,8 @@ public abstract class CarrierMessagingService extends Service { @Override public void sendMultipartTextSms(List<String> parts, int subId, String destAddress, - final ICarrierMessagingCallback callback) { - onSendMultipartTextSms(parts, subId, destAddress, + int sendSmsFlag, final ICarrierMessagingCallback callback) { + onSendMultipartTextSms(parts, subId, destAddress, sendSmsFlag, new ResultCallback<SendMultipartSmsResult>() { @Override public void onReceiveResult(final SendMultipartSmsResult result) diff --git a/core/java/android/service/carrier/ICarrierMessagingService.aidl b/core/java/android/service/carrier/ICarrierMessagingService.aidl index 40a9047..2d96c3d 100644 --- a/core/java/android/service/carrier/ICarrierMessagingService.aidl +++ b/core/java/android/service/carrier/ICarrierMessagingService.aidl @@ -48,9 +48,10 @@ oneway interface ICarrierMessagingService { * @param text the text to send * @param subId SMS subscription ID of the SIM * @param destAddress phone number of the recipient of the message + * @param sendSmsFlag flag for sending SMS * @param callback the callback to notify upon completion */ - void sendTextSms(String text, int subId, String destAddress, + void sendTextSms(String text, int subId, String destAddress, int sendSmsFlag, in ICarrierMessagingCallback callback); /** @@ -62,10 +63,11 @@ oneway interface ICarrierMessagingService { * @param subId SMS subscription ID of the SIM * @param destAddress phone number of the recipient of the message * @param destPort port number of the recipient of the message + * @param sendSmsFlag flag for sending SMS * @param callback the callback to notify upon completion */ void sendDataSms(in byte[] data, int subId, String destAddress, int destPort, - in ICarrierMessagingCallback callback); + int sendSmsFlag, in ICarrierMessagingCallback callback); /** * Request sending a new multi-part text SMS from the device. @@ -75,10 +77,11 @@ oneway interface ICarrierMessagingService { * @param parts the parts of the multi-part text SMS to send * @param subId SMS subscription ID of the SIM * @param destAddress phone number of the recipient of the message + * @param sendSmsFlag flag for sending SMS * @param callback the callback to notify upon completion */ void sendMultipartTextSms(in List<String> parts, int subId, String destAddress, - in ICarrierMessagingCallback callback); + int sendSmsFlag, in ICarrierMessagingCallback callback); /** * Request sending a new MMS PDU from the device. diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 14e947c..26bd10f 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -55,6 +55,7 @@ public class ZenModeConfig implements Parcelable { public static final int SOURCE_CONTACT = 1; public static final int SOURCE_STAR = 2; public static final int MAX_SOURCE = SOURCE_STAR; + private static final int DEFAULT_SOURCE = SOURCE_CONTACT; public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; @@ -62,11 +63,13 @@ public class ZenModeConfig implements Parcelable { Calendar.WEDNESDAY, Calendar.THURSDAY }; public static final int[] WEEKEND_DAYS = { Calendar.FRIDAY, Calendar.SATURDAY }; - public static final int[] MINUTE_BUCKETS = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 }; + public static final int[] MINUTE_BUCKETS = generateMinuteBuckets(); private static final int SECONDS_MS = 1000; private static final int MINUTES_MS = 60 * SECONDS_MS; private static final int ZERO_VALUE_MS = 10 * SECONDS_MS; + private static final boolean DEFAULT_ALLOW_CALLS = true; + private static final boolean DEFAULT_ALLOW_MESSAGES = false; private static final boolean DEFAULT_ALLOW_REMINDERS = true; private static final boolean DEFAULT_ALLOW_EVENTS = true; private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false; @@ -79,6 +82,8 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers"; private static final String ALLOW_ATT_MESSAGES = "messages"; private static final String ALLOW_ATT_FROM = "from"; + private static final String ALLOW_ATT_CALLS_FROM = "callsFrom"; + private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom"; private static final String ALLOW_ATT_REMINDERS = "reminders"; private static final String ALLOW_ATT_EVENTS = "events"; @@ -95,7 +100,7 @@ public class ZenModeConfig implements Parcelable { private static final String MANUAL_TAG = "manual"; private static final String AUTOMATIC_TAG = "automatic"; - private static final String RULE_ATT_ID = "id"; + private static final String RULE_ATT_ID = "ruleId"; private static final String RULE_ATT_ENABLED = "enabled"; private static final String RULE_ATT_SNOOZING = "snoozing"; private static final String RULE_ATT_NAME = "name"; @@ -103,12 +108,13 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_ZEN = "zen"; private static final String RULE_ATT_CONDITION_ID = "conditionId"; - public boolean allowCalls; + public boolean allowCalls = DEFAULT_ALLOW_CALLS; public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS; - public boolean allowMessages; + public boolean allowMessages = DEFAULT_ALLOW_MESSAGES; public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; public boolean allowEvents = DEFAULT_ALLOW_EVENTS; - public int allowFrom = SOURCE_ANYONE; + public int allowCallsFrom = DEFAULT_SOURCE; + public int allowMessagesFrom = DEFAULT_SOURCE; public ZenRule manualRule; public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>(); @@ -121,7 +127,8 @@ public class ZenModeConfig implements Parcelable { allowMessages = source.readInt() == 1; allowReminders = source.readInt() == 1; allowEvents = source.readInt() == 1; - allowFrom = source.readInt(); + allowCallsFrom = source.readInt(); + allowMessagesFrom = source.readInt(); manualRule = source.readParcelable(null); final int len = source.readInt(); if (len > 0) { @@ -142,7 +149,8 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(allowMessages ? 1 : 0); dest.writeInt(allowReminders ? 1 : 0); dest.writeInt(allowEvents ? 1 : 0); - dest.writeInt(allowFrom); + dest.writeInt(allowCallsFrom); + dest.writeInt(allowMessagesFrom); dest.writeParcelable(manualRule, 0); if (!automaticRules.isEmpty()) { final int len = automaticRules.size(); @@ -166,7 +174,8 @@ public class ZenModeConfig implements Parcelable { .append("allowCalls=").append(allowCalls) .append(",allowRepeatCallers=").append(allowRepeatCallers) .append(",allowMessages=").append(allowMessages) - .append(",allowFrom=").append(sourceToString(allowFrom)) + .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom)) + .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom)) .append(",allowReminders=").append(allowReminders) .append(",allowEvents=").append(allowEvents) .append(",automaticRules=").append(automaticRules) @@ -201,6 +210,18 @@ public class ZenModeConfig implements Parcelable { } } + private static int[] generateMinuteBuckets() { + final int maxHrs = 12; + final int[] buckets = new int[maxHrs + 3]; + buckets[0] = 15; + buckets[1] = 30; + buckets[2] = 45; + for (int i = 1; i <= maxHrs; i++) { + buckets[2 + i] = 60 * i; + } + return buckets; + } + public static String sourceToString(int source) { switch (source) { case SOURCE_ANYONE: @@ -222,7 +243,8 @@ public class ZenModeConfig implements Parcelable { return other.allowCalls == allowCalls && other.allowRepeatCallers == allowRepeatCallers && other.allowMessages == allowMessages - && other.allowFrom == allowFrom + && other.allowCallsFrom == allowCallsFrom + && other.allowMessagesFrom == allowMessagesFrom && other.allowReminders == allowReminders && other.allowEvents == allowEvents && Objects.equals(other.automaticRules, automaticRules) @@ -231,8 +253,8 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { - return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowFrom, - allowReminders, allowEvents, automaticRules, manualRule); + return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom, + allowMessagesFrom, allowReminders, allowEvents, automaticRules, manualRule); } private static String toDayList(int[] days) { @@ -267,6 +289,15 @@ public class ZenModeConfig implements Parcelable { } } + private static long tryParseLong(String value, long defValue) { + if (TextUtils.isEmpty(value)) return defValue; + try { + return Long.valueOf(value); + } catch (NumberFormatException e) { + return defValue; + } + } + public static ZenModeConfig readXml(XmlPullParser parser, Migration migration) throws XmlPullParserException, IOException { int type = parser.getEventType(); @@ -293,15 +324,25 @@ public class ZenModeConfig implements Parcelable { rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, DEFAULT_ALLOW_REMINDERS); rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); - rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE); - if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) { - throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom); + final int from = safeInt(parser, ALLOW_ATT_FROM, -1); + final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1); + final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1); + if (isValidSource(callsFrom) && isValidSource(messagesFrom)) { + rt.allowCallsFrom = callsFrom; + rt.allowMessagesFrom = messagesFrom; + } else if (isValidSource(from)) { + Slog.i(TAG, "Migrating existing shared 'from': " + sourceToString(from)); + rt.allowCallsFrom = from; + rt.allowMessagesFrom = from; + } else { + rt.allowCallsFrom = DEFAULT_SOURCE; + rt.allowMessagesFrom = DEFAULT_SOURCE; } } else if (MANUAL_TAG.equals(tag)) { - rt.manualRule = readRuleXml(parser); + rt.manualRule = readRuleXml(parser, false /*conditionRequired*/); } else if (AUTOMATIC_TAG.equals(tag)) { final String id = parser.getAttributeValue(null, RULE_ATT_ID); - final ZenRule automaticRule = readRuleXml(parser); + final ZenRule automaticRule = readRuleXml(parser, true /*conditionRequired*/); if (id != null && automaticRule != null) { rt.automaticRules.put(id, automaticRule); } @@ -321,7 +362,8 @@ public class ZenModeConfig implements Parcelable { out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders)); out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents)); - out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom)); + out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom)); + out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom)); out.endTag(null, ALLOW_TAG); if (manualRule != null) { @@ -341,7 +383,7 @@ public class ZenModeConfig implements Parcelable { out.endTag(null, ZEN_TAG); } - public static ZenRule readRuleXml(XmlPullParser parser) { + public static ZenRule readRuleXml(XmlPullParser parser, boolean conditionRequired) { final ZenRule rt = new ZenRule(); rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true); rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false); @@ -355,7 +397,7 @@ public class ZenModeConfig implements Parcelable { rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID); rt.component = safeComponentName(parser, RULE_ATT_COMPONENT); rt.condition = readConditionXml(parser); - return rt.condition != null ? rt : null; + return rt; } public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException { @@ -411,6 +453,10 @@ public class ZenModeConfig implements Parcelable { return val >= 0 && val < 60; } + private static boolean isValidSource(int source) { + return source >= SOURCE_ANYONE && source <= MAX_SOURCE; + } + private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) { final String val = parser.getAttributeValue(null, att); if (TextUtils.isEmpty(val)) return defValue; @@ -473,7 +519,8 @@ public class ZenModeConfig implements Parcelable { public Policy toNotificationPolicy() { int priorityCategories = 0; - int prioritySenders = Policy.PRIORITY_SENDERS_ANY; + int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS; + int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS; if (allowCalls) { priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; } @@ -489,18 +536,27 @@ public class ZenModeConfig implements Parcelable { if (allowRepeatCallers) { priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; } - switch (allowFrom) { - case SOURCE_ANYONE: - prioritySenders = Policy.PRIORITY_SENDERS_ANY; - break; - case SOURCE_CONTACT: - prioritySenders = Policy.PRIORITY_SENDERS_CONTACTS; - break; - case SOURCE_STAR: - prioritySenders = Policy.PRIORITY_SENDERS_STARRED; - break; + priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); + priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); + return new Policy(priorityCategories, priorityCallSenders); + } + + private static int sourceToPrioritySenders(int source, int def) { + switch (source) { + case SOURCE_ANYONE: return Policy.PRIORITY_SENDERS_ANY; + case SOURCE_CONTACT: return Policy.PRIORITY_SENDERS_CONTACTS; + case SOURCE_STAR: return Policy.PRIORITY_SENDERS_STARRED; + default: return def; + } + } + + private static int prioritySendersToSource(int prioritySenders, int def) { + switch (prioritySenders) { + case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT; + case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR; + case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE; + default: return def; } - return new Policy(priorityCategories, prioritySenders); } public void applyNotificationPolicy(Policy policy) { @@ -511,17 +567,7 @@ public class ZenModeConfig implements Parcelable { allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0; allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; - switch (policy.prioritySenders) { - case Policy.PRIORITY_SENDERS_CONTACTS: - allowFrom = SOURCE_CONTACT; - break; - case Policy.PRIORITY_SENDERS_STARRED: - allowFrom = SOURCE_STAR; - break; - default: - allowFrom = SOURCE_ANYONE; - break; - } + allowCallsFrom = prioritySendersToSource(policy.prioritySenders, allowCallsFrom); } public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { @@ -556,10 +602,12 @@ public class ZenModeConfig implements Parcelable { Condition.FLAG_RELEVANT_NOW); } - // For built-in conditions + // ==== Built-in system conditions ==== + public static final String SYSTEM_AUTHORITY = "android"; - // Built-in countdown conditions, e.g. condition://android/countdown/1399917958951 + // ==== Built-in system condition: countdown ==== + public static final String COUNTDOWN_PATH = "countdown"; public static Uri toCountdownConditionId(long time) { @@ -586,9 +634,43 @@ public class ZenModeConfig implements Parcelable { return tryParseCountdownConditionId(conditionId) != 0; } - // built-in schedule conditions + // ==== Built-in system condition: schedule ==== + public static final String SCHEDULE_PATH = "schedule"; + public static Uri toScheduleConditionId(ScheduleInfo schedule) { + return new Uri.Builder().scheme(Condition.SCHEME) + .authority(SYSTEM_AUTHORITY) + .appendPath(SCHEDULE_PATH) + .appendQueryParameter("days", toDayList(schedule.days)) + .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute) + .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute) + .build(); + } + + public static boolean isValidScheduleConditionId(Uri conditionId) { + return tryParseScheduleConditionId(conditionId) != null; + } + + public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) { + final boolean isSchedule = conditionId != null + && conditionId.getScheme().equals(Condition.SCHEME) + && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) + && conditionId.getPathSegments().size() == 1 + && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH); + if (!isSchedule) return null; + final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start")); + final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end")); + if (start == null || end == null) return null; + final ScheduleInfo rt = new ScheduleInfo(); + rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\."); + rt.startHour = start[0]; + rt.startMinute = start[1]; + rt.endHour = end[0]; + rt.endMinute = end[1]; + return rt; + } + public static class ScheduleInfo { public int[] days; public int startHour; @@ -626,39 +708,67 @@ public class ZenModeConfig implements Parcelable { } } - public static Uri toScheduleConditionId(ScheduleInfo schedule) { + // ==== Built-in system condition: event ==== + + public static final String EVENT_PATH = "event"; + + public static Uri toEventConditionId(EventInfo event) { return new Uri.Builder().scheme(Condition.SCHEME) .authority(SYSTEM_AUTHORITY) - .appendPath(SCHEDULE_PATH) - .appendQueryParameter("days", toDayList(schedule.days)) - .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute) - .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute) + .appendPath(EVENT_PATH) + .appendQueryParameter("calendar", Long.toString(event.calendar)) + .appendQueryParameter("reply", Integer.toString(event.reply)) .build(); } - public static boolean isValidScheduleConditionId(Uri conditionId) { - return tryParseScheduleConditionId(conditionId) != null; + public static boolean isValidEventConditionId(Uri conditionId) { + return tryParseEventConditionId(conditionId) != null; } - public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) { - final boolean isSchedule = conditionId != null + public static EventInfo tryParseEventConditionId(Uri conditionId) { + final boolean isEvent = conditionId != null && conditionId.getScheme().equals(Condition.SCHEME) && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) && conditionId.getPathSegments().size() == 1 - && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH); - if (!isSchedule) return null; - final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start")); - final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end")); - if (start == null || end == null) return null; - final ScheduleInfo rt = new ScheduleInfo(); - rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\."); - rt.startHour = start[0]; - rt.startMinute = start[1]; - rt.endHour = end[0]; - rt.endMinute = end[1]; + && conditionId.getPathSegments().get(0).equals(EVENT_PATH); + if (!isEvent) return null; + final EventInfo rt = new EventInfo(); + rt.calendar = tryParseLong(conditionId.getQueryParameter("calendar"), 0L); + rt.reply = tryParseInt(conditionId.getQueryParameter("reply"), 0); return rt; } + public static class EventInfo { + public static final int REPLY_ANY_EXCEPT_NO = 0; + public static final int REPLY_YES_OR_MAYBE = 1; + public static final int REPLY_YES = 2; + + public long calendar; // CalendarContract.Calendars._ID, or 0 for any + public int reply; + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof EventInfo)) return false; + final EventInfo other = (EventInfo) o; + return calendar == other.calendar + && reply == other.reply; + } + + public EventInfo copy() { + final EventInfo rt = new EventInfo(); + rt.calendar = calendar; + rt.reply = reply; + return rt; + } + } + + // ==== End built-in system conditions ==== + private static int[] tryParseHourAndMinute(String value) { if (TextUtils.isEmpty(value)) return null; final int i = value.indexOf('.'); diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl index e8265a2..e3d68a6 100644 --- a/core/java/android/service/voice/IVoiceInteractionService.aidl +++ b/core/java/android/service/voice/IVoiceInteractionService.aidl @@ -23,4 +23,5 @@ oneway interface IVoiceInteractionService { void ready(); void soundModelsChanged(); void shutdown(); + void launchVoiceAssistFromKeyguard(); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index fee0c75..8c89ddb 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -98,6 +98,10 @@ public class VoiceInteractionService extends Service { @Override public void soundModelsChanged() { mHandler.sendEmptyMessage(MSG_SOUND_MODELS_CHANGED); } + @Override + public void launchVoiceAssistFromKeyguard() throws RemoteException { + mHandler.sendEmptyMessage(MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD); + } }; MyHandler mHandler; @@ -113,6 +117,7 @@ public class VoiceInteractionService extends Service { static final int MSG_READY = 1; static final int MSG_SHUTDOWN = 2; static final int MSG_SOUND_MODELS_CHANGED = 3; + static final int MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD = 4; class MyHandler extends Handler { @Override @@ -127,6 +132,9 @@ public class VoiceInteractionService extends Service { case MSG_SOUND_MODELS_CHANGED: onSoundModelsChangedInternal(); break; + case MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD: + onLaunchVoiceAssistFromKeyguard(); + break; default: super.handleMessage(msg); } @@ -134,6 +142,19 @@ public class VoiceInteractionService extends Service { } /** + * Called when a user has activated an affordance to launch voice assist from the Keyguard. + * + * <p>This method will only be called if the VoiceInteractionService has set + * {@link android.R.attr#supportsLaunchVoiceAssistFromKeyguard} and the Keyguard is showing.</p> + * + * <p>A valid implementation must start a new activity that should use {@link + * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display + * on top of the lock screen.</p> + */ + public void onLaunchVoiceAssistFromKeyguard() { + } + + /** * Check whether the given service component is the currently active * VoiceInteractionService. */ diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java index 4bc97c9..463eb5b 100644 --- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java +++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java @@ -43,7 +43,8 @@ public class VoiceInteractionServiceInfo { private String mSessionService; private String mRecognitionService; private String mSettingsActivity; - private boolean mSupportsAssistGesture; + private boolean mSupportsAssist; + private boolean mSupportsLaunchFromKeyguard; public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp) throws PackageManager.NameNotFoundException { @@ -95,8 +96,11 @@ public class VoiceInteractionServiceInfo { com.android.internal.R.styleable.VoiceInteractionService_recognitionService); mSettingsActivity = array.getString( com.android.internal.R.styleable.VoiceInteractionService_settingsActivity); - mSupportsAssistGesture = array.getBoolean( - com.android.internal.R.styleable.VoiceInteractionService_supportsAssistGesture, + mSupportsAssist = array.getBoolean( + com.android.internal.R.styleable.VoiceInteractionService_supportsAssist, + false); + mSupportsLaunchFromKeyguard = array.getBoolean(com.android.internal. + R.styleable.VoiceInteractionService_supportsLaunchVoiceAssistFromKeyguard, false); array.recycle(); if (mSessionService == null) { @@ -145,7 +149,11 @@ public class VoiceInteractionServiceInfo { return mSettingsActivity; } - public boolean getSupportsAssistGesture() { - return mSupportsAssistGesture; + public boolean getSupportsAssist() { + return mSupportsAssist; + } + + public boolean getSupportsLaunchFromKeyguard() { + return mSupportsLaunchFromKeyguard; } } diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java index e991d84..ce94315 100644 --- a/core/java/android/speech/RecognizerIntent.java +++ b/core/java/android/speech/RecognizerIntent.java @@ -403,4 +403,12 @@ public class RecognizerIntent { */ public static final String EXTRA_SUPPORTED_LANGUAGES = "android.speech.extra.SUPPORTED_LANGUAGES"; + + /** + * Optional boolean, to be used with {@link #ACTION_RECOGNIZE_SPEECH}, + * {@link #ACTION_VOICE_SEARCH_HANDS_FREE}, {@link #ACTION_WEB_SEARCH} to indicate whether to + * only use an offline speech recognition engine. The default is false, meaning that either + * network or offline recognition engines may be used. + */ + public static final String EXTRA_PREFER_OFFLINE = "android.speech.extra.PREFER_OFFLINE"; } diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 239b386..fc65f63 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -283,15 +283,14 @@ public class DynamicLayout extends Layout if (reflowed == null) { reflowed = new StaticLayout(null); - b = StaticLayout.Builder.obtain(text, where, where + after, getWidth()); + b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth()); } b.setText(text, where, where + after) .setPaint(getPaint()) .setWidth(getWidth()) .setTextDir(getTextDirectionHeuristic()) - .setSpacingMult(getSpacingMultiplier()) - .setSpacingAdd(getSpacingAdd()) + .setLineSpacing(getSpacingAdd(), getSpacingMultiplier()) .setEllipsizedWidth(mEllipsizedWidth) .setEllipsize(mEllipsizeAt) .setBreakStrategy(mBreakStrategy); diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 7bebbfb..a55a08c 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -278,7 +278,7 @@ public class Html { if (style[j] instanceof TypefaceSpan) { String s = ((TypefaceSpan) style[j]).getFamily(); - if (s.equals("monospace")) { + if ("monospace".equals(s)) { out.append("<tt>"); } } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 67794b1..451abea 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -16,6 +16,7 @@ package android.text; +import android.annotation.Nullable; import android.graphics.Paint; import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; @@ -46,18 +47,31 @@ public class StaticLayout extends Layout { static final String TAG = "StaticLayout"; /** - * Builder for static layouts. It would be better if this were a public - * API (as it would offer much greater flexibility for adding new options) - * but for the time being it's just internal. - * - * @hide + * Builder for static layouts. The builder is a newer pattern for constructing + * StaticLayout objects and should be preferred over the constructors, + * particularly to access newer features. To build a static layout, first + * call {@link #obtain} with the required arguments (text, paint, and width), + * then call setters for optional parameters, and finally {@link #build} + * to build the StaticLayout object. Parameters not explicitly set will get + * default values. */ public final static class Builder { private Builder() { mNativePtr = nNewBuilder(); } - public static Builder obtain(CharSequence source, int start, int end, int width) { + /** + * Obtain a builder for constructing StaticLayout objects + * + * @param source The text to be laid out, optionally with spans + * @param start The index of the start of the text + * @param end The index + 1 of the end of the text + * @param paint The base paint used for layout + * @param width The width in pixels + * @return a builder object used for constructing the StaticLayout + */ + public static Builder obtain(CharSequence source, int start, int end, TextPaint paint, + int width) { Builder b = sPool.acquire(); if (b == null) { b = new Builder(); @@ -67,6 +81,7 @@ public class StaticLayout extends Layout { b.mText = source; b.mStart = start; b.mEnd = end; + b.mPaint = paint; b.mWidth = width; b.mAlignment = Alignment.ALIGN_NORMAL; b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR; @@ -98,6 +113,18 @@ public class StaticLayout extends Layout { return setText(source, 0, source.length()); } + /** + * Set the text. Only useful when re-using the builder, which is done for + * the internal implementation of {@link DynamicLayout} but not as part + * of normal {@link StaticLayout} usage. + * + * @param source The text to be laid out, optionally with spans + * @param start The index of the start of the text + * @param end The index + 1 of the end of the text + * @return this builder, useful for chaining + * + * @hide + */ public Builder setText(CharSequence source, int start, int end) { mText = source; mStart = start; @@ -105,11 +132,27 @@ public class StaticLayout extends Layout { return this; } + /** + * Set the paint. Internal for reuse cases only. + * + * @param paint The base paint used for layout + * @return this builder, useful for chaining + * + * @hide + */ public Builder setPaint(TextPaint paint) { mPaint = paint; return this; } + /** + * Set the width. Internal for reuse cases only. + * + * @param width The width in pixels + * @return this builder, useful for chaining + * + * @hide + */ public Builder setWidth(int width) { mWidth = width; if (mEllipsize == null) { @@ -118,53 +161,126 @@ public class StaticLayout extends Layout { return this; } + /** + * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}. + * + * @param alignment Alignment for the resulting {@link StaticLayout} + * @return this builder, useful for chaining + */ public Builder setAlignment(Alignment alignment) { mAlignment = alignment; return this; } + /** + * Set the text direction heuristic. The text direction heuristic is used to + * resolve text direction based per-paragraph based on the input text. The default is + * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}. + * + * @param textDir text direction heuristic for resolving BiDi behavior. + * @return this builder, useful for chaining + */ public Builder setTextDir(TextDirectionHeuristic textDir) { mTextDir = textDir; return this; } - // TODO: combine the following, as they're almost always set together? - public Builder setSpacingMult(float spacingMult) { - mSpacingMult = spacingMult; - return this; - } - - public Builder setSpacingAdd(float spacingAdd) { + /** + * Set line spacing parameters. The default is 0.0 for {@code spacingAdd} + * and 1.0 for {@code spacingMult}. + * + * @param spacingAdd line spacing add + * @param spacingMult line spacing multiplier + * @return this builder, useful for chaining + * @see android.widget.TextView#setLineSpacing + */ + public Builder setLineSpacing(float spacingAdd, float spacingMult) { mSpacingAdd = spacingAdd; + mSpacingMult = spacingMult; return this; } + /** + * Set whether to include extra space beyond font ascent and descent (which is + * needed to avoid clipping in some languages, such as Arabic and Kannada). The + * default is {@code true}. + * + * @param includePad whether to include padding + * @return this builder, useful for chaining + * @see android.widget.TextView#setIncludeFontPadding + */ public Builder setIncludePad(boolean includePad) { mIncludePad = includePad; return this; } - // TODO: combine the following? + /** + * Set the width as used for ellipsizing purposes, if it differs from the + * normal layout width. The default is the {@code width} + * passed to {@link #obtain}. + * + * @param ellipsizedWidth width used for ellipsizing, in pixels + * @return this builder, useful for chaining + * @see android.widget.TextView#setEllipsize + */ public Builder setEllipsizedWidth(int ellipsizedWidth) { mEllipsizedWidth = ellipsizedWidth; return this; } - public Builder setEllipsize(TextUtils.TruncateAt ellipsize) { + /** + * Set ellipsizing on the layout. Causes words that are longer than the view + * is wide, or exceeding the number of lines (see #setMaxLines) in the case + * of {@link android.text.TextUtils.TruncateAt#END} or + * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead + * of broken. The default is + * {@code null}, indicating no ellipsis is to be applied. + * + * @param ellipsize type of ellipsis behavior + * @return this builder, useful for chaining + * @see android.widget.TextView#setEllipsize + */ + public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) { mEllipsize = ellipsize; return this; } + /** + * Set maximum number of lines. This is particularly useful in the case of + * ellipsizing, where it changes the layout of the last line. The default is + * unlimited. + * + * @param maxLines maximum number of lines in the layout + * @return this builder, useful for chaining + * @see android.widget.TextView#setMaxLines + */ public Builder setMaxLines(int maxLines) { mMaxLines = maxLines; return this; } + /** + * Set break strategy, useful for selecting high quality or balanced paragraph + * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}. + * + * @param breakStrategy break strategy for paragraph layout + * @return this builder, useful for chaining + * @see android.widget.TextView#setBreakStrategy + */ public Builder setBreakStrategy(@BreakStrategy int breakStrategy) { mBreakStrategy = breakStrategy; return this; } + /** + * Set indents. Arguments are arrays holding an indent amount, one per line, measured in + * pixels. For lines past the last element in the array, the last element repeats. + * + * @param leftIndents array of indent values for left margin, in pixels + * @param rightIndents array of indent values for right margin, in pixels + * @return this builder, useful for chaining + * @see android.widget.TextView#setIndents + */ public Builder setIndents(int[] leftIndents, int[] rightIndents) { int leftLen = leftIndents == null ? 0 : leftIndents.length; int rightLen = rightIndents == null ? 0 : rightIndents.length; @@ -218,6 +334,15 @@ public class StaticLayout extends Layout { nAddReplacementRun(mNativePtr, start, end, width); } + /** + * Build the {@link StaticLayout} after options have been set. + * + * <p>Note: the builder object must not be reused in any way after calling this + * method. Setting parameters after calling this method, or calling it a second + * time on the same builder object, will likely lead to unexpected results. + * + * @return the newly constructed {@link StaticLayout} object + */ public StaticLayout build() { StaticLayout result = new StaticLayout(this); Builder.recycle(this); @@ -327,12 +452,10 @@ public class StaticLayout extends Layout { : new Ellipsizer(source), paint, outerwidth, align, textDir, spacingmult, spacingadd); - Builder b = Builder.obtain(source, bufstart, bufend, outerwidth) - .setPaint(paint) + Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth) .setAlignment(align) .setTextDir(textDir) - .setSpacingMult(spacingmult) - .setSpacingAdd(spacingadd) + .setLineSpacing(spacingadd, spacingmult) .setIncludePad(includepad) .setEllipsizedWidth(ellipsizedWidth) .setEllipsize(ellipsize) diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index c942042..ebc2aac 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -792,6 +792,9 @@ public abstract class Transition implements Cloneable { * views are ignored and only the ids are used). */ boolean isValidTarget(View target) { + if (target == null) { + return false; + } int targetId = target.getId(); if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) { return false; diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index a7d9503..cbf76bc 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -211,10 +211,10 @@ public class TransitionInflater { .asSubclass(expectedType); if (c != null) { constructor = c.getConstructor(sConstructorSignature); + constructor.setAccessible(true); sConstructors.put(className, constructor); } } - return constructor.newInstance(mContext, attrs); } } catch (InstantiationException e) { diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index cd68fd1..ed7fd86 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -19,12 +19,17 @@ package android.transition; import com.android.internal.R; import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.Animator.AnimatorPauseListener; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; + /** * This transition tracks changes to the visibility of target views in the * start and end scenes. Visibility is determined not just by the @@ -286,6 +291,12 @@ public abstract class Visibility extends Transition { return null; } } + final boolean isForcedVisibility = mForcedStartVisibility != -1 || + mForcedEndVisibility != -1; + if (isForcedVisibility) { + // Make sure that we reverse the effect of onDisappear's setTransitionAlpha(0) + endValues.view.setTransitionAlpha(1); + } return onAppear(sceneRoot, endValues.view, startValues, endValues); } @@ -418,9 +429,9 @@ public abstract class Visibility extends Transition { sceneRoot.getOverlay().remove(overlayView); } else { final View finalOverlayView = overlayView; - animator.addListener(new AnimatorListenerAdapter() { + addListener(new TransitionListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onTransitionEnd(Transition transition) { finalSceneRoot.getOverlay().remove(finalOverlayView); } }); @@ -438,40 +449,10 @@ public abstract class Visibility extends Transition { } Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues); if (animator != null) { - final View finalViewToKeep = viewToKeep; - animator.addListener(new AnimatorListenerAdapter() { - boolean mCanceled = false; - - @Override - public void onAnimationPause(Animator animation) { - if (!mCanceled && !isForcedVisibility) { - finalViewToKeep.setVisibility(finalVisibility); - } - } - - @Override - public void onAnimationResume(Animator animation) { - if (!mCanceled && !isForcedVisibility) { - finalViewToKeep.setVisibility(View.VISIBLE); - } - } - - @Override - public void onAnimationCancel(Animator animation) { - mCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (!mCanceled) { - if (isForcedVisibility) { - finalViewToKeep.setTransitionAlpha(0); - } else { - finalViewToKeep.setVisibility(finalVisibility); - } - } - } - }); + DisappearListener disappearListener = new DisappearListener(viewToKeep, + finalVisibility, isForcedVisibility); + animator.addListener(disappearListener); + addListener(disappearListener); } else if (!isForcedVisibility) { viewToKeep.setVisibility(originalVisibility); } @@ -517,4 +498,68 @@ public abstract class Visibility extends Transition { TransitionValues endValues) { return null; } + + private static class DisappearListener + extends TransitionListenerAdapter implements AnimatorListener, AnimatorPauseListener { + private final boolean mIsForcedVisibility; + private final View mView; + private final int mFinalVisibility; + + boolean mCanceled = false; + + public DisappearListener(View view, int finalVisibility, boolean isForcedVisibility) { + this.mView = view; + this.mIsForcedVisibility = isForcedVisibility; + this.mFinalVisibility = finalVisibility; + } + + @Override + public void onAnimationPause(Animator animation) { + if (!mCanceled && !mIsForcedVisibility) { + mView.setVisibility(mFinalVisibility); + } + } + + @Override + public void onAnimationResume(Animator animation) { + if (!mCanceled && !mIsForcedVisibility) { + mView.setVisibility(View.VISIBLE); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + hideViewWhenNotCanceled(); + } + + @Override + public void onTransitionEnd(Transition transition) { + hideViewWhenNotCanceled(); + } + + private void hideViewWhenNotCanceled() { + if (!mCanceled) { + if (mIsForcedVisibility) { + mView.setTransitionAlpha(0); + } else { + mView.setVisibility(mFinalVisibility); + } + } + } + } } diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index aefced8..558b8f5 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -74,6 +74,7 @@ public class EventLog { private static final byte LONG_TYPE = 1; private static final byte STRING_TYPE = 2; private static final byte LIST_TYPE = 3; + private static final byte FLOAT_TYPE = 4; /** @param data containing event, read from the system */ /*package*/ Event(byte[] data) { @@ -106,7 +107,7 @@ public class EventLog { return mBuffer.getInt(offset); } - /** @return one of Integer, Long, String, null, or Object[] of same. */ + /** @return one of Integer, Long, Float, String, null, or Object[] of same. */ public synchronized Object getData() { try { int offset = mBuffer.getShort(HEADER_SIZE_OFFSET); @@ -130,10 +131,13 @@ public class EventLog { byte type = mBuffer.get(); switch (type) { case INT_TYPE: - return (Integer) mBuffer.getInt(); + return mBuffer.getInt(); case LONG_TYPE: - return (Long) mBuffer.getLong(); + return mBuffer.getLong(); + + case FLOAT_TYPE: + return mBuffer.getFloat(); case STRING_TYPE: try { @@ -180,6 +184,14 @@ public class EventLog { /** * Record an event log message. * @param tag The event type tag code + * @param value A value to log + * @return The number of bytes written + */ + public static native int writeEvent(int tag, float value); + + /** + * Record an event log message. + * @param tag The event type tag code * @param str A value to log * @return The number of bytes written */ diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java index eedbc70..bb761f0 100644 --- a/core/java/android/view/DisplayListCanvas.java +++ b/core/java/android/view/DisplayListCanvas.java @@ -93,12 +93,6 @@ public class DisplayListCanvas extends Canvas { private static native long nCreateDisplayListCanvas(); - public static void setProperty(String name, String value) { - nSetProperty(name, value); - } - - private static native void nSetProperty(String name, String value); - /////////////////////////////////////////////////////////////////////////// // Canvas management /////////////////////////////////////////////////////////////////////////// @@ -234,25 +228,13 @@ public class DisplayListCanvas extends Canvas { * Draws the specified display list onto this canvas. The display list can only * be drawn if {@link android.view.RenderNode#isValid()} returns true. * - * @param renderNode The RenderNode to replay. + * @param renderNode The RenderNode to draw. */ public void drawRenderNode(RenderNode renderNode) { - drawRenderNode(renderNode, RenderNode.FLAG_CLIP_CHILDREN); - } - - /** - * Draws the specified display list onto this canvas. - * - * @param renderNode The RenderNode to replay. - * @param flags Optional flags about drawing, see {@link RenderNode} for - * the possible flags. - */ - public void drawRenderNode(RenderNode renderNode, int flags) { - nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList(), flags); + nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList()); } - private static native void nDrawRenderNode(long renderer, long renderNode, - int flags); + private static native void nDrawRenderNode(long renderer, long renderNode); /////////////////////////////////////////////////////////////////////////// // Hardware layer @@ -283,7 +265,7 @@ public class DisplayListCanvas extends Canvas { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); - nDrawPatch(mNativeCanvasWrapper, bitmap.getSkBitmap(), patch.mNativeChunk, + nDrawPatch(mNativeCanvasWrapper, bitmap, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } @@ -293,11 +275,11 @@ public class DisplayListCanvas extends Canvas { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); - nDrawPatch(mNativeCanvasWrapper, bitmap.getSkBitmap(), patch.mNativeChunk, + nDrawPatch(mNativeCanvasWrapper, bitmap, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } - private static native void nDrawPatch(long renderer, long bitmap, long chunk, + private static native void nDrawPatch(long renderer, Bitmap bitmap, long chunk, float left, float top, float right, float bottom, long paint); public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java index d58e7c0..bc38e1a 100644 --- a/core/java/android/view/GhostView.java +++ b/core/java/android/view/GhostView.java @@ -41,7 +41,7 @@ public class GhostView extends View { final ViewGroup parent = (ViewGroup) mView.getParent(); setGhostedVisibility(View.INVISIBLE); parent.mRecreateDisplayList = true; - parent.getDisplayList(); + parent.updateDisplayListIfDirty(); } @Override @@ -49,7 +49,7 @@ public class GhostView extends View { if (canvas instanceof DisplayListCanvas) { DisplayListCanvas dlCanvas = (DisplayListCanvas) canvas; mView.mRecreateDisplayList = true; - RenderNode renderNode = mView.getDisplayList(); + RenderNode renderNode = mView.updateDisplayListIfDirty(); if (renderNode.isValid()) { dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode dlCanvas.drawRenderNode(renderNode); @@ -84,7 +84,7 @@ public class GhostView extends View { final ViewGroup parent = (ViewGroup) mView.getParent(); if (parent != null) { parent.mRecreateDisplayList = true; - parent.getDisplayList(); + parent.updateDisplayListIfDirty(); } } } diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index cc090ad..6651b83 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -52,6 +52,11 @@ public class HapticFeedbackConstants { public static final int CALENDAR_DATE = 5; /** + * The user has touched the screen with a stylus and pressed the stylus button. + */ + public static final int STYLUS_BUTTON_PRESS = 6; + + /** * This is a private constant. Feel free to renumber as desired. * @hide */ diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 457d6ad..37312d0 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -590,6 +590,7 @@ public abstract class LayoutInflater { } } constructor = clazz.getConstructor(mConstructorSignature); + constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { // If we have a filter, apply it to cached constructor @@ -615,7 +616,6 @@ public abstract class LayoutInflater { Object[] args = mConstructorArgs; args[1] = attrs; - constructor.setAccessible(true); final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. @@ -946,25 +946,21 @@ public abstract class LayoutInflater { attrs, R.styleable.Include); final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); final int visibility = a.getInt(R.styleable.Include_visibility, -1); - final boolean hasWidth = a.hasValue(R.styleable.Include_layout_width); - final boolean hasHeight = a.hasValue(R.styleable.Include_layout_height); a.recycle(); - // We try to load the layout params set in the <include /> tag. If - // they don't exist, we will rely on the layout params set in the - // included XML file. - // During a layoutparams generation, a runtime exception is thrown - // if either layout_width or layout_height is missing. We catch - // this exception and set localParams accordingly: true means we - // successfully loaded layout params from the <include /> tag, - // false means we need to rely on the included layout params. + // We try to load the layout params set in the <include /> tag. + // If the parent can't generate layout params (ex. missing width + // or height for the framework ViewGroups, though this is not + // necessarily true of all ViewGroups) then we expect it to throw + // a runtime exception. + // We catch this exception and set localParams accordingly: true + // means we successfully loaded layout params from the <include> + // tag, false means we need to rely on the included layout params. ViewGroup.LayoutParams params = null; - if (hasWidth && hasHeight) { - try { - params = group.generateLayoutParams(attrs); - } catch (RuntimeException e) { - // Ignore, just fail over to child attrs. - } + try { + params = group.generateLayoutParams(attrs); + } catch (RuntimeException e) { + // Ignore, just fail over to child attrs. } if (params == null) { params = group.generateLayoutParams(childAttrs); diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index b49a59e..dc8cadf 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -544,6 +544,7 @@ public class MenuInflater { try { Class<?> clazz = mContext.getClassLoader().loadClass(className); Constructor<?> constructor = clazz.getConstructor(constructorSignature); + constructor.setAccessible(true); return (T) constructor.newInstance(arguments); } catch (Exception e) { Log.w(LOG_TAG, "Cannot instantiate class: " + className, e); diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 5e45c8f..5df596a 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -3180,6 +3180,18 @@ public final class MotionEvent extends InputEvent implements Parcelable { return (getButtonState() & button) == button; } + /** + * Checks if a stylus is being used and if the first stylus button is + * pressed. + * + * @return True if the tool is a stylus and if the first stylus button is + * pressed. + * @see #BUTTON_SECONDARY + */ + public final boolean isStylusButtonPressed() { + return (isButtonPressed(BUTTON_SECONDARY) && getToolType(0) == TOOL_TYPE_STYLUS); + } + public static final Parcelable.Creator<MotionEvent> CREATOR = new Parcelable.Creator<MotionEvent>() { public MotionEvent createFromParcel(Parcel in) { diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java index 794c8e7..a3e7a10 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/android/view/PhoneWindow.java @@ -3599,7 +3599,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (!mForcedNavigationBarColor) { mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000); } - if (a.getBoolean(R.styleable.Window_windowHasLightStatusBar, false)) { + if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { decor.setSystemUiVisibility( decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index ad34f02..85b22fb 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -246,6 +246,9 @@ public class TextureView extends View { mSurface = null; mLayer = null; + // Make sure if/when new layer gets re-created, transform matrix will + // be re-applied. + mMatrixChanged = true; mHadSurface = true; } } @@ -284,11 +287,6 @@ public class TextureView extends View { return LAYER_TYPE_HARDWARE; } - @Override - boolean hasStaticLayer() { - return true; - } - /** * Calling this method has no effect. */ diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 87d5d9a..1fd7109 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.IntDef; +import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -269,7 +270,7 @@ public class ThreadedRenderer extends HardwareRenderer { view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) == View.PFLAG_INVALIDATED; view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; - view.getDisplayList(); + view.updateDisplayListIfDirty(); view.mRecreateDisplayList = false; } @@ -285,7 +286,7 @@ public class ThreadedRenderer extends HardwareRenderer { callbacks.onHardwarePreDraw(canvas); canvas.insertReorderBarrier(); - canvas.drawRenderNode(view.getDisplayList()); + canvas.drawRenderNode(view.updateDisplayListIfDirty()); canvas.insertInorderBarrier(); callbacks.onHardwarePostDraw(canvas); @@ -360,7 +361,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override boolean copyLayerInto(final HardwareLayer layer, final Bitmap bitmap) { return nCopyLayerInto(mNativeProxy, - layer.getDeferredLayerUpdater(), bitmap.getSkBitmap()); + layer.getDeferredLayerUpdater(), bitmap); } @Override @@ -412,6 +413,13 @@ public class ThreadedRenderer extends HardwareRenderer { nTrimMemory(level); } + public static void overrideProperty(@NonNull String name, @NonNull String value) { + if (name == null || value == null) { + throw new IllegalArgumentException("name and value must be non-null"); + } + nOverrideProperty(name, value); + } + public static void dumpProfileData(byte[] data, FileDescriptor fd) { nDumpProfileData(data, fd); } @@ -460,8 +468,6 @@ public class ThreadedRenderer extends HardwareRenderer { if (buffer != null) { long[] map = atlas.getMap(); if (map != null) { - // TODO Remove after fixing b/15425820 - validateMap(context, map); nSetAtlas(renderProxy, buffer, map); } // If IAssetAtlas is not the same class as the IBinder @@ -476,32 +482,6 @@ public class ThreadedRenderer extends HardwareRenderer { Log.w(LOG_TAG, "Could not acquire atlas", e); } } - - private static void validateMap(Context context, long[] map) { - Log.d("Atlas", "Validating map..."); - HashSet<Long> preloadedPointers = new HashSet<Long>(); - - // We only care about drawables that hold bitmaps - final Resources resources = context.getResources(); - final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables(); - - final int count = drawables.size(); - ArrayList<Bitmap> tmpList = new ArrayList<Bitmap>(); - for (int i = 0; i < count; i++) { - drawables.valueAt(i).addAtlasableBitmaps(tmpList); - for (int j = 0; j < tmpList.size(); j++) { - preloadedPointers.add(tmpList.get(j).getSkBitmap()); - } - tmpList.clear(); - } - - for (int i = 0; i < map.length; i += 4) { - if (!preloadedPointers.contains(map[i])) { - Log.w("Atlas", String.format("Pointer 0x%X, not in getPreloadedDrawables?", map[i])); - map[i] = 0; - } - } - } } static native void setupShadersDiskCache(String cacheFile); @@ -531,13 +511,14 @@ public class ThreadedRenderer extends HardwareRenderer { private static native long nCreateTextureLayer(long nativeProxy); private static native void nBuildLayer(long nativeProxy, long node); - private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap); + private static native boolean nCopyLayerInto(long nativeProxy, long layer, Bitmap bitmap); private static native void nPushLayerUpdate(long nativeProxy, long layer); private static native void nCancelLayerUpdate(long nativeProxy, long layer); private static native void nDetachSurfaceTexture(long nativeProxy, long layer); private static native void nDestroyHardwareResources(long nativeProxy); private static native void nTrimMemory(int level); + private static native void nOverrideProperty(String name, String value); private static native void nFence(long nativeProxy); private static native void nStopDrawing(long nativeProxy); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7da2da4..f62e6a2 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -28,8 +28,10 @@ import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.UiThread; import android.content.ClipData; import android.content.Context; +import android.content.ContextWrapper; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -699,6 +701,7 @@ import java.util.concurrent.atomic.AtomicInteger; * * @see android.view.ViewGroup */ +@UiThread public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { private static final boolean DBG = false; @@ -986,6 +989,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int DUPLICATE_PARENT_STATE = 0x00400000; + /** + * <p> + * Indicates this view can be stylus button pressed. When stylus button + * pressable, a View reacts to stylus button presses by notifiying + * the OnStylusButtonPressListener. + * </p> + * {@hide} + */ + static final int STYLUS_BUTTON_PRESSABLE = 0x00800000; + + /** @hide */ @IntDef({ SCROLLBARS_INSIDE_OVERLAY, @@ -2381,10 +2395,143 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x80; + /** + * Flag indicating that the bottom scroll indicator should be displayed + * when this view can scroll up. + */ + static final int PFLAG3_SCROLL_INDICATOR_TOP = 0x0100; + + /** + * Flag indicating that the bottom scroll indicator should be displayed + * when this view can scroll down. + */ + static final int PFLAG3_SCROLL_INDICATOR_BOTTOM = 0x0200; + + /** + * Flag indicating that the left scroll indicator should be displayed + * when this view can scroll left. + */ + static final int PFLAG3_SCROLL_INDICATOR_LEFT = 0x0400; + + /** + * Flag indicating that the right scroll indicator should be displayed + * when this view can scroll right. + */ + static final int PFLAG3_SCROLL_INDICATOR_RIGHT = 0x0800; + + /** + * Flag indicating that the start scroll indicator should be displayed + * when this view can scroll in the start direction. + */ + static final int PFLAG3_SCROLL_INDICATOR_START = 0x1000; + + /** + * Flag indicating that the end scroll indicator should be displayed + * when this view can scroll in the end direction. + */ + static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000; + /* End of masks for mPrivateFlags3 */ static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED; + static final int SCROLL_INDICATORS_NONE = 0x0000; + + /** + * Mask for use with setFlags indicating bits used for indicating which + * scroll indicators are enabled. + */ + static final int SCROLL_INDICATORS_PFLAG3_MASK = PFLAG3_SCROLL_INDICATOR_TOP + | PFLAG3_SCROLL_INDICATOR_BOTTOM | PFLAG3_SCROLL_INDICATOR_LEFT + | PFLAG3_SCROLL_INDICATOR_RIGHT | PFLAG3_SCROLL_INDICATOR_START + | PFLAG3_SCROLL_INDICATOR_END; + + /** + * Left-shift required to translate between public scroll indicator flags + * and internal PFLAGS3 flags. When used as a right-shift, translates + * PFLAGS3 flags to public flags. + */ + static final int SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT = 8; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = { + SCROLL_INDICATOR_TOP, + SCROLL_INDICATOR_BOTTOM, + SCROLL_INDICATOR_LEFT, + SCROLL_INDICATOR_RIGHT, + SCROLL_INDICATOR_START, + SCROLL_INDICATOR_END, + }) + public @interface ScrollIndicators {} + + /** + * Scroll indicator direction for the top edge of the view. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_TOP = + PFLAG3_SCROLL_INDICATOR_TOP >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the bottom edge of the view. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_BOTTOM = + PFLAG3_SCROLL_INDICATOR_BOTTOM >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the left edge of the view. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_LEFT = + PFLAG3_SCROLL_INDICATOR_LEFT >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the right edge of the view. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_RIGHT = + PFLAG3_SCROLL_INDICATOR_RIGHT >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the starting edge of the view. + * <p> + * Resolved according to the view's layout direction, see + * {@link #getLayoutDirection()} for more information. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_START = + PFLAG3_SCROLL_INDICATOR_START >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the ending edge of the view. + * <p> + * Resolved according to the view's layout direction, see + * {@link #getLayoutDirection()} for more information. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_END = + PFLAG3_SCROLL_INDICATOR_END >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + /** * <p>Indicates that we are allowing {@link android.view.ViewAssistStructure} to traverse * into this view.<p> @@ -2591,7 +2738,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS * FLAG_TRANSLUCENT_STATUS}. * - * @see android.R.attr#windowHasLightStatusBar + * @see android.R.attr#windowLightStatusBar */ public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000; @@ -3203,6 +3350,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(deepExport = true, prefix = "fg_") private ForegroundInfo mForegroundInfo; + private Drawable mScrollIndicatorDrawable; + /** * RenderNode used for backgrounds. * <p> @@ -3269,6 +3418,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected OnLongClickListener mOnLongClickListener; /** + * Listener used to dispatch stylus touch and button press events. This field should be made + * private, so it is hidden from the SDK. + * {@hide} + */ + protected OnStylusButtonPressListener mOnStylusButtonPressListener; + + /** * Listener used to build the context menu. * This field should be made private, so it is hidden from the SDK. * {@hide} @@ -3359,6 +3515,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private boolean mHasPerformedLongPress; /** + * Whether the stylus button is currently pressed down. This is true when + * the stylus is touching the screen and the button has been pressed, this + * is false once the stylus has been lifted. + */ + private boolean mInStylusButtonPress = false; + + /** * The minimum height of the view. We'll try our best to have the height * of this view to at least this amount. */ @@ -3741,6 +3904,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; int overScrollMode = mOverScrollMode; boolean initializeScrollbars = false; + boolean initializeScrollIndicators = false; boolean startPaddingDefined = false; boolean endPaddingDefined = false; @@ -3874,6 +4038,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, viewFlagMasks |= LONG_CLICKABLE; } break; + case com.android.internal.R.styleable.View_stylusButtonPressable: + if (a.getBoolean(attr, false)) { + viewFlagValues |= STYLUS_BUTTON_PRESSABLE; + viewFlagMasks |= STYLUS_BUTTON_PRESSABLE; + } + break; case com.android.internal.R.styleable.View_saveEnabled: if (!a.getBoolean(attr, true)) { viewFlagValues |= SAVE_DISABLED; @@ -4016,37 +4186,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final String handlerName = a.getString(attr); if (handlerName != null) { - setOnClickListener(new OnClickListener() { - private Method mHandler; - - public void onClick(View v) { - if (mHandler == null) { - try { - mHandler = getContext().getClass().getMethod(handlerName, - View.class); - } catch (NoSuchMethodException e) { - int id = getId(); - String idText = id == NO_ID ? "" : " with id '" - + getContext().getResources().getResourceEntryName( - id) + "'"; - throw new IllegalStateException("Could not find a method " + - handlerName + "(View) in the activity " - + getContext().getClass() + " for onClick handler" - + " on view " + View.this.getClass() + idText, e); - } - } - - try { - mHandler.invoke(getContext(), View.this); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Could not execute non " - + "public method of the activity", e); - } catch (InvocationTargetException e) { - throw new IllegalStateException("Could not execute " - + "method of the activity", e); - } - } - }); + setOnClickListener(new DeclaredOnClickListener(this, handlerName)); } break; case R.styleable.View_overScrollMode: @@ -4131,6 +4271,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mForegroundInfo.mInsidePadding = a.getBoolean(attr, mForegroundInfo.mInsidePadding); + case R.styleable.View_scrollIndicators: + final int scrollIndicators = + a.getInt(attr, SCROLL_INDICATORS_NONE) & SCROLL_INDICATORS_PFLAG3_MASK; + if (scrollIndicators != 0) { + viewFlagValues |= scrollIndicators; + viewFlagMasks |= SCROLL_INDICATORS_PFLAG3_MASK; + initializeScrollIndicators = true; + } break; } } @@ -4207,6 +4355,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, initializeScrollbarsInternal(a); } + if (initializeScrollIndicators) { + initializeScrollIndicatorsInternal(); + } + a.recycle(); // Needs to be called after mViewFlags is set @@ -4238,6 +4390,66 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * An implementation of OnClickListener that attempts to lazily load a + * named click handling method from a parent or ancestor context. + */ + private static class DeclaredOnClickListener implements OnClickListener { + private final View mHostView; + private final String mMethodName; + + private Method mMethod; + + public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) { + mHostView = hostView; + mMethodName = methodName; + } + + @Override + public void onClick(@NonNull View v) { + if (mMethod == null) { + mMethod = resolveMethod(mHostView.getContext(), mMethodName); + } + + try { + mMethod.invoke(mHostView.getContext(), v); + } catch (IllegalAccessException e) { + throw new IllegalStateException( + "Could not execute non-public method for android:onClick", e); + } catch (InvocationTargetException e) { + throw new IllegalStateException( + "Could not execute method for android:onClick", e); + } + } + + @NonNull + private Method resolveMethod(@Nullable Context context, @NonNull String name) { + while (context != null) { + try { + if (!context.isRestricted()) { + return context.getClass().getMethod(mMethodName, View.class); + } + } catch (NoSuchMethodException e) { + // Failed to find method, keep searching up the hierarchy. + } + + if (context instanceof ContextWrapper) { + context = ((ContextWrapper) context).getBaseContext(); + } else { + // Can't search up the hierarchy, null out and fail. + context = null; + } + } + + final int id = mHostView.getId(); + final String idText = id == NO_ID ? "" : " with id '" + + mHostView.getContext().getResources().getResourceEntryName(id) + "'"; + throw new IllegalStateException("Could not find method " + mMethodName + + "(View) in a parent or ancestor Context for android:onClick " + + "attribute defined on view " + mHostView.getClass() + idText); + } + } + + /** * Non-public constructor for use in testing */ View() { @@ -4309,6 +4521,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, out.append((mViewFlags&SCROLLBARS_VERTICAL) != 0 ? 'V' : '.'); out.append((mViewFlags&CLICKABLE) != 0 ? 'C' : '.'); out.append((mViewFlags&LONG_CLICKABLE) != 0 ? 'L' : '.'); + out.append((mViewFlags & STYLUS_BUTTON_PRESSABLE) != 0 ? 'S' : '.'); out.append(' '); out.append((mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0 ? 'R' : '.'); out.append((mPrivateFlags&PFLAG_FOCUSED) != 0 ? 'F' : '.'); @@ -4617,6 +4830,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, resolvePadding(); } + private void initializeScrollIndicatorsInternal() { + // Some day maybe we'll break this into top/left/start/etc. and let the + // client control it. Until then, you can have any scroll indicator you + // want as long as it's a 1dp foreground-colored rectangle. + if (mScrollIndicatorDrawable == null) { + mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material); + } + } + /** * <p> * Initalizes the scrollability cache if necessary. @@ -4656,6 +4878,118 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mVerticalScrollbarPosition; } + /** + * Sets the state of all scroll indicators. + * <p> + * See {@link #setScrollIndicators(int, int)} for usage information. + * + * @param indicators a bitmask of indicators that should be enabled, or + * {@code 0} to disable all indicators + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + * @attr ref android.R.styleable#View_scrollIndicators + */ + public void setScrollIndicators(@ScrollIndicators int indicators) { + setScrollIndicators(indicators, SCROLL_INDICATORS_PFLAG3_MASK); + } + + /** + * Sets the state of the scroll indicators specified by the mask. To change + * all scroll indicators at once, see {@link #setScrollIndicators(int)}. + * <p> + * When a scroll indicator is enabled, it will be displayed if the view + * can scroll in the direction of the indicator. + * <p> + * Multiple indicator types may be enabled or disabled by passing the + * logical OR of the desired types. If multiple types are specified, they + * will all be set to the same enabled state. + * <p> + * For example, to enable the top scroll indicatorExample: {@code setScrollIndicators + * + * @param indicators the indicator direction, or the logical OR of multiple + * indicator directions. One or more of: + * <ul> + * <li>{@link #SCROLL_INDICATOR_TOP}</li> + * <li>{@link #SCROLL_INDICATOR_BOTTOM}</li> + * <li>{@link #SCROLL_INDICATOR_LEFT}</li> + * <li>{@link #SCROLL_INDICATOR_RIGHT}</li> + * <li>{@link #SCROLL_INDICATOR_START}</li> + * <li>{@link #SCROLL_INDICATOR_END}</li> + * </ul> + * @see #setScrollIndicators(int) + * @see #getScrollIndicators() + * @attr ref android.R.styleable#View_scrollIndicators + */ + public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask) { + // Shift and sanitize mask. + mask <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + mask &= SCROLL_INDICATORS_PFLAG3_MASK; + + // Shift and mask indicators. + indicators <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + indicators &= mask; + + // Merge with non-masked flags. + final int updatedFlags = indicators | (mPrivateFlags3 & ~mask); + + if (mPrivateFlags3 != updatedFlags) { + mPrivateFlags3 = updatedFlags; + + if (indicators != 0) { + initializeScrollIndicatorsInternal(); + } + invalidate(); + } + } + + /** + * Returns a bitmask representing the enabled scroll indicators. + * <p> + * For example, if the top and left scroll indicators are enabled and all + * other indicators are disabled, the return value will be + * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}. + * <p> + * To check whether the bottom scroll indicator is enabled, use the value + * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}. + * + * @return a bitmask representing the enabled scroll indicators + */ + @ScrollIndicators + public int getScrollIndicators() { + return (mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) + >>> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + } + + /** + * Returns whether the specified scroll indicator is enabled. + * <p> + * Multiple indicator types may be queried by passing the logical OR of the + * desired types. If multiple types are specified, the return value + * represents whether they are all enabled. + * + * @param direction the indicator direction, or the logical OR of multiple + * indicator directions. One or more of: + * <ul> + * <li>{@link #SCROLL_INDICATOR_TOP}</li> + * <li>{@link #SCROLL_INDICATOR_BOTTOM}</li> + * <li>{@link #SCROLL_INDICATOR_LEFT}</li> + * <li>{@link #SCROLL_INDICATOR_RIGHT}</li> + * <li>{@link #SCROLL_INDICATOR_START}</li> + * <li>{@link #SCROLL_INDICATOR_END}</li> + * </ul> + * @return {@code true} if the specified indicator(s) are enabled, + * {@code false} otherwise + * @attr ref android.R.styleable#View_scrollIndicators + */ + public boolean isScrollIndicatorEnabled(int direction) { + // Shift and sanitize input. + direction <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + direction &= SCROLL_INDICATORS_PFLAG3_MASK; + + // All of the flags must be set. + return (mPrivateFlags3 & direction) == direction; + } + ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; @@ -4804,6 +5138,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Register a callback to be invoked when this view is touched with a stylus and the button is + * pressed. + * + * @param l The callback that will run + * @see #setStylusButtonPressable(boolean) + */ + public void setOnStylusButtonPressListener(@Nullable OnStylusButtonPressListener l) { + if (!isStylusButtonPressable()) { + setStylusButtonPressable(true); + } + getListenerInfo().mOnStylusButtonPressListener = l; + } + + /** * Register a callback to be invoked when the context menu for this view is * being built. If this view is not long clickable, it becomes long clickable. * @@ -4881,6 +5229,46 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Call this view's OnStylusButtonPressListener, if it is defined. + * + * @return True if there was an assigned OnStylusButtonPressListener that consumed the event, + * false otherwise. + */ + public boolean performStylusButtonPress() { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_STYLUS_BUTTON_PRESSED); + + boolean handled = false; + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnStylusButtonPressListener != null) { + handled = li.mOnStylusButtonPressListener.onStylusButtonPress(View.this); + } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.STYLUS_BUTTON_PRESS); + } + return handled; + } + + /** + * Checks for a stylus button press and calls the listener. + * + * @param event The event. + * @return True if the event was consumed. + */ + private boolean performStylusActionOnButtonPress(MotionEvent event) { + if (isStylusButtonPressable() && !mInStylusButtonPress + && !mHasPerformedLongPress && event.isStylusButtonPressed()) { + if (performStylusButtonPress()) { + mInStylusButtonPress = true; + setPressed(true, event.getX(), event.getY()); + removeTapCallback(); + removeLongPressCallback(); + return true; + } + } + return false; + } + + /** * Performs button-related actions during a touch down event. * * @param event The event. @@ -5670,6 +6058,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>{@link AccessibilityNodeInfo#setFocused(boolean)},</li> * <li>{@link AccessibilityNodeInfo#setLongClickable(boolean)},</li> * <li>{@link AccessibilityNodeInfo#setSelected(boolean)},</li> + * <li>{@link AccessibilityNodeInfo#setStylusButtonPressable(boolean)}</li> * </ul> * <p> * Subclasses should override this method, call the super implementation, @@ -5821,6 +6210,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setChecked(true); } } + if (isStylusButtonPressable()) { + structure.setStylusButtonPressable(true); + } structure.setClassName(getAccessibilityClassName().toString()); structure.setContentDescription(getContentDescription()); } @@ -5878,6 +6270,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setChecked(true); } } + if (info.isStylusButtonPressable()) { + structure.setStylusButtonPressable(true); + } CharSequence cname = info.getClassName(); structure.setClassName(cname != null ? cname.toString() : null); structure.setContentDescription(info.getContentDescription()); @@ -6007,6 +6402,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.setAccessibilityFocused(isAccessibilityFocused()); info.setSelected(isSelected()); info.setLongClickable(isLongClickable()); + info.setStylusButtonPressable(isStylusButtonPressable()); info.setLiveRegion(getAccessibilityLiveRegion()); // TODO: These make sense only if we are in an AdapterView but all @@ -6037,6 +6433,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); } + if (isStylusButtonPressable() && isEnabled()) { + info.addAction(AccessibilityAction.ACTION_STYLUS_BUTTON_PRESS); + } + CharSequence text = getIterableTextForAccessibility(); if (text != null && text.length() > 0) { info.setTextSelection(getAccessibilitySelectionStart(), getAccessibilitySelectionEnd()); @@ -7411,6 +7811,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Indicates whether this view reacts to stylus button press events or not. + * + * @return true if the view is stylus button pressable, false otherwise + * @see #setStylusButtonPressable(boolean) + * @attr ref android.R.styleable#View_stylusButtonPressable + */ + public boolean isStylusButtonPressable() { + return (mViewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE; + } + + /** + * Enables or disables stylus button press events for this view. When a view is stylus button + * pressable it reacts to the user touching the screen with a stylus and pressing the first + * stylus button. This event can launch the listener. + * + * @param stylusButtonPressable true to make the view react to a stylus button press, false + * otherwise + * @see #isStylusButtonPressable() + * @attr ref android.R.styleable#View_stylusButtonPressable + */ + public void setStylusButtonPressable(boolean stylusButtonPressable) { + setFlags(stylusButtonPressable ? STYLUS_BUTTON_PRESSABLE : 0, STYLUS_BUTTON_PRESSABLE); + } + + /** * Sets the pressed state for this view and provides a touch coordinate for * animation hinting. * @@ -7825,7 +8250,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void addTouchables(ArrayList<View> views) { final int viewFlags = mViewFlags; - if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) + if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE + || (viewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE) && (viewFlags & ENABLED_MASK) == ENABLED) { views.add(this); } @@ -8440,7 +8866,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public boolean performAccessibilityActionInternal(int action, Bundle arguments) { if (isNestedScrollingEnabled() && (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD - || action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)) { + || action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD + || action == R.id.accessibilityActionScrollUp + || action == R.id.accessibilityActionScrollLeft + || action == R.id.accessibilityActionScrollDown + || action == R.id.accessibilityActionScrollRight)) { if (dispatchNestedPrePerformAccessibilityAction(action, arguments)) { return true; } @@ -8540,6 +8970,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return requestRectangleOnScreen(r, true); } } break; + case R.id.accessibilityActionStylusButtonPress: { + if (isStylusButtonPressable()) { + performStylusButtonPress(); + return true; + } + } break; } return false; } @@ -9697,7 +10133,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } return (viewFlags & CLICKABLE) == CLICKABLE - || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE; + || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE + || (viewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE; } /** @@ -9780,15 +10217,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; + final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { - if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { + if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. - return (((viewFlags & CLICKABLE) == CLICKABLE || - (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); + return (((viewFlags & CLICKABLE) == CLICKABLE + || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) + || (viewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE); } if (mTouchDelegate != null) { @@ -9798,9 +10237,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (((viewFlags & CLICKABLE) == CLICKABLE || - (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { - switch (event.getAction()) { + (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || + (viewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE) { + switch (action) { case MotionEvent.ACTION_UP: + if (mInStylusButtonPress) { + mInStylusButtonPress = false; + mHasPerformedLongPress = false; + } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in @@ -9854,6 +10298,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; + mInStylusButtonPress = false; + + if (performStylusActionOnButtonPress(event)) { + break; + } if (performButtonActionOnTouchDown(event)) { break; @@ -9883,6 +10332,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPressed(false); removeTapCallback(); removeLongPressCallback(); + if (mInStylusButtonPress) { + mInStylusButtonPress = false; + mHasPerformedLongPress = false; + } break; case MotionEvent.ACTION_MOVE: @@ -9898,6 +10351,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPressed(false); } + } else if (performStylusActionOnButtonPress(event)) { + // Check for stylus button press if we're within the view. + break; } break; } @@ -10185,7 +10641,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (accessibilityEnabled) { if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0 - || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0) { + || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 + || (changed & STYLUS_BUTTON_PRESSABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { notifySubtreeAccessibilityStateChangedIfNeeded(); } else { @@ -10909,25 +11366,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * <p>Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is - * completely transparent and 1 means the view is completely opaque.</p> + * Sets the opacity of the view to a value from 0 to 1, where 0 means the view is + * completely transparent and 1 means the view is completely opaque. * - * <p> Note that setting alpha to a translucent value (0 < alpha < 1) can have significant - * performance implications, especially for large views. It is best to use the alpha property - * sparingly and transiently, as in the case of fading animations.</p> + * <p class="note"><strong>Note:</strong> setting alpha to a translucent value (0 < alpha < 1) + * can have significant performance implications, especially for large views. It is best to use + * the alpha property sparingly and transiently, as in the case of fading animations.</p> * * <p>For a view with a frequently changing alpha, such as during a fading animation, it is * strongly recommended for performance reasons to either override - * {@link #hasOverlappingRendering()} to return false if appropriate, or setting a - * {@link #setLayerType(int, android.graphics.Paint) layer type} on the view.</p> + * {@link #hasOverlappingRendering()} to return <code>false</code> if appropriate, or setting a + * {@link #setLayerType(int, android.graphics.Paint) layer type} on the view for the duration + * of the animation. On versions {@link android.os.Build.VERSION_CODES#MNC} and below, + * the default path for rendering an unlayered View with alpha could add multiple milliseconds + * of rendering cost, even for simple or small views. Starting with + * {@link android.os.Build.VERSION_CODES#MNC}, {@link #LAYER_TYPE_HARDWARE} is automatically + * applied to the view at the rendering level.</p> * * <p>If this view overrides {@link #onSetAlpha(int)} to return true, then this view is * responsible for applying the opacity itself.</p> * - * <p>Note that if the view is backed by a - * {@link #setLayerType(int, android.graphics.Paint) layer} and is associated with a - * {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an alpha value less than - * 1.0 will supersede the alpha of the layer paint.</p> + * <p>On versions {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and below, note that if + * the view is backed by a {@link #setLayerType(int, android.graphics.Paint) layer} and is + * associated with a {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an + * alpha value less than 1.0 will supersede the alpha of the layer paint.</p> + * + * <p>Starting with {@link android.os.Build.VERSION_CODES#MNC}, setting a translucent alpha + * value will clip a View to its bounds, unless the View returns <code>false</code> from + * {@link #hasOverlappingRendering}.</p> * * @param alpha The opacity of the view. * @@ -13247,6 +13713,75 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + void getScrollIndicatorBounds(@NonNull Rect out) { + out.left = mScrollX; + out.right = mScrollX + mRight - mLeft; + out.top = mScrollY; + out.bottom = mScrollY + mBottom - mTop; + } + + private void onDrawScrollIndicators(Canvas c) { + if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) { + // No scroll indicators enabled. + return; + } + + final Drawable dr = mScrollIndicatorDrawable; + if (dr == null) { + // Scroll indicators aren't supported here. + return; + } + + final int h = dr.getIntrinsicHeight(); + final int w = dr.getIntrinsicWidth(); + final Rect rect = mAttachInfo.mTmpInvalRect; + getScrollIndicatorBounds(rect); + + if ((mPrivateFlags3 & PFLAG3_SCROLL_INDICATOR_TOP) != 0) { + final boolean canScrollUp = canScrollVertically(-1); + if (canScrollUp) { + dr.setBounds(rect.left, rect.top, rect.right, rect.top + h); + dr.draw(c); + } + } + + if ((mPrivateFlags3 & PFLAG3_SCROLL_INDICATOR_BOTTOM) != 0) { + final boolean canScrollDown = canScrollVertically(1); + if (canScrollDown) { + dr.setBounds(rect.left, rect.bottom - h, rect.right, rect.bottom); + dr.draw(c); + } + } + + final int leftRtl; + final int rightRtl; + if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { + leftRtl = PFLAG3_SCROLL_INDICATOR_END; + rightRtl = PFLAG3_SCROLL_INDICATOR_START; + } else { + leftRtl = PFLAG3_SCROLL_INDICATOR_START; + rightRtl = PFLAG3_SCROLL_INDICATOR_END; + } + + final int leftMask = PFLAG3_SCROLL_INDICATOR_LEFT | leftRtl; + if ((mPrivateFlags3 & leftMask) != 0) { + final boolean canScrollLeft = canScrollHorizontally(-1); + if (canScrollLeft) { + dr.setBounds(rect.left, rect.top, rect.left + w, rect.bottom); + dr.draw(c); + } + } + + final int rightMask = PFLAG3_SCROLL_INDICATOR_RIGHT | rightRtl; + if ((mPrivateFlags3 & rightMask) != 0) { + final boolean canScrollRight = canScrollHorizontally(1); + if (canScrollRight) { + dr.setBounds(rect.right - w, rect.top, rect.right, rect.bottom); + dr.draw(c); + } + } + } + /** * <p>Request the drawing of the horizontal and the vertical scrollbar. The * scrollbars are painted only if they have been awakened first.</p> @@ -14342,15 +14877,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Indicates whether this view has a static layer. A view with layer type - * {@link #LAYER_TYPE_NONE} is a static layer. Other types of layers are - * dynamic. - */ - boolean hasStaticLayer() { - return true; - } - - /** * Indicates what type of layer is currently associated with this view. By default * a view does not have a layer, and the layer type is {@link #LAYER_TYPE_NONE}. * Refer to the documentation of {@link #setLayerType(int, android.graphics.Paint)} @@ -14529,11 +15055,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return !(mAttachInfo == null || mAttachInfo.mHardwareRenderer == null); } - private void updateDisplayListIfDirty() { + /** + * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported) + * @hide + */ + @NonNull + public RenderNode updateDisplayListIfDirty() { final RenderNode renderNode = mRenderNode; if (!canHaveDisplayList()) { // can't populate RenderNode, don't try - return; + return renderNode; } if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 @@ -14547,7 +15078,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchGetDisplayList(); - return; // no work needed + return renderNode; // no work needed } // If we got here, we're recreating it. Mark it as such to ensure that @@ -14596,19 +15127,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; } - } - - /** - * Returns a RenderNode with View draw content recorded, which can be - * used to draw this view again without executing its draw method. - * - * @return A RenderNode ready to replay, or null if caching is not enabled. - * - * @hide - */ - public RenderNode getDisplayList() { - updateDisplayListIfDirty(); - return mRenderNode; + return renderNode; } private void resetDisplayList() { @@ -15370,7 +15889,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (drawingWithRenderNode) { // Delay getting the display list until animation-driven alpha values are // set up and possibly passed on to the view - renderNode = getDisplayList(); + renderNode = updateDisplayListIfDirty(); if (!renderNode.isValid()) { // Uncommon, but possible. If a view is removed from the hierarchy during the call // to getDisplayList(), the display list will be marked invalid and we should not @@ -15468,12 +15987,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (drawingWithRenderNode) { renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha()); } else if (layerType == LAYER_TYPE_NONE) { - int layerFlags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; - if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0) { - layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG; - } canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(), - multipliedAlpha, layerFlags); + multipliedAlpha); } } else { // Alpha is handled by the child directly, clobber the layer's alpha @@ -15509,7 +16024,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!drawingWithDrawingCache) { if (drawingWithRenderNode) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; - ((DisplayListCanvas) canvas).drawRenderNode(renderNode, parentFlags); + ((DisplayListCanvas) canvas).drawRenderNode(renderNode); } else { // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { @@ -17095,6 +17610,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param canvas canvas to draw into */ public void onDrawForeground(Canvas canvas) { + onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; @@ -20764,6 +21280,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Interface definition for a callback to be invoked when a view is touched with a stylus while + * the stylus button is pressed. + */ + public interface OnStylusButtonPressListener { + /** + * Called when a view is touched with a stylus while the stylus button is pressed. + * + * @param v The view that was touched. + * @return true if the callback consumed the stylus button press, false otherwise. + */ + boolean onStylusButtonPress(View v); + } + + /** * Interface definition for a callback to be invoked when the context menu * for this view is being built. */ diff --git a/core/java/android/view/ViewAssistStructure.java b/core/java/android/view/ViewAssistStructure.java index 346b8ec..fccfbb8 100644 --- a/core/java/android/view/ViewAssistStructure.java +++ b/core/java/android/view/ViewAssistStructure.java @@ -40,6 +40,8 @@ public abstract class ViewAssistStructure { public abstract void setLongClickable(boolean state); + public abstract void setStylusButtonPressable(boolean state); + public abstract void setFocusable(boolean state); public abstract void setFocused(boolean state); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 4324e75..babb4e9 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -18,6 +18,8 @@ package android.view; import android.animation.LayoutTransition; import android.annotation.IdRes; +import android.annotation.NonNull; +import android.annotation.UiThread; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -105,6 +107,7 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; * @attr ref android.R.styleable#ViewGroup_splitMotionEvents * @attr ref android.R.styleable#ViewGroup_layoutMode */ +@UiThread public abstract class ViewGroup extends View implements ViewParent, ViewManager { private static final String TAG = "ViewGroup"; @@ -3505,8 +3508,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; - if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) && - child.hasStaticLayer()) { + if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) { recreateChildDisplayList(child); } } @@ -3525,10 +3527,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } private void recreateChildDisplayList(View child) { - child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) - == PFLAG_INVALIDATED; + child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0; child.mPrivateFlags &= ~PFLAG_INVALIDATED; - child.getDisplayList(); + child.updateDisplayListIfDirty(); child.mRecreateDisplayList = false; } @@ -3547,6 +3548,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return child.draw(canvas, this, drawingTime); } + @Override + void getScrollIndicatorBounds(@NonNull Rect out) { + super.getScrollIndicatorBounds(out); + + // If we have padding and we're supposed to clip children to that + // padding, offset the scroll indicators to match our clip bounds. + final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; + if (clipToPadding) { + out.left += mPaddingLeft; + out.right -= mPaddingRight; + out.top += mPaddingTop; + out.bottom -= mPaddingBottom; + } + } + /** * Returns whether this group's children are clipped to their bounds before drawing. * The default value is true. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4158340..fda6e63 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2322,10 +2322,8 @@ public final class ViewRootImpl implements ViewParent, * @hide */ void outputDisplayList(View view) { - RenderNode renderNode = view.getDisplayList(); - if (renderNode != null) { - renderNode.output(); - } + RenderNode renderNode = view.updateDisplayListIfDirty(); + renderNode.output(); } /** diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 417e22c..b0dbeca 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -684,6 +684,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_WINDOWS_CHANGED = 0x00400000; /** + * Represents the event of a stylus button press on a {@link android.view.View}. + */ + public static final int TYPE_VIEW_STYLUS_BUTTON_PRESSED = 0x00800000; + + /** * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: * The type of change is not defined. */ @@ -731,6 +736,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @see #TYPE_TOUCH_INTERACTION_START * @see #TYPE_TOUCH_INTERACTION_END * @see #TYPE_WINDOWS_CHANGED + * @see #TYPE_VIEW_STYLUS_BUTTON_PRESSED */ public static final int TYPES_ALL_MASK = 0xFFFFFFFF; @@ -1396,6 +1402,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("TYPE_WINDOWS_CHANGED"); eventTypeCount++; } break; + case TYPE_VIEW_STYLUS_BUTTON_PRESSED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_STYLUS_BUTTON_PRESSED"); + eventTypeCount++; + } + break; } } if (eventTypeCount > 1) { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 0736ed8..c785149 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -520,6 +520,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000; + private static final int BOOLEAN_PROPERTY_STYLUS_BUTTON_PRESSABLE = 0x00020000; + /** * Bits that provide the id of a virtual descendant of a view. */ @@ -1930,6 +1932,30 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets whether this node is stylus button pressable. + * + * @return True if the node is stylus button pressable. + */ + public boolean isStylusButtonPressable() { + return getBooleanProperty(BOOLEAN_PROPERTY_STYLUS_BUTTON_PRESSABLE); + } + + /** + * Sets whether this node is stylus button pressable. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is made immutable + * before being delivered to an AccessibilityService. + * </p> + * + * @param stylusButtonPressable True if the node is stylus button pressable. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setStylusButtonPressable(boolean stylusButtonPressable) { + setBooleanProperty(BOOLEAN_PROPERTY_STYLUS_BUTTON_PRESSABLE, stylusButtonPressable); + } + + /** * Gets the node's live region mode. * <p> * A live region is a node that contains information that is important for @@ -3117,6 +3143,7 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; selected: ").append(isSelected()); builder.append("; clickable: ").append(isClickable()); builder.append("; longClickable: ").append(isLongClickable()); + builder.append("; stylusButtonPressable: ").append(isStylusButtonPressable()); builder.append("; enabled: ").append(isEnabled()); builder.append("; password: ").append(isPassword()); builder.append("; scrollable: ").append(isScrollable()); @@ -3472,6 +3499,36 @@ public class AccessibilityNodeInfo implements Parcelable { public static final AccessibilityAction ACTION_SCROLL_TO_POSITION = new AccessibilityAction(R.id.accessibilityActionScrollToPosition, null); + /** + * Action to scroll the node content up. + */ + public static final AccessibilityAction ACTION_SCROLL_UP = + new AccessibilityAction(R.id.accessibilityActionScrollUp, null); + + /** + * Action to scroll the node content left. + */ + public static final AccessibilityAction ACTION_SCROLL_LEFT = + new AccessibilityAction(R.id.accessibilityActionScrollLeft, null); + + /** + * Action to scroll the node content down. + */ + public static final AccessibilityAction ACTION_SCROLL_DOWN = + new AccessibilityAction(R.id.accessibilityActionScrollDown, null); + + /** + * Action to scroll the node content right. + */ + public static final AccessibilityAction ACTION_SCROLL_RIGHT = + new AccessibilityAction(R.id.accessibilityActionScrollRight, null); + + /** + * Action that stylus button presses the node. + */ + public static final AccessibilityAction ACTION_STYLUS_BUTTON_PRESS = + new AccessibilityAction(R.id.accessibilityActionStylusButtonPress, null); + private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<>(); static { sStandardActions.add(ACTION_FOCUS); @@ -3498,6 +3555,11 @@ public class AccessibilityNodeInfo implements Parcelable { sStandardActions.add(ACTION_SET_TEXT); sStandardActions.add(ACTION_SHOW_ON_SCREEN); sStandardActions.add(ACTION_SCROLL_TO_POSITION); + sStandardActions.add(ACTION_SCROLL_UP); + sStandardActions.add(ACTION_SCROLL_LEFT); + sStandardActions.add(ACTION_SCROLL_DOWN); + sStandardActions.add(ACTION_SCROLL_RIGHT); + sStandardActions.add(ACTION_STYLUS_BUTTON_PRESS); } private final int mActionId; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index c9d9a8c..c57a53a 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -1506,10 +1506,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (isEnabled()) { if (canScrollUp()) { info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD); + info.addAction(AccessibilityAction.ACTION_SCROLL_UP); info.setScrollable(true); } if (canScrollDown()) { info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN); info.setScrollable(true); } } @@ -1537,14 +1539,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return true; } switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: + case R.id.accessibilityActionScrollDown: { if (isEnabled() && getLastVisiblePosition() < getCount() - 1) { final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION); return true; } } return false; - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: + case R.id.accessibilityActionScrollUp: { if (isEnabled() && mFirstPosition > 0) { final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION); @@ -3638,11 +3642,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te startNestedScroll(SCROLL_AXIS_VERTICAL); - if (mFastScroll != null) { - boolean intercepted = mFastScroll.onTouchEvent(ev); - if (intercepted) { - return true; - } + if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) { + return true; } initVelocityTrackerIfNotExists(); diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java index 30c74c0..d8ce60c 100644 --- a/core/java/android/widget/CursorAdapter.java +++ b/core/java/android/widget/CursorAdapter.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.WorkerThread; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; @@ -425,6 +426,7 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, * @see #getFilterQueryProvider() * @see #setFilterQueryProvider(android.widget.FilterQueryProvider) */ + @WorkerThread public Cursor runQueryOnBackgroundThread(CharSequence constraint) { if (mFilterQueryProvider != null) { return mFilterQueryProvider.runQuery(constraint); diff --git a/core/java/android/widget/DayPickerPagerAdapter.java b/core/java/android/widget/DayPickerPagerAdapter.java index 478fa00..d271af2 100644 --- a/core/java/android/widget/DayPickerPagerAdapter.java +++ b/core/java/android/widget/DayPickerPagerAdapter.java @@ -286,14 +286,10 @@ class DayPickerPagerAdapter extends PagerAdapter { return null; } - private boolean isCalendarInRange(Calendar value) { - return value.compareTo(mMinDate) >= 0 && value.compareTo(mMaxDate) <= 0; - } - private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() { @Override public void onDayClick(SimpleMonthView view, Calendar day) { - if (day != null && isCalendarInRange(day)) { + if (day != null) { setSelectedDay(day); if (mOnDaySelectedListener != null) { diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index 113e597..334afab 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -178,6 +178,13 @@ class DayPickerView extends ViewGroup { }); } + private void updateButtonVisibility(int position) { + final boolean hasPrev = position > 0; + final boolean hasNext = position < (mAdapter.getCount() - 1); + mPrevButton.setVisibility(hasPrev ? View.VISIBLE : View.INVISIBLE); + mNextButton.setVisibility(hasNext ? View.VISIBLE : View.INVISIBLE); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final ViewPager viewPager = mViewPager; @@ -218,12 +225,6 @@ class DayPickerView extends ViewGroup { final int height = bottom - top; mViewPager.layout(0, 0, width, height); - if (mViewPager.getChildCount() < 1) { - leftButton.setVisibility(View.INVISIBLE); - rightButton.setVisibility(View.INVISIBLE); - return; - } - final SimpleMonthView monthView = (SimpleMonthView) mViewPager.getChildAt(0); final int monthHeight = monthView.getMonthHeight(); final int cellWidth = monthView.getCellWidth(); @@ -235,7 +236,6 @@ class DayPickerView extends ViewGroup { final int leftIconTop = monthView.getPaddingTop() + (monthHeight - leftDH) / 2; final int leftIconLeft = monthView.getPaddingLeft() + (cellWidth - leftDW) / 2; leftButton.layout(leftIconLeft, leftIconTop, leftIconLeft + leftDW, leftIconTop + leftDH); - leftButton.setVisibility(View.VISIBLE); final int rightDW = rightButton.getMeasuredWidth(); final int rightDH = rightButton.getMeasuredHeight(); @@ -243,7 +243,6 @@ class DayPickerView extends ViewGroup { final int rightIconRight = width - monthView.getPaddingRight() - (cellWidth - rightDW) / 2; rightButton.layout(rightIconRight - rightDW, rightIconTop, rightIconRight, rightIconTop + rightDH); - rightButton.setVisibility(View.VISIBLE); } public void setDayOfWeekTextAppearance(int resId) { @@ -399,10 +398,7 @@ class DayPickerView extends ViewGroup { @Override public void onPageSelected(int position) { - mPrevButton.setVisibility( - position > 0 ? View.VISIBLE : View.INVISIBLE); - mNextButton.setVisibility( - position < (mAdapter.getCount() - 1) ? View.VISIBLE : View.INVISIBLE); + updateButtonVisibility(position); } }; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index f1be434..8d35b83 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -62,7 +62,6 @@ import android.text.TextUtils; import android.text.method.KeyListener; import android.text.method.MetaKeyKeyListener; import android.text.method.MovementMethod; -import android.text.method.PasswordTransformationMethod; import android.text.method.WordIterator; import android.text.style.EasyEditSpan; import android.text.style.SuggestionRangeSpan; @@ -145,16 +144,16 @@ public class Editor { InputContentType mInputContentType; InputMethodState mInputMethodState; - private static class TextDisplayList { - RenderNode displayList; + private static class TextRenderNode { + RenderNode renderNode; boolean isDirty; - public TextDisplayList(String name) { + public TextRenderNode(String name) { isDirty = true; - displayList = RenderNode.create(name, null); + renderNode = RenderNode.create(name, null); } - boolean needsRecord() { return isDirty || !displayList.isValid(); } + boolean needsRecord() { return isDirty || !renderNode.isValid(); } } - TextDisplayList[] mTextDisplayLists; + TextRenderNode[] mTextRenderNodes; boolean mFrozenWithFocus; boolean mSelectionMoved; @@ -319,6 +318,7 @@ public class Editor { } getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true); + resumeBlink(); } void onDetachedFromWindow() { @@ -328,9 +328,7 @@ public class Editor { hideError(); } - if (mBlink != null) { - mBlink.removeCallbacks(mBlink); - } + suspendBlink(); if (mInsertionPointCursorController != null) { mInsertionPointCursorController.onDetached(); @@ -360,10 +358,10 @@ public class Editor { } private void destroyDisplayListsData() { - if (mTextDisplayLists != null) { - for (int i = 0; i < mTextDisplayLists.length; i++) { - RenderNode displayList = mTextDisplayLists[i] != null - ? mTextDisplayLists[i].displayList : null; + if (mTextRenderNodes != null) { + for (int i = 0; i < mTextRenderNodes.length; i++) { + RenderNode displayList = mTextRenderNodes[i] != null + ? mTextRenderNodes[i].renderNode : null; if (displayList != null && displayList.isValid()) { displayList.destroyDisplayListData(); } @@ -579,7 +577,12 @@ public class Editor { } private void hideCursorControllers() { - if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) { + // When mTextView is not ExtractEditText, we need to distinguish two kinds of focus-lost. + // One is the true focus lost where suggestions pop-up (if any) should be dismissed, and the + // other is an side effect of showing the suggestions pop-up itself. We use isShowingUp() + // to distinguish one from the other. + if (mSuggestionsPopupWindow != null && ((mTextView instanceof ExtractEditText) || + !mSuggestionsPopupWindow.isShowingUp())) { // Should be done before hide insertion point controller since it triggers a show of it mSuggestionsPopupWindow.hide(); } @@ -682,34 +685,6 @@ public class Editor { } } - /** - * Unlike {@link TextView#textCanBeSelected()}, this method is based on the <i>current</i> state - * of the TextView. textCanBeSelected() has to be true (this is one of the conditions to have - * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient. - */ - private boolean canSelectText() { - return hasSelectionController() && mTextView.getText().length() != 0; - } - - /** - * It would be better to rely on the input type for everything. A password inputType should have - * a password transformation. We should hence use isPasswordInputType instead of this method. - * - * We should: - * - Call setInputType in setKeyListener instead of changing the input type directly (which - * would install the correct transformation). - * - Refuse the installation of a non-password transformation in setTransformation if the input - * type is password. - * - * However, this is like this for legacy reasons and we cannot break existing apps. This method - * is useful since it matches what the user can see (obfuscated text or not). - * - * @return true if the current transformation method is of the password type. - */ - private boolean hasPasswordTransformationMethod() { - return mTextView.getTransformationMethod() instanceof PasswordTransformationMethod; - } - private int getWordStart(int offset) { // FIXME - For this and similar methods we're not doing anything to check if there's // a LocaleSpan in the text, this may be something we should try handling or checking for. @@ -758,11 +733,11 @@ public class Editor { * successfully performed. */ private boolean selectCurrentWord() { - if (!canSelectText()) { + if (!mTextView.canSelectText()) { return false; } - if (hasPasswordTransformationMethod()) { + if (mTextView.hasPasswordTransformationMethod()) { // Always select all on a password field. // Cut/copy menu entries are not available for passwords, but being able to select all // is however useful to delete or paste to replace the entire content. @@ -1426,12 +1401,11 @@ public class Editor { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { if (imm.isActive(mTextView)) { - boolean reported = false; if (ims.mContentChanged || ims.mSelectionModeChanged) { // We are in extract mode and the content has changed // in some way... just report complete new text to the // input method. - reported = reportExtractedText(); + reportExtractedText(); } } } @@ -1467,8 +1441,8 @@ public class Editor { firstLine, lastLine); if (layout instanceof DynamicLayout) { - if (mTextDisplayLists == null) { - mTextDisplayLists = ArrayUtils.emptyArray(TextDisplayList.class); + if (mTextRenderNodes == null) { + mTextRenderNodes = ArrayUtils.emptyArray(TextRenderNode.class); } DynamicLayout dynamicLayout = (DynamicLayout) layout; @@ -1489,19 +1463,19 @@ public class Editor { searchStartIndex); // Note how dynamic layout's internal block indices get updated from Editor blockIndices[i] = blockIndex; - if (mTextDisplayLists[blockIndex] != null) { - mTextDisplayLists[blockIndex].isDirty = true; + if (mTextRenderNodes[blockIndex] != null) { + mTextRenderNodes[blockIndex].isDirty = true; } searchStartIndex = blockIndex + 1; } - if (mTextDisplayLists[blockIndex] == null) { - mTextDisplayLists[blockIndex] = - new TextDisplayList("Text " + blockIndex); + if (mTextRenderNodes[blockIndex] == null) { + mTextRenderNodes[blockIndex] = + new TextRenderNode("Text " + blockIndex); } - final boolean blockDisplayListIsInvalid = mTextDisplayLists[blockIndex].needsRecord(); - RenderNode blockDisplayList = mTextDisplayLists[blockIndex].displayList; + final boolean blockDisplayListIsInvalid = mTextRenderNodes[blockIndex].needsRecord(); + RenderNode blockDisplayList = mTextRenderNodes[blockIndex].renderNode; if (i >= indexFirstChangedBlock || blockDisplayListIsInvalid) { final int blockBeginLine = endOfPreviousBlock + 1; final int top = layout.getLineTop(blockBeginLine); @@ -1528,7 +1502,7 @@ public class Editor { // brings this range of text back to the top left corner of the viewport displayListCanvas.translate(-left, -top); layout.drawText(displayListCanvas, blockBeginLine, blockEndLine); - mTextDisplayLists[blockIndex].isDirty = false; + mTextRenderNodes[blockIndex].isDirty = false; // No need to untranslate, previous context is popped after // drawDisplayList } finally { @@ -1543,8 +1517,7 @@ public class Editor { blockDisplayList.setLeftTopRightBottom(left, top, right, bottom); } - ((DisplayListCanvas) canvas).drawRenderNode(blockDisplayList, - 0 /* no child clipping, our TextView parent enforces it */); + ((DisplayListCanvas) canvas).drawRenderNode(blockDisplayList); endOfPreviousBlock = blockEndLine; } @@ -1558,7 +1531,7 @@ public class Editor { private int getAvailableDisplayListIndex(int[] blockIndices, int numberOfBlocks, int searchStartIndex) { - int length = mTextDisplayLists.length; + int length = mTextRenderNodes.length; for (int i = searchStartIndex; i < length; i++) { boolean blockIndexFound = false; for (int j = 0; j < numberOfBlocks; j++) { @@ -1572,7 +1545,7 @@ public class Editor { } // No available index found, the pool has to grow - mTextDisplayLists = GrowingArrayUtils.append(mTextDisplayLists, length, null); + mTextRenderNodes = GrowingArrayUtils.append(mTextRenderNodes, length, null); return length; } @@ -1589,7 +1562,7 @@ public class Editor { * Invalidates all the sub-display lists that overlap the specified character range */ void invalidateTextDisplayList(Layout layout, int start, int end) { - if (mTextDisplayLists != null && layout instanceof DynamicLayout) { + if (mTextRenderNodes != null && layout instanceof DynamicLayout) { final int firstLine = layout.getLineForOffset(start); final int lastLine = layout.getLineForOffset(end); @@ -1609,7 +1582,7 @@ public class Editor { while (i < numberOfBlocks) { final int blockIndex = blockIndices[i]; if (blockIndex != DynamicLayout.INVALID_BLOCK_INDEX) { - mTextDisplayLists[blockIndex].isDirty = true; + mTextRenderNodes[blockIndex].isDirty = true; } if (blockEndLines[i] >= lastLine) break; i++; @@ -1618,9 +1591,9 @@ public class Editor { } void invalidateTextDisplayList() { - if (mTextDisplayLists != null) { - for (int i = 0; i < mTextDisplayLists.length; i++) { - if (mTextDisplayLists[i] != null) mTextDisplayLists[i].isDirty = true; + if (mTextRenderNodes != null) { + for (int i = 0; i < mTextRenderNodes.length; i++) { + if (mTextRenderNodes[i] != null) mTextRenderNodes[i].isDirty = true; } } } @@ -1718,7 +1691,7 @@ public class Editor { return false; } - if (!canSelectText() || !mTextView.requestFocus()) { + if (!mTextView.canSelectText() || !mTextView.requestFocus()) { Log.w(TextView.LOG_TAG, "TextView does not support text selection. Action mode cancelled."); return false; @@ -1949,10 +1922,6 @@ public class Editor { mSuggestionsPopupWindow.show(); } - boolean areSuggestionsShown() { - return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing(); - } - void onScrollChanged() { if (mPositionListener != null) { mPositionListener.onScrollChanged(); @@ -3090,7 +3059,7 @@ public class Editor { MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); } - if (canSelectText() && !hasPasswordTransformationMethod()) { + if (mTextView.canSelectAllText()) { menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). setAlphabeticShortcut('a'). setShowAsAction( @@ -3563,7 +3532,7 @@ public class Editor { return false; } - return isPositionVisible(mPositionX + mHotspotX, mPositionY); + return isPositionVisible(mPositionX + mHotspotX + getHorizontalOffset(), mPositionY); } public abstract int getCurrentCursorOffset(); @@ -3847,6 +3816,10 @@ public class Editor { startSelectionActionModeWithoutSelection(); } } + } else { + if (mSelectionActionMode != null) { + mSelectionActionMode.invalidateContentRect(); + } } hideAfterDelay(); break; @@ -4491,7 +4464,7 @@ public class Editor { private class CorrectionHighlighter { private final Path mPath = new Path(); - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mPaint = new Paint(); private int mStart, mEnd; private long mFadingStartTime; private RectF mTempRectF; @@ -4646,8 +4619,6 @@ public class Editor { } static class InputMethodState { - Rect mCursorRectInWindow = new Rect(); - float[] mTmpOffset = new float[2]; ExtractedTextRequest mExtractedTextRequest; final ExtractedText mExtractedText = new ExtractedText(); int mBatchEditNesting; diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 552b274..f06f3c2 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -1389,7 +1389,8 @@ class FastScroller { // to intercept events. If it does, we will receive a CANCEL // event. if (!mList.isInScrollingContainer()) { - beginDrag(); + // This will get dispatched to onTouchEvent(). Start + // dragging there. return true; } @@ -1406,6 +1407,8 @@ class FastScroller { final float pos = getPosFromMotionEvent(mInitialTouchY); scrollTo(pos); + // This may get dispatched to onTouchEvent(), but it + // doesn't really matter since we'll already be in a drag. return onTouchEvent(ev); } break; @@ -1440,6 +1443,15 @@ class FastScroller { } switch (me.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + if (isPointInside(me.getX(), me.getY())) { + if (!mList.isInScrollingContainer()) { + beginDrag(); + return true; + } + } + } break; + case MotionEvent.ACTION_UP: { if (mPendingDrag >= 0) { // Allow a tap to scroll. diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 324c2aa..0879c5d 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -40,6 +40,8 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; +import com.android.internal.R; + import java.util.List; /** @@ -768,7 +770,8 @@ public class HorizontalScrollView extends FrameLayout { return true; } switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: + case R.id.accessibilityActionScrollRight: { if (!isEnabled()) { return false; } @@ -779,7 +782,8 @@ public class HorizontalScrollView extends FrameLayout { return true; } } return false; - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: + case R.id.accessibilityActionScrollLeft: { if (!isEnabled()) { return false; } @@ -807,10 +811,12 @@ public class HorizontalScrollView extends FrameLayout { if (scrollRange > 0) { info.setScrollable(true); if (isEnabled() && mScrollX > 0) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_LEFT); } if (isEnabled() && mScrollX < scrollRange) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_RIGHT); } } } diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index 2375089..8d8b3a3 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -326,6 +326,16 @@ public class MediaController extends FrameLayout { if (mFfwdButton != null && !mPlayer.canSeekForward()) { mFfwdButton.setEnabled(false); } + // TODO What we really should do is add a canSeek to the MediaPlayerControl interface; + // this scheme can break the case when applications want to allow seek through the + // progress bar but disable forward/backward buttons. + // + // However, currently the flags SEEK_BACKWARD_AVAILABLE, SEEK_FORWARD_AVAILABLE, + // and SEEK_AVAILABLE are all (un)set together; as such the aforementioned issue + // shouldn't arise in existing applications. + if (mProgress != null && !mPlayer.canSeekBackward() && !mPlayer.canSeekForward()) { + mProgress.setEnabled(false); + } } catch (IncompatibleClassChangeError ex) { // We were given an old version of the interface, that doesn't have // the canPause/canSeekXYZ methods. This is OK, it just means we diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 10e4db3..5953a98 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -618,7 +618,15 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // remove based on both its position as well as it's current memory usage, as well // as whether it was directly requested vs. whether it was preloaded by our caching // mechanism. - mIndexRemoteViews.remove(getFarthestPositionFrom(pruneFromPosition, visibleWindow)); + int trimIndex = getFarthestPositionFrom(pruneFromPosition, visibleWindow); + + // Need to check that this is a valid index, to cover the case where you have only + // a single view in the cache, but it's larger than the max memory limit + if (trimIndex < 0) { + break; + } + + mIndexRemoteViews.remove(trimIndex); } // Update the metadata cache diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 2026169..98d61d3 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -819,7 +819,8 @@ public class ScrollView extends FrameLayout { return false; } switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: + case R.id.accessibilityActionScrollDown: { final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop; final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange()); if (targetScrollY != mScrollY) { @@ -827,7 +828,8 @@ public class ScrollView extends FrameLayout { return true; } } return false; - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: + case R.id.accessibilityActionScrollUp: { final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop; final int targetScrollY = Math.max(mScrollY - viewportHeight, 0); if (targetScrollY != mScrollY) { @@ -853,10 +855,13 @@ public class ScrollView extends FrameLayout { if (scrollRange > 0) { info.setScrollable(true); if (mScrollY > 0) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + info.addAction( + AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP); } if (mScrollY < scrollRange) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN); } } } diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index bbf120a..088adbb 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -18,6 +18,7 @@ package android.widget; import static android.widget.SuggestionsAdapter.getColumnString; +import android.annotation.Nullable; import android.app.PendingIntent; import android.app.SearchManager; import android.app.SearchableInfo; @@ -120,6 +121,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { private final Intent mVoiceWebSearchIntent; private final Intent mVoiceAppSearchIntent; + private final CharSequence mDefaultQueryHint; + private OnQueryTextListener mOnQueryChangeListener; private OnCloseListener mOnCloseListener; private OnFocusChangeListener mOnQueryTextFocusChangeListener; @@ -329,10 +332,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { setMaxWidth(maxWidth); } - final CharSequence queryHint = a.getText(R.styleable.SearchView_queryHint); - if (!TextUtils.isEmpty(queryHint)) { - setQueryHint(queryHint); - } + mDefaultQueryHint = a.getText(R.styleable.SearchView_defaultQueryHint); + mQueryHint = a.getText(R.styleable.SearchView_queryHint); final int imeOptions = a.getInt(R.styleable.SearchView_imeOptions, -1); if (imeOptions != -1) { @@ -570,36 +571,48 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } /** - * Sets the hint text to display in the query text field. This overrides any hint specified - * in the SearchableInfo. - * - * @param hint the hint text to display + * Sets the hint text to display in the query text field. This overrides + * any hint specified in the {@link SearchableInfo}. + * <p> + * This value may be specified as an empty string to prevent any query hint + * from being displayed. * + * @param hint the hint text to display or {@code null} to clear * @attr ref android.R.styleable#SearchView_queryHint */ - public void setQueryHint(CharSequence hint) { + public void setQueryHint(@Nullable CharSequence hint) { mQueryHint = hint; updateQueryHint(); } /** - * Gets the hint text to display in the query text field. - * @return the query hint text, if specified, null otherwise. + * Returns the hint text that will be displayed in the query text field. + * <p> + * The displayed query hint is chosen in the following order: + * <ol> + * <li>Non-null value set with {@link #setQueryHint(CharSequence)} + * <li>Value specified in XML using + * {@link android.R.styleable#SearchView_queryHint android:queryHint} + * <li>Valid string resource ID exposed by the {@link SearchableInfo} via + * {@link SearchableInfo#getHintId()} + * <li>Default hint provided by the theme against which the view was + * inflated + * </ol> * + * @return the displayed query hint text, or {@code null} if none set * @attr ref android.R.styleable#SearchView_queryHint */ + @Nullable public CharSequence getQueryHint() { + final CharSequence hint; if (mQueryHint != null) { - return mQueryHint; - } else if (mSearchable != null) { - CharSequence hint = null; - int hintId = mSearchable.getHintId(); - if (hintId != 0) { - hint = getContext().getString(hintId); - } - return hint; + hint = mQueryHint; + } else if (mSearchable != null && mSearchable.getHintId() != 0) { + hint = getContext().getText(mSearchable.getHintId()); + } else { + hint = mDefaultQueryHint; } - return null; + return hint; } /** @@ -1113,20 +1126,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } private void updateQueryHint() { - if (mQueryHint != null) { - mSearchSrcTextView.setHint(getDecoratedHint(mQueryHint)); - } else if (mSearchable != null) { - CharSequence hint = null; - int hintId = mSearchable.getHintId(); - if (hintId != 0) { - hint = getContext().getString(hintId); - } - if (hint != null) { - mSearchSrcTextView.setHint(getDecoratedHint(hint)); - } - } else { - mSearchSrcTextView.setHint(getDecoratedHint("")); - } + final CharSequence hint = getQueryHint(); + mSearchSrcTextView.setHint(getDecoratedHint(hint == null ? "" : hint)); } /** diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index 2778f0f..acf1df9 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -31,6 +31,7 @@ import android.text.TextPaint; import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.IntArray; +import android.util.MathUtils; import android.util.StateSet; import android.view.MotionEvent; import android.view.View; @@ -422,7 +423,8 @@ class SimpleMonthView extends View { int stateMask = 0; - if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { + final boolean isDayEnabled = isDayEnabled(day); + if (isDayEnabled) { stateMask |= StateSet.VIEW_STATE_ENABLED; } @@ -435,8 +437,11 @@ class SimpleMonthView extends View { } else if (mTouchedItem == day) { stateMask |= StateSet.VIEW_STATE_PRESSED; - // Adjust the circle to be centered on the row. - canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDayHighlightPaint); + if (isDayEnabled) { + // Adjust the circle to be centered on the row. + canvas.drawCircle(colCenterRtl, rowCenter, + mDaySelectorRadius, mDayHighlightPaint); + } } final boolean isDayToday = mToday == day; @@ -460,6 +465,14 @@ class SimpleMonthView extends View { } } + private boolean isDayEnabled(int day) { + return day >= mEnabledDayStart && day <= mEnabledDayEnd; + } + + private boolean isValidDayOfMonth(int day) { + return day >= 1 && day <= mDaysInMonth; + } + private static boolean isValidDayOfWeek(int day) { return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY; } @@ -536,13 +549,6 @@ class SimpleMonthView extends View { mWeekStart = mCalendar.getFirstDayOfWeek(); } - if (enabledDayStart > 0 && enabledDayEnd < 32) { - mEnabledDayStart = enabledDayStart; - } - if (enabledDayEnd > 0 && enabledDayEnd < 32 && enabledDayEnd >= enabledDayStart) { - mEnabledDayEnd = enabledDayEnd; - } - // Figure out what day today is. final Calendar today = Calendar.getInstance(); mToday = -1; @@ -554,6 +560,9 @@ class SimpleMonthView extends View { } } + mEnabledDayStart = MathUtils.constrain(enabledDayStart, 1, mDaysInMonth); + mEnabledDayEnd = MathUtils.constrain(enabledDayEnd, mEnabledDayStart, mDaysInMonth); + // Invalidate the old title. mTitle = null; @@ -694,7 +703,7 @@ class SimpleMonthView extends View { final int col = (paddedXRtl * DAYS_IN_WEEK) / mPaddedWidth; final int index = col + row * DAYS_IN_WEEK; final int day = index + 1 - findDayOffset(); - if (day < 1 || day > mDaysInMonth) { + if (!isValidDayOfMonth(day)) { return -1; } @@ -708,7 +717,7 @@ class SimpleMonthView extends View { * @param outBounds the rect to populate with bounds */ private boolean getBoundsForDay(int id, Rect outBounds) { - if (id < 1 || id > mDaysInMonth) { + if (!isValidDayOfMonth(id)) { return false; } @@ -742,7 +751,7 @@ class SimpleMonthView extends View { * @param day the day that was clicked */ private boolean onDayClicked(int day) { - if (day < 0 || day > mDaysInMonth) { + if (!isValidDayOfMonth(day) || !isDayEnabled(day)) { return false; } @@ -774,7 +783,7 @@ class SimpleMonthView extends View { @Override protected int getVirtualViewAt(float x, float y) { final int day = getDayAtLocation((int) (x + 0.5f), (int) (y + 0.5f)); - if (day >= 0) { + if (day != -1) { return day; } return ExploreByTouchHelper.INVALID_ID; @@ -808,7 +817,13 @@ class SimpleMonthView extends View { node.setText(getDayText(virtualViewId)); node.setContentDescription(getDayDescription(virtualViewId)); node.setBoundsInParent(mTempRect); - node.addAction(AccessibilityAction.ACTION_CLICK); + + final boolean isDayEnabled = isDayEnabled(virtualViewId); + if (isDayEnabled) { + node.addAction(AccessibilityAction.ACTION_CLICK); + } + + node.setEnabled(isDayEnabled); if (virtualViewId == mActivatedDay) { // TODO: This should use activated once that's supported. @@ -835,7 +850,7 @@ class SimpleMonthView extends View { * @return a description of the virtual view */ private CharSequence getDayDescription(int id) { - if (id >= 1 && id <= mDaysInMonth) { + if (isValidDayOfMonth(id)) { mTempCalendar.set(mYear, mMonth, id); return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis()); } @@ -850,7 +865,7 @@ class SimpleMonthView extends View { * @return the visible text of the virtual view */ private CharSequence getDayText(int id) { - if (id >= 1 && id <= mDaysInMonth) { + if (isValidDayOfMonth(id)) { return Integer.toString(id); } diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index ae779fe..f94f97c 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -216,7 +216,7 @@ public class Switch extends CompoundButton { public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + mTextPaint = new TextPaint(); final Resources res = getResources(); mTextPaint.density = res.getDisplayMetrics().density; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3e8df08..449173f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -668,11 +668,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final Resources res = getResources(); final CompatibilityInfo compat = res.getCompatibilityInfo(); - mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + mTextPaint = new TextPaint(); mTextPaint.density = res.getDisplayMetrics().density; mTextPaint.setCompatibilityScaling(compat.applicationScale); - mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mHighlightPaint = new Paint(); mHighlightPaint.setCompatibilityScaling(compat.applicationScale); mMovement = getDefaultMovementMethod(); @@ -4569,7 +4569,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @return true if the current transformation method is of the password type. */ - private boolean hasPasswordTransformationMethod() { + boolean hasPasswordTransformationMethod() { return mTransformation instanceof PasswordTransformationMethod; } @@ -6630,12 +6630,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // TODO: code duplication with makeSingleLayout() if (mHintLayout == null) { StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, - mHint.length(), hintWidth) - .setPaint(mTextPaint) + mHint.length(), mTextPaint, hintWidth) .setAlignment(alignment) .setTextDir(mTextDir) - .setSpacingMult(mSpacingMult) - .setSpacingAdd(mSpacingAdd) + .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) .setBreakStrategy(mBreakStrategy); if (mLeftIndents != null || mRightIndents != null) { @@ -6721,12 +6719,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (result == null) { StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, - 0, mTransformed.length(), wantWidth) - .setPaint(mTextPaint) + 0, mTransformed.length(), mTextPaint, wantWidth) .setAlignment(alignment) .setTextDir(mTextDir) - .setSpacingMult(mSpacingMult) - .setSpacingAdd(mSpacingAdd) + .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) .setBreakStrategy(mBreakStrategy); if (mLeftIndents != null || mRightIndents != null) { @@ -8583,7 +8579,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not * sufficient. */ - private boolean canSelectText() { + boolean canSelectText() { return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); } @@ -9199,6 +9195,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } + boolean canSelectAllText() { + return canSelectText() && !hasPasswordTransformationMethod(); + } + boolean selectAllText() { // Need to hide insert point cursor controller before settings selection, otherwise insert // point cursor controller obtains cursor update event and update cursor with cancelling |
