summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accounts/AccountManager.java38
-rw-r--r--core/java/android/app/ActivityThread.java6
-rw-r--r--core/java/android/app/AppOpsManager.java133
-rw-r--r--core/java/android/app/backup/BackupAgent.java276
-rw-r--r--core/java/android/app/backup/FullBackup.java401
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java14
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java23
-rw-r--r--core/java/android/content/pm/IntentFilterVerificationInfo.java30
-rw-r--r--core/java/android/content/pm/PackageParser.java18
-rw-r--r--core/java/android/hardware/Camera.java5
-rw-r--r--core/java/android/hardware/ICameraService.aidl6
-rw-r--r--core/java/android/hardware/SystemSensorManager.java80
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java10
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java37
-rw-r--r--core/java/android/os/Environment.java10
-rw-r--r--core/java/android/os/IPermissionController.aidl1
-rw-r--r--core/java/android/os/Parcel.java20
-rw-r--r--core/java/android/service/voice/VoiceInteractionServiceInfo.java10
-rw-r--r--core/java/android/text/DynamicLayout.java5
-rw-r--r--core/java/android/text/StaticLayout.java161
-rw-r--r--core/java/android/view/GhostView.java6
-rw-r--r--core/java/android/view/TextureView.java3
-rw-r--r--core/java/android/view/ThreadedRenderer.java4
-rw-r--r--core/java/android/view/View.java58
-rw-r--r--core/java/android/view/ViewGroup.java5
-rw-r--r--core/java/android/view/ViewRootImpl.java6
-rw-r--r--core/java/android/widget/AbsListView.java7
-rw-r--r--core/java/android/widget/Editor.java16
-rw-r--r--core/java/android/widget/FastScroller.java14
-rw-r--r--core/java/android/widget/TextView.java12
30 files changed, 1130 insertions, 285 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/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..6bbbf9e 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,14 @@ 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) {
+ return sPermToOp.get(permission);
+ }
+
+ /**
* Retrieve whether the op allows the system (and system ui) to
* bypass the user restriction.
* @hide
@@ -1185,7 +1208,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/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/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/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/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/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/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/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index 4bc97c9..997d586 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -43,7 +43,7 @@ public class VoiceInteractionServiceInfo {
private String mSessionService;
private String mRecognitionService;
private String mSettingsActivity;
- private boolean mSupportsAssistGesture;
+ private boolean mSupportsAssist;
public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp)
throws PackageManager.NameNotFoundException {
@@ -95,8 +95,8 @@ 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);
array.recycle();
if (mSessionService == null) {
@@ -145,7 +145,7 @@ public class VoiceInteractionServiceInfo {
return mSettingsActivity;
}
- public boolean getSupportsAssistGesture() {
- return mSupportsAssistGesture;
+ public boolean getSupportsAssist() {
+ return mSupportsAssist;
}
}
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/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/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/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 25c5127..91e6d68 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -269,7 +269,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 +285,7 @@ public class ThreadedRenderer extends HardwareRenderer {
callbacks.onHardwarePreDraw(canvas);
canvas.insertReorderBarrier();
- canvas.drawRenderNode(view.getDisplayList());
+ canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.insertInorderBarrier();
callbacks.onHardwarePostDraw(canvas);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 81ad5ad..963b7a6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11091,25 +11091,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 +14711,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 +14734,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 +14783,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 +15545,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..ef57dc3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3524,10 +3524,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/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index c9d9a8c..1df43d0 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3638,11 +3638,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/Editor.java b/core/java/android/widget/Editor.java
index 35e7389..9b36b84 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -578,7 +578,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 +1402,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 +1923,6 @@ public class Editor {
mSuggestionsPopupWindow.show();
}
- boolean areSuggestionsShown() {
- return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing();
- }
-
void onScrollChanged() {
if (mPositionListener != null) {
mPositionListener.onScrollChanged();
@@ -4620,8 +4620,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/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) {