diff options
Diffstat (limited to 'core/java/android')
75 files changed, 1689 insertions, 959 deletions
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index ffa36d6..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 @@ -319,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 { @@ -344,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"); @@ -409,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); @@ -431,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); @@ -490,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()); } @@ -576,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) { @@ -621,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) { @@ -659,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 { @@ -684,6 +699,7 @@ public class AccountManager { * * @param account The {@link Account} to be updated. */ + @RequiresPermission(AUTHENTICATE_ACCOUNTS) public boolean notifyAccountAuthenticated(Account account) { if (account == null) throw new IllegalArgumentException("account is null"); @@ -715,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."); @@ -783,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) { @@ -837,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"); @@ -909,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 { @@ -935,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 { @@ -964,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"); @@ -990,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 { @@ -1014,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 { @@ -1039,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"); @@ -1066,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"); @@ -1100,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 { @@ -1174,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) { @@ -1264,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, @@ -1342,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, @@ -1411,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, @@ -1598,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, @@ -1674,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, @@ -1725,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) { @@ -2258,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, @@ -2382,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 e79e20c..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; @@ -891,6 +892,7 @@ 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); @@ -6496,6 +6498,11 @@ public class Activity extends ContextThemeWrapper return (w == null) ? 0 : w.getAttributes().windowAnimations; } + @Override + public void onAttachFragment(Fragment fragment) { + Activity.this.onAttachFragment(fragment); + } + @Nullable @Override public View onFindViewById(int id) { 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 2e45b79..da6d8c5 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1638,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) diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 8a3c9c8..5aa399b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -223,8 +223,12 @@ public class AppOpsManager { 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 = 56; + public static final int _NUM_OP = 58; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -280,9 +284,6 @@ public class AppOpsManager { /** @hide Allows an application to send SMS messages. */ public static final String OPSTR_SEND_SMS = "android:send_sms"; - /** @hide Allows an application to add system alert windows. */ - public static final String OPSTR_SYSTEM_ALERT_WINDOW - = "android:system_alert_window"; /** @hide Required to be able to access the camera device. */ public static final String OPSTR_CAMERA = "android:camera"; @@ -295,6 +296,18 @@ public class AppOpsManager { /** @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 @@ -360,7 +373,9 @@ public class AppOpsManager { OP_ADD_VOICEMAIL, OP_USE_SIP, OP_PROCESS_OUTGOING_CALLS, - OP_USE_FINGERPRINT + OP_USE_FINGERPRINT, + OP_BODY_SENSORS, + OP_READ_CELL_BROADCASTS }; /** @@ -372,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, @@ -419,11 +434,13 @@ public class AppOpsManager { null, null, null, + OPSTR_READ_PHONE_STATE, + OPSTR_ADD_VOICEMAIL, + OPSTR_USE_SIP, null, - null, - null, - null, - null + OPSTR_USE_FINGERPRINT, + OPSTR_BODY_SENSORS, + OPSTR_READ_CELL_BROADCASTS }; /** @@ -486,7 +503,9 @@ public class AppOpsManager { "ADD_VOICEMAIL", "USE_SIP", "PROCESS_OUTGOING_CALLS", - "USE_FINGERPRINT" + "USE_FINGERPRINT", + "BODY_SENSORS", + "READ_CELL_BROADCASTS" }; /** @@ -549,7 +568,9 @@ public class AppOpsManager { Manifest.permission.ADD_VOICEMAIL, Manifest.permission.USE_SIP, Manifest.permission.PROCESS_OUTGOING_CALLS, - Manifest.permission.USE_FINGERPRINT + Manifest.permission.USE_FINGERPRINT, + Manifest.permission.BODY_SENSORS, + Manifest.permission.READ_CELL_BROADCASTS }; /** @@ -613,7 +634,9 @@ public class AppOpsManager { null, // ADD_VOICEMAIL null, // USE_SIP null, // PROCESS_OUTGOING_CALLS - null // USE_FINGERPRINT + null, // USE_FINGERPRINT + null, // BODY_SENSORS + null // READ_CELL_BROADCASTS }; /** @@ -676,7 +699,9 @@ public class AppOpsManager { false, //ADD_VOICEMAIL false, // USE_SIP false, // PROCESS_OUTGOING_CALLS - false // USE_FINGERPRINT + false, // USE_FINGERPRINT + false, // BODY_SENSORS + false // READ_CELL_BROADCASTS }; /** @@ -738,6 +763,8 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED }; @@ -804,37 +831,20 @@ public class AppOpsManager { false, false, false, + false, + false, false }; /** - * This is a mapping from a permission name to public app op name. + * Mapping from an app op name to the app op code. */ - private static final ArrayMap<String, String> sPermToOp = new ArrayMap<>(); - static { - sPermToOp.put(Manifest.permission.ACCESS_COARSE_LOCATION, OPSTR_COARSE_LOCATION); - sPermToOp.put(Manifest.permission.ACCESS_FINE_LOCATION, OPSTR_FINE_LOCATION); - sPermToOp.put(Manifest.permission.PACKAGE_USAGE_STATS, OPSTR_GET_USAGE_STATS); - sPermToOp.put(Manifest.permission.READ_CONTACTS, OPSTR_READ_CONTACTS); - sPermToOp.put(Manifest.permission.WRITE_CONTACTS, OPSTR_WRITE_CONTACTS); - sPermToOp.put(Manifest.permission.READ_CALL_LOG, OPSTR_READ_CALL_LOG); - sPermToOp.put(Manifest.permission.WRITE_CALL_LOG, OPSTR_WRITE_CALL_LOG); - sPermToOp.put(Manifest.permission.READ_CALENDAR, OPSTR_READ_CALENDAR); - sPermToOp.put(Manifest.permission.WRITE_CALENDAR, OPSTR_WRITE_CALENDAR); - sPermToOp.put(Manifest.permission.CALL_PHONE, OPSTR_CALL_PHONE); - sPermToOp.put(Manifest.permission.READ_SMS, OPSTR_READ_SMS); - sPermToOp.put(Manifest.permission.RECEIVE_SMS, OPSTR_RECEIVE_SMS); - sPermToOp.put(Manifest.permission.RECEIVE_MMS, OPSTR_RECEIVE_MMS); - sPermToOp.put(Manifest.permission.RECEIVE_WAP_PUSH, OPSTR_RECEIVE_WAP_PUSH); - sPermToOp.put(Manifest.permission.SEND_SMS, OPSTR_SEND_SMS); - sPermToOp.put(Manifest.permission.SYSTEM_ALERT_WINDOW, OPSTR_SYSTEM_ALERT_WINDOW); - sPermToOp.put(Manifest.permission.CAMERA, OPSTR_CAMERA); - sPermToOp.put(Manifest.permission.RECORD_AUDIO, OPSTR_RECORD_AUDIO); - sPermToOp.put(Manifest.permission.READ_PHONE_STATE, OPSTR_READ_PHONE_STATE); - sPermToOp.put(Manifest.permission.ADD_VOICEMAIL, OPSTR_ADD_VOICEMAIL); - } + private static HashMap<String, Integer> sOpStrToOp = new HashMap<>(); - private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>(); + /** + * Mapping from a permission to the corresponding app op. + */ + private static HashMap<String, Integer> sPermToOp = new HashMap<>(); static { if (sOpToSwitch.length != _NUM_OP) { @@ -874,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); + } + } } /** @@ -922,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 @@ -1185,7 +1209,11 @@ public class AppOpsManager { */ @SystemApi public static String permissionToOp(String permission) { - return sPermToOp.get(permission); + final Integer opCode = sPermToOp.get(permission); + if (opCode == null) { + return null; + } + return sOpToString[opCode]; } /** 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 91d810e..40c5c64 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1314,6 +1314,12 @@ 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); + } } /** diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java index dad2c79..3e753f0 100644 --- a/core/java/android/app/FragmentHostCallback.java +++ b/core/java/android/app/FragmentHostCallback.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.util.ArrayMap; -import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -140,6 +139,14 @@ public abstract class FragmentHostCallback<E> extends FragmentContainer { 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) { @@ -187,14 +194,6 @@ public abstract class FragmentHostCallback<E> extends FragmentContainer { } } - void onFragmentInflate(Fragment fragment, AttributeSet attrs, Bundle savedInstanceState) { - fragment.onInflate(mContext, attrs, savedInstanceState); - } - - void onFragmentAttach(Fragment fragment) { - fragment.onAttach(mContext); - } - void doLoaderStart() { if (mLoadersStarted) { return; diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 62436e9..6b5239d 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -844,13 +844,13 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate f.mFragmentManager = mParent != null ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); f.mCalled = false; - mHost.onFragmentAttach(f); + f.onAttach(mHost.getContext()); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onAttach()"); } if (f.mParentFragment == null) { - mHost.onFragmentAttach(f); + mHost.onAttachFragment(f); } if (!f.mRetaining) { @@ -2107,7 +2107,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate fragment.mTag = tag; fragment.mInLayout = true; fragment.mFragmentManager = this; - mHost.onFragmentInflate(fragment, 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 @@ -2124,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) { - mHost.onFragmentInflate(fragment, 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/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/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 9f71ea5..4b72dc3 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 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 9b4dcc6..0a77868 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1692,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/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 dad486d..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; @@ -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/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 0b24594..94b0223 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -96,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/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/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 9596c42..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( 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/Resources.java b/core/java/android/content/res/Resources.java index 6e77e33..ae41b69 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1731,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/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index f6791a4..e9564b3 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -580,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 @@ -678,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 @@ -688,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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 2d63e3f..d8c3361 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2571,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 { 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 e3e16eb..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 diff --git a/core/java/android/os/IPermissionController.aidl b/core/java/android/os/IPermissionController.aidl index 0cc1603..5e8590a 100644 --- a/core/java/android/os/IPermissionController.aidl +++ b/core/java/android/os/IPermissionController.aidl @@ -21,4 +21,5 @@ package android.os; 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/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 50e7d1c..3fdabee 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -584,7 +584,7 @@ public class StorageManager { // Nickname always takes precedence when defined if (!TextUtils.isEmpty(vol.fsUuid)) { final VolumeRecord rec = findRecordByUuid(vol.fsUuid); - if (!TextUtils.isEmpty(rec.nickname)) { + if (rec != null && !TextUtils.isEmpty(rec.nickname)) { return rec.nickname; } } 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/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 f09f4d2..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 }; @@ -67,6 +68,8 @@ public class ZenModeConfig implements Parcelable { 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"; @@ -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) @@ -234,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) @@ -243,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) { @@ -314,9 +324,19 @@ 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, false /*conditionRequired*/); @@ -342,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) { @@ -432,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; @@ -494,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; } @@ -510,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) { @@ -532,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) { @@ -692,7 +717,6 @@ public class ZenModeConfig implements Parcelable { .authority(SYSTEM_AUTHORITY) .appendPath(EVENT_PATH) .appendQueryParameter("calendar", Long.toString(event.calendar)) - .appendQueryParameter("attendance", Integer.toString(event.attendance)) .appendQueryParameter("reply", Integer.toString(event.reply)) .build(); } @@ -710,22 +734,16 @@ public class ZenModeConfig implements Parcelable { if (!isEvent) return null; final EventInfo rt = new EventInfo(); rt.calendar = tryParseLong(conditionId.getQueryParameter("calendar"), 0L); - rt.attendance = tryParseInt(conditionId.getQueryParameter("attendance"), 0); rt.reply = tryParseInt(conditionId.getQueryParameter("reply"), 0); return rt; } public static class EventInfo { - public static final int ATTENDANCE_REQUIRED_OR_OPTIONAL = 0; - public static final int ATTENDANCE_REQUIRED = 1; - public static final int ATTENDANCE_OPTIONAL = 2; - - public static final int REPLY_ANY = 0; - public static final int REPLY_ANY_EXCEPT_NO = 1; + 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 attendance; public int reply; @Override @@ -738,14 +756,12 @@ public class ZenModeConfig implements Parcelable { if (!(o instanceof EventInfo)) return false; final EventInfo other = (EventInfo) o; return calendar == other.calendar - && attendance == other.attendance && reply == other.reply; } public EventInfo copy() { final EventInfo rt = new EventInfo(); rt.calendar = calendar; - rt.attendance = attendance; rt.reply = reply; return rt; } 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/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 ca37d49..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); } } - constructor.setAccessible(true); 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/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java index 46dd857..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 /////////////////////////////////////////////////////////////////////////// @@ -271,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); } @@ -281,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/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 1503728..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. 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/TextureView.java b/core/java/android/view/TextureView.java index 6db46e9..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; } } 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 81ad5ad..75dc0a2 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -28,6 +28,7 @@ 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; @@ -700,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; @@ -8595,7 +8597,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; } @@ -11091,25 +11097,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. * @@ -14702,11 +14717,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 @@ -14720,7 +14740,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 @@ -14769,19 +14789,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() { @@ -15543,7 +15551,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 diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index d0d4201..f240fd6 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -18,6 +18,7 @@ package android.view; import android.animation.LayoutTransition; import android.annotation.IdRes; +import android.annotation.UiThread; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -105,6 +106,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"; @@ -3524,10 +3526,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; } 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/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index bf4b7ae..c785149 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -3500,6 +3500,30 @@ public class AccessibilityNodeInfo implements Parcelable { 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 = @@ -3531,6 +3555,10 @@ 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); } 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/Editor.java b/core/java/android/widget/Editor.java index 35e7389..8d35b83 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -318,6 +318,7 @@ public class Editor { } getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true); + resumeBlink(); } void onDetachedFromWindow() { @@ -327,9 +328,7 @@ public class Editor { hideError(); } - if (mBlink != null) { - mBlink.removeCallbacks(mBlink); - } + suspendBlink(); if (mInsertionPointCursorController != null) { mInsertionPointCursorController.onDetached(); @@ -578,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(); } @@ -1397,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(); } } } @@ -1919,10 +1922,6 @@ public class Editor { mSuggestionsPopupWindow.show(); } - boolean areSuggestionsShown() { - return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing(); - } - void onScrollChanged() { if (mPositionListener != null) { mPositionListener.onScrollChanged(); @@ -4620,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/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/TextView.java b/core/java/android/widget/TextView.java index 774a864..449173f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -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) { |
