Merge "Fix bug #4509967 (layout_gravity not always respected)"
diff --git a/Android.mk b/Android.mk
index c838600..270ee2e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -110,6 +110,7 @@
core/java/android/net/IConnectivityManager.aidl \
core/java/android/net/INetworkManagementEventObserver.aidl \
core/java/android/net/IThrottleManager.aidl \
+ core/java/android/net/INetworkPolicyListener.aidl \
core/java/android/net/INetworkPolicyManager.aidl \
core/java/android/nfc/ILlcpConnectionlessSocket.aidl \
core/java/android/nfc/ILlcpServiceSocket.aidl \
diff --git a/api/current.txt b/api/current.txt
index a621508..6550007 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -179,9 +179,9 @@
public static final class R.attr {
ctor public R.attr();
field public static final int absListViewStyle = 16842858; // 0x101006a
- field public static final int accessibilityEventTypes = 16843642; // 0x101037a
- field public static final int accessibilityFeedbackType = 16843644; // 0x101037c
- field public static final int accessibilityFlags = 16843646; // 0x101037e
+ field public static final int accessibilityEventTypes = 16843643; // 0x101037b
+ field public static final int accessibilityFeedbackType = 16843645; // 0x101037d
+ field public static final int accessibilityFlags = 16843647; // 0x101037f
field public static final int accountPreferences = 16843423; // 0x101029f
field public static final int accountType = 16843407; // 0x101028f
field public static final int action = 16842797; // 0x101002d
@@ -201,6 +201,7 @@
field public static final int actionModeCopyDrawable = 16843538; // 0x1010312
field public static final int actionModeCutDrawable = 16843537; // 0x1010311
field public static final int actionModePasteDrawable = 16843539; // 0x1010313
+ field public static final int actionModeSelectAllDrawable = 16843641; // 0x1010379
field public static final int actionOverflowButtonStyle = 16843510; // 0x10102f6
field public static final int actionViewClass = 16843516; // 0x10102fc
field public static final int activatedBackgroundIndicator = 16843517; // 0x10102fd
@@ -272,7 +273,7 @@
field public static final int cacheColorHint = 16843009; // 0x1010101
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
- field public static final int canRetrieveWindowContent = 16843647; // 0x101037f
+ field public static final int canRetrieveWindowContent = 16843648; // 0x1010380
field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
field public static final deprecated int capitalize = 16843113; // 0x1010169
field public static final int centerBright = 16842956; // 0x10100cc
@@ -530,7 +531,7 @@
field public static final int installLocation = 16843447; // 0x10102b7
field public static final int interpolator = 16843073; // 0x1010141
field public static final int isAlwaysSyncable = 16843571; // 0x1010333
- field public static final int isAuxiliary = 16843641; // 0x1010379
+ field public static final int isAuxiliary = 16843642; // 0x101037a
field public static final int isDefault = 16843297; // 0x1010221
field public static final int isIndicator = 16843079; // 0x1010147
field public static final int isModifier = 16843334; // 0x1010246
@@ -659,7 +660,7 @@
field public static final int nextFocusUp = 16842979; // 0x10100e3
field public static final int noHistory = 16843309; // 0x101022d
field public static final int normalScreens = 16843397; // 0x1010285
- field public static final int notificationTimeout = 16843645; // 0x101037d
+ field public static final int notificationTimeout = 16843646; // 0x101037e
field public static final int numColumns = 16843032; // 0x1010118
field public static final int numStars = 16843076; // 0x1010144
field public static final deprecated int numeric = 16843109; // 0x1010165
@@ -676,7 +677,7 @@
field public static final int overScrollFooter = 16843459; // 0x10102c3
field public static final int overScrollHeader = 16843458; // 0x10102c2
field public static final int overScrollMode = 16843457; // 0x10102c1
- field public static final int packageNames = 16843643; // 0x101037b
+ field public static final int packageNames = 16843644; // 0x101037c
field public static final int padding = 16842965; // 0x10100d5
field public static final int paddingBottom = 16842969; // 0x10100d9
field public static final int paddingLeft = 16842966; // 0x10100d6
@@ -6813,6 +6814,7 @@
method public void setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
method public void setDistinct(boolean);
method public void setProjectionMap(java.util.Map<java.lang.String, java.lang.String>);
+ method public void setStrict(boolean);
method public void setTables(java.lang.String);
}
@@ -11040,6 +11042,7 @@
method public static android.net.NetworkInfo.DetailedState valueOf(java.lang.String);
method public static final android.net.NetworkInfo.DetailedState[] values();
enum_constant public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
+ enum_constant public static final android.net.NetworkInfo.DetailedState BLOCKED;
enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTED;
enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTING;
enum_constant public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index f3f04326..e81f799 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -34,11 +34,12 @@
IBackupManager mBackupManager;
public static void main(String[] args) {
+ Log.d(TAG, "Beginning: " + args[0]);
mArgs = args;
try {
new Backup().run();
} catch (Exception e) {
- Log.e(TAG, "Error running backup", e);
+ Log.e(TAG, "Error running backup/restore", e);
}
Log.d(TAG, "Finished.");
}
@@ -46,7 +47,7 @@
public void run() {
mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
if (mBackupManager == null) {
- System.err.println("ERROR: could not contact backup manager");
+ Log.e(TAG, "Can't obtain Backup Manager binder");
return;
}
@@ -56,7 +57,7 @@
} else if (arg.equals("restore")) {
doFullRestore();
} else {
- System.err.println("ERROR: invalid operation '" + arg + "'");
+ Log.e(TAG, "Invalid operation '" + arg + "'");
}
}
@@ -80,7 +81,6 @@
} else if ("-all".equals(arg)) {
doEverything = true;
} else {
- System.err.println("WARNING: unknown backup flag " + arg);
Log.w(TAG, "Unknown backup flag " + arg);
continue;
}
@@ -91,13 +91,10 @@
}
if (doEverything && packages.size() > 0) {
- System.err.println("WARNING: -all used with explicit backup package set");
Log.w(TAG, "-all passed for backup along with specific package names");
}
if (!doEverything && !saveShared && packages.size() == 0) {
- System.err.println(
- "ERROR: no packages supplied for backup and neither -shared nor -all given");
Log.e(TAG, "no backup packages supplied and neither -shared nor -all given");
return;
}
@@ -108,13 +105,22 @@
mBackupManager.fullBackup(fd, saveApks, saveShared, doEverything,
packages.toArray(packArray));
} catch (IOException e) {
- System.err.println("ERROR: cannot dup System.out");
+ Log.e(TAG, "Can't dup out");
} catch (RemoteException e) {
- System.err.println("ERROR: unable to invoke backup manager service");
+ Log.e(TAG, "Unable to invoke backup manager for backup");
}
}
private void doFullRestore() {
+ // No arguments to restore
+ try {
+ ParcelFileDescriptor fd = ParcelFileDescriptor.dup(FileDescriptor.in);
+ mBackupManager.fullRestore(fd);
+ } catch (IOException e) {
+ Log.e(TAG, "Can't dup System.in");
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to invoke backup manager for restore");
+ }
}
private String nextArg() {
diff --git a/cmds/keystore/Android.mk b/cmds/keystore/Android.mk
index 15a199f..67dd9f8 100644
--- a/cmds/keystore/Android.mk
+++ b/cmds/keystore/Android.mk
@@ -19,14 +19,14 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := keystore.c
+LOCAL_SRC_FILES := keystore.cpp
LOCAL_C_INCLUDES := external/openssl/include
LOCAL_SHARED_LIBRARIES := libcutils libcrypto
LOCAL_MODULE:= keystore
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := keystore_cli.c
+LOCAL_SRC_FILES := keystore_cli.cpp
LOCAL_C_INCLUDES := external/openssl/include
LOCAL_SHARED_LIBRARIES := libcutils libcrypto
LOCAL_MODULE:= keystore_cli
diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c
deleted file mode 100644
index e34053b..0000000
--- a/cmds/keystore/keystore.c
+++ /dev/null
@@ -1,590 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <unistd.h>
-#include <signal.h>
-#include <errno.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <arpa/inet.h>
-
-#include <openssl/aes.h>
-#include <openssl/evp.h>
-#include <openssl/md5.h>
-
-#define LOG_TAG "keystore"
-#include <cutils/log.h>
-#include <cutils/sockets.h>
-#include <private/android_filesystem_config.h>
-
-#include "keystore.h"
-
-/* KeyStore is a secured storage for key-value pairs. In this implementation,
- * each file stores one key-value pair. Keys are encoded in file names, and
- * values are encrypted with checksums. The encryption key is protected by a
- * user-defined password. To keep things simple, buffers are always larger than
- * the maximum space we needed, so boundary checks on buffers are omitted. */
-
-#define KEY_SIZE ((NAME_MAX - 15) / 2)
-#define VALUE_SIZE 32768
-#define PASSWORD_SIZE VALUE_SIZE
-
-/* Here is the encoding of keys. This is necessary in order to allow arbitrary
- * characters in keys. Characters in [0-~] are not encoded. Others are encoded
- * into two bytes. The first byte is one of [+-.] which represents the first
- * two bits of the character. The second byte encodes the rest of the bits into
- * [0-o]. Therefore in the worst case the length of a key gets doubled. Note
- * that Base64 cannot be used here due to the need of prefix match on keys. */
-
-static int encode_key(char *out, uint8_t *in, int length)
-{
- int i;
- for (i = length; i > 0; --i, ++in, ++out) {
- if (*in >= '0' && *in <= '~') {
- *out = *in;
- } else {
- *out = '+' + (*in >> 6);
- *++out = '0' + (*in & 0x3F);
- ++length;
- }
- }
- *out = 0;
- return length;
-}
-
-static int decode_key(uint8_t *out, char *in, int length)
-{
- int i;
- for (i = 0; i < length; ++i, ++in, ++out) {
- if (*in >= '0' && *in <= '~') {
- *out = *in;
- } else {
- *out = (*in - '+') << 6;
- *out |= (*++in - '0') & 0x3F;
- --length;
- }
- }
- *out = 0;
- return length;
-}
-
-/* Here is the protocol used in both requests and responses:
- * code [length_1 message_1 ... length_n message_n] end-of-file
- * where code is one byte long and lengths are unsigned 16-bit integers in
- * network order. Thus the maximum length of a message is 65535 bytes. */
-
-static int the_socket = -1;
-
-static int recv_code(int8_t *code)
-{
- return recv(the_socket, code, 1, 0) == 1;
-}
-
-static int recv_message(uint8_t *message, int length)
-{
- uint8_t bytes[2];
- if (recv(the_socket, &bytes[0], 1, 0) != 1 ||
- recv(the_socket, &bytes[1], 1, 0) != 1) {
- return -1;
- } else {
- int offset = bytes[0] << 8 | bytes[1];
- if (length < offset) {
- return -1;
- }
- length = offset;
- offset = 0;
- while (offset < length) {
- int n = recv(the_socket, &message[offset], length - offset, 0);
- if (n <= 0) {
- return -1;
- }
- offset += n;
- }
- }
- return length;
-}
-
-static int recv_end_of_file()
-{
- uint8_t byte;
- return recv(the_socket, &byte, 1, 0) == 0;
-}
-
-static void send_code(int8_t code)
-{
- send(the_socket, &code, 1, 0);
-}
-
-static void send_message(uint8_t *message, int length)
-{
- uint16_t bytes = htons(length);
- send(the_socket, &bytes, 2, 0);
- send(the_socket, message, length, 0);
-}
-
-/* Here is the file format. There are two parts in blob.value, the secret and
- * the description. The secret is stored in ciphertext, and its original size
- * can be found in blob.length. The description is stored after the secret in
- * plaintext, and its size is specified in blob.info. The total size of the two
- * parts must be no more than VALUE_SIZE bytes. The first three bytes of the
- * file are reserved for future use and are always set to zero. Fields other
- * than blob.info, blob.length, and blob.value are modified by encrypt_blob()
- * and decrypt_blob(). Thus they should not be accessed from outside. */
-
-static int the_entropy = -1;
-
-static struct __attribute__((packed)) {
- uint8_t reserved[3];
- uint8_t info;
- uint8_t vector[AES_BLOCK_SIZE];
- uint8_t encrypted[0];
- uint8_t digest[MD5_DIGEST_LENGTH];
- uint8_t digested[0];
- int32_t length;
- uint8_t value[VALUE_SIZE + AES_BLOCK_SIZE];
-} blob;
-
-static int8_t encrypt_blob(char *name, AES_KEY *aes_key)
-{
- uint8_t vector[AES_BLOCK_SIZE];
- int length;
- int fd;
-
- if (read(the_entropy, blob.vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
- return SYSTEM_ERROR;
- }
-
- length = blob.length + (blob.value - blob.encrypted);
- length = (length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
-
- if (blob.info != 0) {
- memmove(&blob.encrypted[length], &blob.value[blob.length], blob.info);
- }
-
- blob.length = htonl(blob.length);
- MD5(blob.digested, length - (blob.digested - blob.encrypted), blob.digest);
-
- memcpy(vector, blob.vector, AES_BLOCK_SIZE);
- AES_cbc_encrypt(blob.encrypted, blob.encrypted, length, aes_key, vector,
- AES_ENCRYPT);
-
- memset(blob.reserved, 0, sizeof(blob.reserved));
- length += (blob.encrypted - (uint8_t *)&blob) + blob.info;
-
- fd = open(".tmp", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
- length -= write(fd, &blob, length);
- close(fd);
- return (length || rename(".tmp", name)) ? SYSTEM_ERROR : NO_ERROR;
-}
-
-static int8_t decrypt_blob(char *name, AES_KEY *aes_key)
-{
- int fd = open(name, O_RDONLY);
- int length;
-
- if (fd == -1) {
- return (errno == ENOENT) ? KEY_NOT_FOUND : SYSTEM_ERROR;
- }
- length = read(fd, &blob, sizeof(blob));
- close(fd);
-
- length -= (blob.encrypted - (uint8_t *)&blob) + blob.info;
- if (length < blob.value - blob.encrypted || length % AES_BLOCK_SIZE != 0) {
- return VALUE_CORRUPTED;
- }
-
- AES_cbc_encrypt(blob.encrypted, blob.encrypted, length, aes_key,
- blob.vector, AES_DECRYPT);
- length -= blob.digested - blob.encrypted;
- if (memcmp(blob.digest, MD5(blob.digested, length, NULL),
- MD5_DIGEST_LENGTH)) {
- return VALUE_CORRUPTED;
- }
-
- length -= blob.value - blob.digested;
- blob.length = ntohl(blob.length);
- if (blob.length < 0 || blob.length > length) {
- return VALUE_CORRUPTED;
- }
- if (blob.info != 0) {
- memmove(&blob.value[blob.length], &blob.value[length], blob.info);
- }
- return NO_ERROR;
-}
-
-/* Here are the actions. Each of them is a function without arguments. All
- * information is defined in global variables, which are set properly before
- * performing an action. The number of parameters required by each action is
- * fixed and defined in a table. If the return value of an action is positive,
- * it will be treated as a response code and transmitted to the client. Note
- * that the lengths of parameters are checked when they are received, so
- * boundary checks on parameters are omitted. */
-
-#define MAX_PARAM 2
-#define MAX_RETRY 4
-
-static uid_t uid = -1;
-static int8_t state = UNINITIALIZED;
-static int8_t retry = MAX_RETRY;
-
-static struct {
- int length;
- uint8_t value[VALUE_SIZE];
-} params[MAX_PARAM];
-
-static AES_KEY encryption_key;
-static AES_KEY decryption_key;
-
-static int8_t test()
-{
- return state;
-}
-
-static int8_t get()
-{
- char name[NAME_MAX];
- int n = sprintf(name, "%u_", uid);
- encode_key(&name[n], params[0].value, params[0].length);
- n = decrypt_blob(name, &decryption_key);
- if (n != NO_ERROR) {
- return n;
- }
- send_code(NO_ERROR);
- send_message(blob.value, blob.length);
- return -NO_ERROR;
-}
-
-static int8_t insert()
-{
- char name[NAME_MAX];
- int n = sprintf(name, "%u_", uid);
- encode_key(&name[n], params[0].value, params[0].length);
- blob.info = 0;
- blob.length = params[1].length;
- memcpy(blob.value, params[1].value, params[1].length);
- return encrypt_blob(name, &encryption_key);
-}
-
-static int8_t delete()
-{
- char name[NAME_MAX];
- int n = sprintf(name, "%u_", uid);
- encode_key(&name[n], params[0].value, params[0].length);
- return (unlink(name) && errno != ENOENT) ? SYSTEM_ERROR : NO_ERROR;
-}
-
-static int8_t exist()
-{
- char name[NAME_MAX];
- int n = sprintf(name, "%u_", uid);
- encode_key(&name[n], params[0].value, params[0].length);
- if (access(name, R_OK) == -1) {
- return (errno != ENOENT) ? SYSTEM_ERROR : KEY_NOT_FOUND;
- }
- return NO_ERROR;
-}
-
-static int8_t saw()
-{
- DIR *dir = opendir(".");
- struct dirent *file;
- char name[NAME_MAX];
- int n;
-
- if (!dir) {
- return SYSTEM_ERROR;
- }
- n = sprintf(name, "%u_", uid);
- n += encode_key(&name[n], params[0].value, params[0].length);
- send_code(NO_ERROR);
- while ((file = readdir(dir)) != NULL) {
- if (!strncmp(name, file->d_name, n)) {
- char *p = &file->d_name[n];
- params[0].length = decode_key(params[0].value, p, strlen(p));
- send_message(params[0].value, params[0].length);
- }
- }
- closedir(dir);
- return -NO_ERROR;
-}
-
-static int8_t reset()
-{
- DIR *dir = opendir(".");
- struct dirent *file;
-
- memset(&encryption_key, 0, sizeof(encryption_key));
- memset(&decryption_key, 0, sizeof(decryption_key));
- state = UNINITIALIZED;
- retry = MAX_RETRY;
-
- if (!dir) {
- return SYSTEM_ERROR;
- }
- while ((file = readdir(dir)) != NULL) {
- unlink(file->d_name);
- }
- closedir(dir);
- return NO_ERROR;
-}
-
-#define MASTER_KEY_FILE ".masterkey"
-#define MASTER_KEY_SIZE 16
-#define SALT_SIZE 16
-
-static void set_key(uint8_t *key, uint8_t *password, int length, uint8_t *salt)
-{
- if (salt) {
- PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, salt, SALT_SIZE,
- 8192, MASTER_KEY_SIZE, key);
- } else {
- PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, (uint8_t *)"keystore",
- sizeof("keystore"), 1024, MASTER_KEY_SIZE, key);
- }
-}
-
-/* Here is the history. To improve the security, the parameters to generate the
- * master key has been changed. To make a seamless transition, we update the
- * file using the same password when the user unlock it for the first time. If
- * any thing goes wrong during the transition, the new file will not overwrite
- * the old one. This avoids permanent damages of the existing data. */
-
-static int8_t password()
-{
- uint8_t key[MASTER_KEY_SIZE];
- AES_KEY aes_key;
- int8_t response = SYSTEM_ERROR;
-
- if (state == UNINITIALIZED) {
- if (read(the_entropy, blob.value, MASTER_KEY_SIZE) != MASTER_KEY_SIZE) {
- return SYSTEM_ERROR;
- }
- } else {
- int fd = open(MASTER_KEY_FILE, O_RDONLY);
- uint8_t *salt = NULL;
- if (fd != -1) {
- int length = read(fd, &blob, sizeof(blob));
- close(fd);
- if (length > SALT_SIZE && blob.info == SALT_SIZE) {
- salt = (uint8_t *)&blob + length - SALT_SIZE;
- }
- }
-
- set_key(key, params[0].value, params[0].length, salt);
- AES_set_decrypt_key(key, MASTER_KEY_SIZE * 8, &aes_key);
- response = decrypt_blob(MASTER_KEY_FILE, &aes_key);
- if (response == SYSTEM_ERROR) {
- return SYSTEM_ERROR;
- }
- if (response != NO_ERROR || blob.length != MASTER_KEY_SIZE) {
- if (retry <= 0) {
- reset();
- return UNINITIALIZED;
- }
- return WRONG_PASSWORD + --retry;
- }
-
- if (!salt && params[1].length == -1) {
- params[1] = params[0];
- }
- }
-
- if (params[1].length == -1) {
- memcpy(key, blob.value, MASTER_KEY_SIZE);
- } else {
- uint8_t *salt = &blob.value[MASTER_KEY_SIZE];
- if (read(the_entropy, salt, SALT_SIZE) != SALT_SIZE) {
- return SYSTEM_ERROR;
- }
-
- set_key(key, params[1].value, params[1].length, salt);
- AES_set_encrypt_key(key, MASTER_KEY_SIZE * 8, &aes_key);
- memcpy(key, blob.value, MASTER_KEY_SIZE);
- blob.info = SALT_SIZE;
- blob.length = MASTER_KEY_SIZE;
- response = encrypt_blob(MASTER_KEY_FILE, &aes_key);
- }
-
- if (response == NO_ERROR) {
- AES_set_encrypt_key(key, MASTER_KEY_SIZE * 8, &encryption_key);
- AES_set_decrypt_key(key, MASTER_KEY_SIZE * 8, &decryption_key);
- state = NO_ERROR;
- retry = MAX_RETRY;
- }
- return response;
-}
-
-static int8_t lock()
-{
- memset(&encryption_key, 0, sizeof(encryption_key));
- memset(&decryption_key, 0, sizeof(decryption_key));
- state = LOCKED;
- return NO_ERROR;
-}
-
-static int8_t unlock()
-{
- params[1].length = -1;
- return password();
-}
-
-/* Here are the permissions, actions, users, and the main function. */
-
-enum perm {
- TEST = 1,
- GET = 2,
- INSERT = 4,
- DELETE = 8,
- EXIST = 16,
- SAW = 32,
- RESET = 64,
- PASSWORD = 128,
- LOCK = 256,
- UNLOCK = 512,
-};
-
-static struct action {
- int8_t (*run)();
- int8_t code;
- int8_t state;
- uint32_t perm;
- int lengths[MAX_PARAM];
-} actions[] = {
- {test, 't', 0, TEST, {0}},
- {get, 'g', NO_ERROR, GET, {KEY_SIZE}},
- {insert, 'i', NO_ERROR, INSERT, {KEY_SIZE, VALUE_SIZE}},
- {delete, 'd', 0, DELETE, {KEY_SIZE}},
- {exist, 'e', 0, EXIST, {KEY_SIZE}},
- {saw, 's', 0, SAW, {KEY_SIZE}},
- {reset, 'r', 0, RESET, {0}},
- {password, 'p', 0, PASSWORD, {PASSWORD_SIZE, PASSWORD_SIZE}},
- {lock, 'l', NO_ERROR, LOCK, {0}},
- {unlock, 'u', LOCKED, UNLOCK, {PASSWORD_SIZE}},
- {NULL, 0 , 0, 0, {0}},
-};
-
-static struct user {
- uid_t uid;
- uid_t euid;
- uint32_t perms;
-} users[] = {
- {AID_SYSTEM, ~0, ~GET},
- {AID_VPN, AID_SYSTEM, GET},
- {AID_WIFI, AID_SYSTEM, GET},
- {AID_ROOT, AID_SYSTEM, GET},
- {AID_KEYCHAIN, AID_SYSTEM, TEST | GET | SAW},
- {~0, ~0, TEST | GET | INSERT | DELETE | EXIST | SAW},
-};
-
-static int8_t process(int8_t code) {
- struct user *user = users;
- struct action *action = actions;
- int i;
-
- while (~user->uid && user->uid != uid) {
- ++user;
- }
- while (action->code && action->code != code) {
- ++action;
- }
- if (!action->code) {
- return UNDEFINED_ACTION;
- }
- if (!(action->perm & user->perms)) {
- return PERMISSION_DENIED;
- }
- if (action->state && action->state != state) {
- return state;
- }
- if (~user->euid) {
- uid = user->euid;
- }
- for (i = 0; i < MAX_PARAM && action->lengths[i]; ++i) {
- params[i].length = recv_message(params[i].value, action->lengths[i]);
- if (params[i].length == -1) {
- return PROTOCOL_ERROR;
- }
- }
- if (!recv_end_of_file()) {
- return PROTOCOL_ERROR;
- }
- return action->run();
-}
-
-#define RANDOM_DEVICE "/dev/urandom"
-
-int main(int argc, char **argv)
-{
- int control_socket = android_get_control_socket("keystore");
- if (argc < 2) {
- LOGE("A directory must be specified!");
- return 1;
- }
- if (chdir(argv[1]) == -1) {
- LOGE("chdir: %s: %s", argv[1], strerror(errno));
- return 1;
- }
- if ((the_entropy = open(RANDOM_DEVICE, O_RDONLY)) == -1) {
- LOGE("open: %s: %s", RANDOM_DEVICE, strerror(errno));
- return 1;
- }
- if (listen(control_socket, 3) == -1) {
- LOGE("listen: %s", strerror(errno));
- return 1;
- }
-
- signal(SIGPIPE, SIG_IGN);
- if (access(MASTER_KEY_FILE, R_OK) == 0) {
- state = LOCKED;
- }
-
- while ((the_socket = accept(control_socket, NULL, 0)) != -1) {
- struct timeval tv = {.tv_sec = 3};
- struct ucred cred;
- socklen_t size = sizeof(cred);
- int8_t request;
-
- setsockopt(the_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
- setsockopt(the_socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
-
- if (getsockopt(the_socket, SOL_SOCKET, SO_PEERCRED, &cred, &size)) {
- LOGW("getsockopt: %s", strerror(errno));
- } else if (recv_code(&request)) {
- int8_t old_state = state;
- int8_t response;
- uid = cred.uid;
-
- if ((response = process(request)) > 0) {
- send_code(response);
- response = -response;
- }
-
- LOGI("uid: %d action: %c -> %d state: %d -> %d retry: %d",
- cred.uid, request, -response, old_state, state, retry);
- }
- close(the_socket);
- }
- LOGE("accept: %s", strerror(errno));
- return 1;
-}
diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp
new file mode 100644
index 0000000..31db9fd
--- /dev/null
+++ b/cmds/keystore/keystore.cpp
@@ -0,0 +1,812 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+
+#include <openssl/aes.h>
+#include <openssl/evp.h>
+#include <openssl/md5.h>
+
+#define LOG_TAG "keystore"
+#include <cutils/log.h>
+#include <cutils/sockets.h>
+#include <private/android_filesystem_config.h>
+
+#include "keystore.h"
+
+/* KeyStore is a secured storage for key-value pairs. In this implementation,
+ * each file stores one key-value pair. Keys are encoded in file names, and
+ * values are encrypted with checksums. The encryption key is protected by a
+ * user-defined password. To keep things simple, buffers are always larger than
+ * the maximum space we needed, so boundary checks on buffers are omitted. */
+
+#define KEY_SIZE ((NAME_MAX - 15) / 2)
+#define VALUE_SIZE 32768
+#define PASSWORD_SIZE VALUE_SIZE
+
+struct Value {
+ int length;
+ uint8_t value[VALUE_SIZE];
+};
+
+/* Here is the encoding of keys. This is necessary in order to allow arbitrary
+ * characters in keys. Characters in [0-~] are not encoded. Others are encoded
+ * into two bytes. The first byte is one of [+-.] which represents the first
+ * two bits of the character. The second byte encodes the rest of the bits into
+ * [0-o]. Therefore in the worst case the length of a key gets doubled. Note
+ * that Base64 cannot be used here due to the need of prefix match on keys. */
+
+static int encode_key(char* out, uid_t uid, const Value* key) {
+ int n = snprintf(out, NAME_MAX, "%u_", uid);
+ out += n;
+ const uint8_t* in = key->value;
+ int length = key->length;
+ for (int i = length; i > 0; --i, ++in, ++out) {
+ if (*in >= '0' && *in <= '~') {
+ *out = *in;
+ } else {
+ *out = '+' + (*in >> 6);
+ *++out = '0' + (*in & 0x3F);
+ ++length;
+ }
+ }
+ *out = '\0';
+ return n + length;
+}
+
+static int decode_key(uint8_t* out, char* in, int length) {
+ for (int i = 0; i < length; ++i, ++in, ++out) {
+ if (*in >= '0' && *in <= '~') {
+ *out = *in;
+ } else {
+ *out = (*in - '+') << 6;
+ *out |= (*++in - '0') & 0x3F;
+ --length;
+ }
+ }
+ *out = '\0';
+ return length;
+}
+
+static size_t readFully(int fd, uint8_t* data, size_t size) {
+ size_t remaining = size;
+ while (remaining > 0) {
+ ssize_t n = TEMP_FAILURE_RETRY(read(fd, data, size));
+ if (n == -1 || n == 0) {
+ return size-remaining;
+ }
+ data += n;
+ remaining -= n;
+ }
+ return size;
+}
+
+static size_t writeFully(int fd, uint8_t* data, size_t size) {
+ size_t remaining = size;
+ while (remaining > 0) {
+ ssize_t n = TEMP_FAILURE_RETRY(write(fd, data, size));
+ if (n == -1 || n == 0) {
+ return size-remaining;
+ }
+ data += n;
+ remaining -= n;
+ }
+ return size;
+}
+
+class Entropy {
+public:
+ Entropy() : mRandom(-1) {}
+ ~Entropy() {
+ if (mRandom != -1) {
+ close(mRandom);
+ }
+ }
+
+ bool open() {
+ const char* randomDevice = "/dev/urandom";
+ mRandom = ::open(randomDevice, O_RDONLY);
+ if (mRandom == -1) {
+ LOGE("open: %s: %s", randomDevice, strerror(errno));
+ return false;
+ }
+ return true;
+ }
+
+ bool generate_random_data(uint8_t* data, size_t size) {
+ return (readFully(mRandom, data, size) == size);
+ }
+
+private:
+ int mRandom;
+};
+
+/* Here is the file format. There are two parts in blob.value, the secret and
+ * the description. The secret is stored in ciphertext, and its original size
+ * can be found in blob.length. The description is stored after the secret in
+ * plaintext, and its size is specified in blob.info. The total size of the two
+ * parts must be no more than VALUE_SIZE bytes. The first three bytes of the
+ * file are reserved for future use and are always set to zero. Fields other
+ * than blob.info, blob.length, and blob.value are modified by encryptBlob()
+ * and decryptBlob(). Thus they should not be accessed from outside. */
+
+struct __attribute__((packed)) blob {
+ uint8_t reserved[3];
+ uint8_t info;
+ uint8_t vector[AES_BLOCK_SIZE];
+ uint8_t encrypted[0];
+ uint8_t digest[MD5_DIGEST_LENGTH];
+ uint8_t digested[0];
+ int32_t length; // in network byte order when encrypted
+ uint8_t value[VALUE_SIZE + AES_BLOCK_SIZE];
+};
+
+class Blob {
+public:
+ Blob(uint8_t* value, int32_t valueLength, uint8_t* info, uint8_t infoLength) {
+ mBlob.length = valueLength;
+ memcpy(mBlob.value, value, valueLength);
+
+ mBlob.info = infoLength;
+ memcpy(mBlob.value + valueLength, info, infoLength);
+ }
+
+ Blob(blob b) {
+ mBlob = b;
+ }
+
+ Blob() {}
+
+ uint8_t* getValue() {
+ return mBlob.value;
+ }
+
+ int32_t getLength() {
+ return mBlob.length;
+ }
+
+ uint8_t getInfo() {
+ return mBlob.info;
+ }
+
+ ResponseCode encryptBlob(const char* filename, AES_KEY *aes_key, Entropy* entropy) {
+ if (!entropy->generate_random_data(mBlob.vector, AES_BLOCK_SIZE)) {
+ return SYSTEM_ERROR;
+ }
+
+ // data includes the value and the value's length
+ size_t dataLength = mBlob.length + sizeof(mBlob.length);
+ // pad data to the AES_BLOCK_SIZE
+ size_t digestedLength = ((dataLength + AES_BLOCK_SIZE - 1)
+ / AES_BLOCK_SIZE * AES_BLOCK_SIZE);
+ // encrypted data includes the digest value
+ size_t encryptedLength = digestedLength + MD5_DIGEST_LENGTH;
+ // move info after space for padding
+ memmove(&mBlob.encrypted[encryptedLength], &mBlob.value[mBlob.length], mBlob.info);
+ // zero padding area
+ memset(mBlob.value + mBlob.length, 0, digestedLength - dataLength);
+
+ mBlob.length = htonl(mBlob.length);
+ MD5(mBlob.digested, digestedLength, mBlob.digest);
+
+ uint8_t vector[AES_BLOCK_SIZE];
+ memcpy(vector, mBlob.vector, AES_BLOCK_SIZE);
+ AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength,
+ aes_key, vector, AES_ENCRYPT);
+
+ memset(mBlob.reserved, 0, sizeof(mBlob.reserved));
+ size_t headerLength = (mBlob.encrypted - (uint8_t*) &mBlob);
+ size_t fileLength = encryptedLength + headerLength + mBlob.info;
+
+ const char* tmpFileName = ".tmp";
+ int out = open(tmpFileName, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
+ if (out == -1) {
+ return SYSTEM_ERROR;
+ }
+ size_t writtenBytes = writeFully(out, (uint8_t*) &mBlob, fileLength);
+ if (close(out) != 0) {
+ return SYSTEM_ERROR;
+ }
+ if (writtenBytes != fileLength) {
+ unlink(tmpFileName);
+ return SYSTEM_ERROR;
+ }
+ return (rename(tmpFileName, filename) == 0) ? NO_ERROR : SYSTEM_ERROR;
+ }
+
+ ResponseCode decryptBlob(const char* filename, AES_KEY *aes_key) {
+ int in = open(filename, O_RDONLY);
+ if (in == -1) {
+ return (errno == ENOENT) ? KEY_NOT_FOUND : SYSTEM_ERROR;
+ }
+ // fileLength may be less than sizeof(mBlob) since the in
+ // memory version has extra padding to tolerate rounding up to
+ // the AES_BLOCK_SIZE
+ size_t fileLength = readFully(in, (uint8_t*) &mBlob, sizeof(mBlob));
+ if (close(in) != 0) {
+ return SYSTEM_ERROR;
+ }
+ size_t headerLength = (mBlob.encrypted - (uint8_t*) &mBlob);
+ if (fileLength < headerLength) {
+ return VALUE_CORRUPTED;
+ }
+
+ ssize_t encryptedLength = fileLength - (headerLength + mBlob.info);
+ if (encryptedLength < 0 || encryptedLength % AES_BLOCK_SIZE != 0) {
+ return VALUE_CORRUPTED;
+ }
+ AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength, aes_key,
+ mBlob.vector, AES_DECRYPT);
+ size_t digestedLength = encryptedLength - MD5_DIGEST_LENGTH;
+ uint8_t computedDigest[MD5_DIGEST_LENGTH];
+ MD5(mBlob.digested, digestedLength, computedDigest);
+ if (memcmp(mBlob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) {
+ return VALUE_CORRUPTED;
+ }
+
+ ssize_t maxValueLength = digestedLength - sizeof(mBlob.length);
+ mBlob.length = ntohl(mBlob.length);
+ if (mBlob.length < 0 || mBlob.length > maxValueLength) {
+ return VALUE_CORRUPTED;
+ }
+ if (mBlob.info != 0) {
+ // move info from after padding to after data
+ memmove(&mBlob.value[mBlob.length], &mBlob.value[maxValueLength], mBlob.info);
+ }
+ return NO_ERROR;
+ }
+
+private:
+ struct blob mBlob;
+};
+
+class KeyStore {
+public:
+ KeyStore(Entropy* entropy) : mEntropy(entropy), mRetry(MAX_RETRY) {
+ if (access(MASTER_KEY_FILE, R_OK) == 0) {
+ setState(STATE_LOCKED);
+ } else {
+ setState(STATE_UNINITIALIZED);
+ }
+ }
+
+ State getState() {
+ return mState;
+ }
+
+ int8_t getRetry() {
+ return mRetry;
+ }
+
+ ResponseCode initialize(Value* pw) {
+ if (!generateMasterKey()) {
+ return SYSTEM_ERROR;
+ }
+ ResponseCode response = writeMasterKey(pw);
+ if (response != NO_ERROR) {
+ return response;
+ }
+ setupMasterKeys();
+ return NO_ERROR;
+ }
+
+ ResponseCode writeMasterKey(Value* pw) {
+ uint8_t passwordKey[MASTER_KEY_SIZE_BYTES];
+ generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, mSalt);
+ AES_KEY passwordAesKey;
+ AES_set_encrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey);
+ Blob masterKeyBlob(mMasterKey, sizeof(mMasterKey), mSalt, sizeof(mSalt));
+ return masterKeyBlob.encryptBlob(MASTER_KEY_FILE, &passwordAesKey, mEntropy);
+ }
+
+ ResponseCode readMasterKey(Value* pw) {
+ int in = open(MASTER_KEY_FILE, O_RDONLY);
+ if (in == -1) {
+ return SYSTEM_ERROR;
+ }
+
+ // we read the raw blob to just to get the salt to generate
+ // the AES key, then we create the Blob to use with decryptBlob
+ blob rawBlob;
+ size_t length = readFully(in, (uint8_t*) &rawBlob, sizeof(rawBlob));
+ if (close(in) != 0) {
+ return SYSTEM_ERROR;
+ }
+ // find salt at EOF if present, otherwise we have an old file
+ uint8_t* salt;
+ if (length > SALT_SIZE && rawBlob.info == SALT_SIZE) {
+ salt = (uint8_t*) &rawBlob + length - SALT_SIZE;
+ } else {
+ salt = NULL;
+ }
+ uint8_t passwordKey[MASTER_KEY_SIZE_BYTES];
+ generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, salt);
+ AES_KEY passwordAesKey;
+ AES_set_decrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey);
+ Blob masterKeyBlob(rawBlob);
+ ResponseCode response = masterKeyBlob.decryptBlob(MASTER_KEY_FILE, &passwordAesKey);
+ if (response == SYSTEM_ERROR) {
+ return SYSTEM_ERROR;
+ }
+ if (response == NO_ERROR && masterKeyBlob.getLength() == MASTER_KEY_SIZE_BYTES) {
+ // if salt was missing, generate one and write a new master key file with the salt.
+ if (salt == NULL) {
+ if (!generateSalt()) {
+ return SYSTEM_ERROR;
+ }
+ response = writeMasterKey(pw);
+ }
+ if (response == NO_ERROR) {
+ setupMasterKeys();
+ }
+ return response;
+ }
+ if (mRetry <= 0) {
+ reset();
+ return UNINITIALIZED;
+ }
+ --mRetry;
+ switch (mRetry) {
+ case 0: return WRONG_PASSWORD_0;
+ case 1: return WRONG_PASSWORD_1;
+ case 2: return WRONG_PASSWORD_2;
+ case 3: return WRONG_PASSWORD_3;
+ default: return WRONG_PASSWORD_3;
+ }
+ }
+
+ bool reset() {
+ clearMasterKeys();
+ setState(STATE_UNINITIALIZED);
+
+ DIR* dir = opendir(".");
+ struct dirent* file;
+
+ if (!dir) {
+ return false;
+ }
+ while ((file = readdir(dir)) != NULL) {
+ if (isKeyFile(file->d_name)) {
+ unlink(file->d_name);
+ }
+ }
+ closedir(dir);
+ return true;
+ }
+
+ bool isEmpty() {
+ DIR* dir = opendir(".");
+ struct dirent* file;
+ if (!dir) {
+ return true;
+ }
+ bool result = true;
+ while ((file = readdir(dir)) != NULL) {
+ if (isKeyFile(file->d_name)) {
+ result = false;
+ break;
+ }
+ }
+ closedir(dir);
+ return result;
+ }
+
+ void lock() {
+ clearMasterKeys();
+ setState(STATE_LOCKED);
+ }
+
+ ResponseCode get(const char* filename, Blob* keyBlob) {
+ return keyBlob->decryptBlob(filename, &mMasterKeyDecryption);
+ }
+
+ ResponseCode put(const char* filename, Blob* keyBlob) {
+ return keyBlob->encryptBlob(filename, &mMasterKeyEncryption, mEntropy);
+ }
+
+private:
+ static const char* MASTER_KEY_FILE;
+ static const int MASTER_KEY_SIZE_BYTES = 16;
+ static const int MASTER_KEY_SIZE_BITS = MASTER_KEY_SIZE_BYTES * 8;
+
+ static const int MAX_RETRY = 4;
+ static const size_t SALT_SIZE = 16;
+
+ Entropy* mEntropy;
+
+ State mState;
+ int8_t mRetry;
+
+ uint8_t mMasterKey[MASTER_KEY_SIZE_BYTES];
+ uint8_t mSalt[SALT_SIZE];
+
+ AES_KEY mMasterKeyEncryption;
+ AES_KEY mMasterKeyDecryption;
+
+ void setState(State state) {
+ mState = state;
+ if (mState == STATE_NO_ERROR || mState == STATE_UNINITIALIZED) {
+ mRetry = MAX_RETRY;
+ }
+ }
+
+ bool generateSalt() {
+ return mEntropy->generate_random_data(mSalt, sizeof(mSalt));
+ }
+
+ bool generateMasterKey() {
+ if (!mEntropy->generate_random_data(mMasterKey, sizeof(mMasterKey))) {
+ return false;
+ }
+ if (!generateSalt()) {
+ return false;
+ }
+ return true;
+ }
+
+ void setupMasterKeys() {
+ AES_set_encrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyEncryption);
+ AES_set_decrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyDecryption);
+ setState(STATE_NO_ERROR);
+ }
+
+ void clearMasterKeys() {
+ memset(mMasterKey, 0, sizeof(mMasterKey));
+ memset(mSalt, 0, sizeof(mSalt));
+ memset(&mMasterKeyEncryption, 0, sizeof(mMasterKeyEncryption));
+ memset(&mMasterKeyDecryption, 0, sizeof(mMasterKeyDecryption));
+ }
+
+ static void generateKeyFromPassword(uint8_t* key, ssize_t keySize, Value* pw, uint8_t* salt) {
+ size_t saltSize;
+ if (salt != NULL) {
+ saltSize = SALT_SIZE;
+ } else {
+ // pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found
+ salt = (uint8_t*) "keystore";
+ // sizeof = 9, not strlen = 8
+ saltSize = sizeof("keystore");
+ }
+ PKCS5_PBKDF2_HMAC_SHA1((char*) pw->value, pw->length, salt, saltSize, 8192, keySize, key);
+ }
+
+ static bool isKeyFile(const char* filename) {
+ return ((strcmp(filename, MASTER_KEY_FILE) != 0)
+ && (strcmp(filename, ".") != 0)
+ && (strcmp(filename, "..") != 0));
+ }
+};
+
+const char* KeyStore::MASTER_KEY_FILE = ".masterkey";
+
+/* Here is the protocol used in both requests and responses:
+ * code [length_1 message_1 ... length_n message_n] end-of-file
+ * where code is one byte long and lengths are unsigned 16-bit integers in
+ * network order. Thus the maximum length of a message is 65535 bytes. */
+
+static int recv_code(int sock, int8_t* code) {
+ return recv(sock, code, 1, 0) == 1;
+}
+
+static int recv_message(int sock, uint8_t* message, int length) {
+ uint8_t bytes[2];
+ if (recv(sock, &bytes[0], 1, 0) != 1 ||
+ recv(sock, &bytes[1], 1, 0) != 1) {
+ return -1;
+ } else {
+ int offset = bytes[0] << 8 | bytes[1];
+ if (length < offset) {
+ return -1;
+ }
+ length = offset;
+ offset = 0;
+ while (offset < length) {
+ int n = recv(sock, &message[offset], length - offset, 0);
+ if (n <= 0) {
+ return -1;
+ }
+ offset += n;
+ }
+ }
+ return length;
+}
+
+static int recv_end_of_file(int sock) {
+ uint8_t byte;
+ return recv(sock, &byte, 1, 0) == 0;
+}
+
+static void send_code(int sock, int8_t code) {
+ send(sock, &code, 1, 0);
+}
+
+static void send_message(int sock, uint8_t* message, int length) {
+ uint16_t bytes = htons(length);
+ send(sock, &bytes, 2, 0);
+ send(sock, message, length, 0);
+}
+
+/* Here are the actions. Each of them is a function without arguments. All
+ * information is defined in global variables, which are set properly before
+ * performing an action. The number of parameters required by each action is
+ * fixed and defined in a table. If the return value of an action is positive,
+ * it will be treated as a response code and transmitted to the client. Note
+ * that the lengths of parameters are checked when they are received, so
+ * boundary checks on parameters are omitted. */
+
+static const ResponseCode NO_ERROR_RESPONSE_CODE_SENT = (ResponseCode) 0;
+
+static ResponseCode test(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) {
+ return (ResponseCode) keyStore->getState();
+}
+
+static ResponseCode get(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) {
+ char filename[NAME_MAX];
+ encode_key(filename, uid, keyName);
+ Blob keyBlob;
+ ResponseCode responseCode = keyStore->get(filename, &keyBlob);
+ if (responseCode != NO_ERROR) {
+ return responseCode;
+ }
+ send_code(sock, NO_ERROR);
+ send_message(sock, keyBlob.getValue(), keyBlob.getLength());
+ return NO_ERROR_RESPONSE_CODE_SENT;
+}
+
+static ResponseCode insert(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value* val) {
+ char filename[NAME_MAX];
+ encode_key(filename, uid, keyName);
+ Blob keyBlob(val->value, val->length, 0, NULL);
+ return keyStore->put(filename, &keyBlob);
+}
+
+static ResponseCode del(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) {
+ char filename[NAME_MAX];
+ encode_key(filename, uid, keyName);
+ return (unlink(filename) && errno != ENOENT) ? SYSTEM_ERROR : NO_ERROR;
+}
+
+static ResponseCode exist(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) {
+ char filename[NAME_MAX];
+ encode_key(filename, uid, keyName);
+ if (access(filename, R_OK) == -1) {
+ return (errno != ENOENT) ? SYSTEM_ERROR : KEY_NOT_FOUND;
+ }
+ return NO_ERROR;
+}
+
+static ResponseCode saw(KeyStore* keyStore, int sock, uid_t uid, Value* keyPrefix, Value*) {
+ DIR* dir = opendir(".");
+ if (!dir) {
+ return SYSTEM_ERROR;
+ }
+ char filename[NAME_MAX];
+ int n = encode_key(filename, uid, keyPrefix);
+ send_code(sock, NO_ERROR);
+
+ struct dirent* file;
+ while ((file = readdir(dir)) != NULL) {
+ if (!strncmp(filename, file->d_name, n)) {
+ char* p = &file->d_name[n];
+ keyPrefix->length = decode_key(keyPrefix->value, p, strlen(p));
+ send_message(sock, keyPrefix->value, keyPrefix->length);
+ }
+ }
+ closedir(dir);
+ return NO_ERROR_RESPONSE_CODE_SENT;
+}
+
+static ResponseCode reset(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) {
+ return keyStore->reset() ? NO_ERROR : SYSTEM_ERROR;
+}
+
+/* Here is the history. To improve the security, the parameters to generate the
+ * master key has been changed. To make a seamless transition, we update the
+ * file using the same password when the user unlock it for the first time. If
+ * any thing goes wrong during the transition, the new file will not overwrite
+ * the old one. This avoids permanent damages of the existing data. */
+
+static ResponseCode password(KeyStore* keyStore, int sock, uid_t uid, Value* pw, Value*) {
+ switch (keyStore->getState()) {
+ case STATE_UNINITIALIZED: {
+ // generate master key, encrypt with password, write to file, initialize mMasterKey*.
+ return keyStore->initialize(pw);
+ }
+ case STATE_NO_ERROR: {
+ // rewrite master key with new password.
+ return keyStore->writeMasterKey(pw);
+ }
+ case STATE_LOCKED: {
+ // read master key, decrypt with password, initialize mMasterKey*.
+ return keyStore->readMasterKey(pw);
+ }
+ }
+ return SYSTEM_ERROR;
+}
+
+static ResponseCode lock(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) {
+ keyStore->lock();
+ return NO_ERROR;
+}
+
+static ResponseCode unlock(KeyStore* keyStore, int sock, uid_t uid, Value* pw, Value* unused) {
+ return password(keyStore, sock, uid, pw, unused);
+}
+
+static ResponseCode zero(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) {
+ return keyStore->isEmpty() ? KEY_NOT_FOUND : NO_ERROR;
+}
+
+/* Here are the permissions, actions, users, and the main function. */
+
+enum perm {
+ TEST = 1,
+ GET = 2,
+ INSERT = 4,
+ DELETE = 8,
+ EXIST = 16,
+ SAW = 32,
+ RESET = 64,
+ PASSWORD = 128,
+ LOCK = 256,
+ UNLOCK = 512,
+ ZERO = 1024,
+};
+
+static const int MAX_PARAM = 2;
+
+static const State STATE_ANY = (State) 0;
+
+static struct action {
+ ResponseCode (*run)(KeyStore* keyStore, int sock, uid_t uid, Value* param1, Value* param2);
+ int8_t code;
+ State state;
+ uint32_t perm;
+ int lengths[MAX_PARAM];
+} actions[] = {
+ {test, 't', STATE_ANY, TEST, {0, 0}},
+ {get, 'g', STATE_NO_ERROR, GET, {KEY_SIZE, 0}},
+ {insert, 'i', STATE_NO_ERROR, INSERT, {KEY_SIZE, VALUE_SIZE}},
+ {del, 'd', STATE_ANY, DELETE, {KEY_SIZE, 0}},
+ {exist, 'e', STATE_ANY, EXIST, {KEY_SIZE, 0}},
+ {saw, 's', STATE_ANY, SAW, {KEY_SIZE, 0}},
+ {reset, 'r', STATE_ANY, RESET, {0, 0}},
+ {password, 'p', STATE_ANY, PASSWORD, {PASSWORD_SIZE, 0}},
+ {lock, 'l', STATE_NO_ERROR, LOCK, {0, 0}},
+ {unlock, 'u', STATE_LOCKED, UNLOCK, {PASSWORD_SIZE, 0}},
+ {zero, 'z', STATE_ANY, ZERO, {0, 0}},
+ {NULL, 0 , STATE_ANY, 0, {0, 0}},
+};
+
+static struct user {
+ uid_t uid;
+ uid_t euid;
+ uint32_t perms;
+} users[] = {
+ {AID_SYSTEM, ~0, ~GET},
+ {AID_VPN, AID_SYSTEM, GET},
+ {AID_WIFI, AID_SYSTEM, GET},
+ {AID_ROOT, AID_SYSTEM, GET},
+ {AID_KEYCHAIN, AID_SYSTEM, TEST | GET | SAW},
+ {~0, ~0, TEST | GET | INSERT | DELETE | EXIST | SAW},
+};
+
+static ResponseCode process(KeyStore* keyStore, int sock, uid_t uid, int8_t code) {
+ struct user* user = users;
+ struct action* action = actions;
+ int i;
+
+ while (~user->uid && user->uid != uid) {
+ ++user;
+ }
+ while (action->code && action->code != code) {
+ ++action;
+ }
+ if (!action->code) {
+ return UNDEFINED_ACTION;
+ }
+ if (!(action->perm & user->perms)) {
+ return PERMISSION_DENIED;
+ }
+ if (action->state != STATE_ANY && action->state != keyStore->getState()) {
+ return (ResponseCode) keyStore->getState();
+ }
+ if (~user->euid) {
+ uid = user->euid;
+ }
+ Value params[MAX_PARAM];
+ for (i = 0; i < MAX_PARAM && action->lengths[i] != 0; ++i) {
+ params[i].length = recv_message(sock, params[i].value, action->lengths[i]);
+ if (params[i].length < 0) {
+ return PROTOCOL_ERROR;
+ }
+ }
+ if (!recv_end_of_file(sock)) {
+ return PROTOCOL_ERROR;
+ }
+ return action->run(keyStore, sock, uid, ¶ms[0], ¶ms[1]);
+}
+
+int main(int argc, char* argv[]) {
+ int controlSocket = android_get_control_socket("keystore");
+ if (argc < 2) {
+ LOGE("A directory must be specified!");
+ return 1;
+ }
+ if (chdir(argv[1]) == -1) {
+ LOGE("chdir: %s: %s", argv[1], strerror(errno));
+ return 1;
+ }
+
+ Entropy entropy;
+ if (!entropy.open()) {
+ return 1;
+ }
+ if (listen(controlSocket, 3) == -1) {
+ LOGE("listen: %s", strerror(errno));
+ return 1;
+ }
+
+ signal(SIGPIPE, SIG_IGN);
+
+ KeyStore keyStore(&entropy);
+ int sock;
+ while ((sock = accept(controlSocket, NULL, 0)) != -1) {
+ struct timeval tv;
+ tv.tv_sec = 3;
+ setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+ setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
+
+ struct ucred cred;
+ socklen_t size = sizeof(cred);
+ int credResult = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &cred, &size);
+ if (credResult != 0) {
+ LOGW("getsockopt: %s", strerror(errno));
+ } else {
+ int8_t request;
+ if (recv_code(sock, &request)) {
+ State old_state = keyStore.getState();
+ ResponseCode response = process(&keyStore, sock, cred.uid, request);
+ if (response == NO_ERROR_RESPONSE_CODE_SENT) {
+ response = NO_ERROR;
+ } else {
+ send_code(sock, response);
+ }
+ LOGI("uid: %d action: %c -> %d state: %d -> %d retry: %d",
+ cred.uid,
+ request, response,
+ old_state, keyStore.getState(),
+ keyStore.getRetry());
+ }
+ }
+ close(sock);
+ }
+ LOGE("accept: %s", strerror(errno));
+ return 1;
+}
diff --git a/cmds/keystore/keystore.h b/cmds/keystore/keystore.h
index 5ef51e9..5ae3d24 100644
--- a/cmds/keystore/keystore.h
+++ b/cmds/keystore/keystore.h
@@ -17,17 +17,27 @@
#ifndef __KEYSTORE_H__
#define __KEYSTORE_H__
-enum response_code {
- NO_ERROR = 1,
- LOCKED = 2,
- UNINITIALIZED = 3,
+// note state values overlap with ResponseCode for the purposes of the state() API
+enum State {
+ STATE_NO_ERROR = 1,
+ STATE_LOCKED = 2,
+ STATE_UNINITIALIZED = 3,
+};
+
+enum ResponseCode {
+ NO_ERROR = STATE_NO_ERROR, // 1
+ LOCKED = STATE_LOCKED, // 2
+ UNINITIALIZED = STATE_UNINITIALIZED, // 3
SYSTEM_ERROR = 4,
PROTOCOL_ERROR = 5,
PERMISSION_DENIED = 6,
KEY_NOT_FOUND = 7,
VALUE_CORRUPTED = 8,
UNDEFINED_ACTION = 9,
- WRONG_PASSWORD = 10,
+ WRONG_PASSWORD_0 = 10,
+ WRONG_PASSWORD_1 = 11,
+ WRONG_PASSWORD_2 = 12,
+ WRONG_PASSWORD_3 = 13, // MAX_RETRY = 4
};
#endif
diff --git a/cmds/keystore/keystore_cli.c b/cmds/keystore/keystore_cli.cpp
similarity index 67%
rename from cmds/keystore/keystore_cli.c
rename to cmds/keystore/keystore_cli.cpp
index e8afb5a..dcd3bcb 100644
--- a/cmds/keystore/keystore_cli.c
+++ b/cmds/keystore/keystore_cli.cpp
@@ -24,44 +24,40 @@
#include "keystore.h"
-char *responses[256] = {
- [NO_ERROR] = "No error",
- [LOCKED] = "Locked",
- [UNINITIALIZED] = "Uninitialized",
- [SYSTEM_ERROR] = "System error",
- [PROTOCOL_ERROR] = "Protocol error",
- [PERMISSION_DENIED] = "Permission denied",
- [KEY_NOT_FOUND] = "Key not found",
- [VALUE_CORRUPTED] = "Value corrupted",
- [UNDEFINED_ACTION] = "Undefined action",
- [WRONG_PASSWORD] = "Wrong password (last chance)",
- [WRONG_PASSWORD + 1] = "Wrong password (2 tries left)",
- [WRONG_PASSWORD + 2] = "Wrong password (3 tries left)",
- [WRONG_PASSWORD + 3] = "Wrong password (4 tries left)",
+static const char* responses[] = {
+ NULL,
+ /* [NO_ERROR] = */ "No error",
+ /* [LOCKED] = */ "Locked",
+ /* [UNINITIALIZED] = */ "Uninitialized",
+ /* [SYSTEM_ERROR] = */ "System error",
+ /* [PROTOCOL_ERROR] = */ "Protocol error",
+ /* [PERMISSION_DENIED] = */ "Permission denied",
+ /* [KEY_NOT_FOUND] = */ "Key not found",
+ /* [VALUE_CORRUPTED] = */ "Value corrupted",
+ /* [UNDEFINED_ACTION] = */ "Undefined action",
+ /* [WRONG_PASSWORD] = */ "Wrong password (last chance)",
+ /* [WRONG_PASSWORD + 1] = */ "Wrong password (2 tries left)",
+ /* [WRONG_PASSWORD + 2] = */ "Wrong password (3 tries left)",
+ /* [WRONG_PASSWORD + 3] = */ "Wrong password (4 tries left)",
};
-#define MAX_RESPONSE (WRONG_PASSWORD + 3)
-
-int main(int argc, char **argv)
+int main(int argc, char* argv[])
{
- uint8_t bytes[65536];
- uint8_t code;
- int sock, i;
-
if (argc < 2) {
printf("Usage: %s action [parameter ...]\n", argv[0]);
return 0;
}
- sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED,
- SOCK_STREAM);
+ int sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED,
+ SOCK_STREAM);
if (sock == -1) {
puts("Failed to connect");
return 1;
}
send(sock, argv[1], 1, 0);
- for (i = 2; i < argc; ++i) {
+ uint8_t bytes[65536];
+ for (int i = 2; i < argc; ++i) {
uint16_t length = strlen(argv[i]);
bytes[0] = length >> 8;
bytes[1] = length;
@@ -70,11 +66,13 @@
}
shutdown(sock, SHUT_WR);
+ uint8_t code;
if (recv(sock, &code, 1, 0) != 1) {
puts("Failed to receive");
return 1;
}
printf("%d %s\n", code , responses[code] ? responses[code] : "Unknown");
+ int i;
while ((i = recv(sock, &bytes[0], 1, 0)) == 1) {
int length;
int offset;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index ef2e54a..a6658cc 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -138,6 +138,25 @@
}
}
+ /** @hide */
+ public boolean getPackageAskScreenCompat(String packageName) {
+ try {
+ return ActivityManagerNative.getDefault().getPackageAskScreenCompat(packageName);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return false;
+ }
+ }
+
+ /** @hide */
+ public void setPackageAskScreenCompat(String packageName, boolean ask) {
+ try {
+ ActivityManagerNative.getDefault().setPackageAskScreenCompat(packageName, ask);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ }
+ }
+
/**
* Return the approximate per-application memory class of the current
* device. This gives you an idea of how hard a memory limit you should
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 2a0d798..85f40c9 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1483,6 +1483,26 @@
return true;
}
+ case GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ String pkg = data.readString();
+ boolean ask = getPackageAskScreenCompat(pkg);
+ reply.writeNoException();
+ reply.writeInt(ask ? 1 : 0);
+ return true;
+ }
+
+ case SET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ String pkg = data.readString();
+ boolean ask = data.readInt() != 0;
+ setPackageAskScreenCompat(pkg, ask);
+ reply.writeNoException();
+ return true;
+ }
+
}
return super.onTransact(code, data, reply, flags);
@@ -3254,7 +3274,8 @@
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
- mRemote.transact(SET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION, data, reply, 0);
+ data.writeString(packageName);
+ mRemote.transact(GET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION, data, reply, 0);
reply.readException();
int mode = reply.readInt();
reply.recycle();
@@ -3275,6 +3296,32 @@
data.recycle();
}
+ public boolean getPackageAskScreenCompat(String packageName) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ mRemote.transact(GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean ask = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return ask;
+ }
+
+ public void setPackageAskScreenCompat(String packageName, boolean ask)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ data.writeInt(ask ? 1 : 0);
+ mRemote.transact(SET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
public boolean switchUser(int userid) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 85e59b3..955cef2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1980,7 +1980,8 @@
BackupAgent agent = null;
String classname = data.appInfo.backupAgentName;
- if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL) {
+ if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL
+ || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL) {
classname = "android.app.backup.FullBackupAgent";
if ((data.appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
// system packages can supply their own full-backup agent
@@ -2011,7 +2012,8 @@
// If this is during restore, fail silently; otherwise go
// ahead and let the user see the crash.
Slog.e(TAG, "Agent threw during creation: " + e);
- if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE) {
+ if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE
+ && data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE_FULL) {
throw e;
}
// falling through with 'binder' still null
@@ -3658,12 +3660,16 @@
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
- List<ProviderInfo> providers = data.providers;
- if (providers != null) {
- installContentProviders(app, providers);
- // For process that contains content providers, we want to
- // ensure that the JIT is enabled "at some point".
- mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
+ // don't bring up providers in restricted mode; they may depend on the
+ // app's custom Application class
+ if (!data.restrictedBackupMode){
+ List<ProviderInfo> providers = data.providers;
+ if (providers != null) {
+ installContentProviders(app, providers);
+ // For process that contains content providers, we want to
+ // ensure that the JIT is enabled "at some point".
+ mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
+ }
}
try {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1f53c0e..e2588cf 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -347,6 +347,9 @@
public int getPackageScreenCompatMode(String packageName) throws RemoteException;
public void setPackageScreenCompatMode(String packageName, int mode)
throws RemoteException;
+ public boolean getPackageAskScreenCompat(String packageName) throws RemoteException;
+ public void setPackageAskScreenCompat(String packageName, boolean ask)
+ throws RemoteException;
// Multi-user APIs
public boolean switchUser(int userid) throws RemoteException;
@@ -577,9 +580,11 @@
int SET_FRONT_ACTIVITY_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+124;
int GET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+125;
int SET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+126;
- int SWITCH_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+127;
- int REMOVE_SUB_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+128;
- int REMOVE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+129;
- int REGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+130;
- int UNREGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+131;
+ int GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+127;
+ int SET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+128;
+ int SWITCH_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+129;
+ int REMOVE_SUB_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+130;
+ int REMOVE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+131;
+ int REGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+132;
+ int UNREGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+133;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 8c31559..05a68a8 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -68,6 +68,7 @@
static final int BACKUP_MODE_INCREMENTAL = 0;
static final int BACKUP_MODE_FULL = 1;
static final int BACKUP_MODE_RESTORE = 2;
+ static final int BACKUP_MODE_RESTORE_FULL = 3;
void scheduleCreateBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo,
int backupMode) throws RemoteException;
void scheduleDestroyBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo)
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 52fc623..8af78fa 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -79,4 +79,23 @@
*/
void doRestore(in ParcelFileDescriptor data, int appVersionCode,
in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder);
+
+ /**
+ * Restore a single "file" to the application. The file was typically obtained from
+ * a full-backup dataset. The agent reads 'size' bytes of file content
+ * from the provided file descriptor.
+ *
+ * @param data Read-only pipe delivering the file content itself.
+ *
+ * @param size Size of the file being restored.
+ * @param type Type of file system entity, e.g. FullBackup.TYPE_DIRECTORY.
+ * @param domain Name of the file's semantic domain to which the 'path' argument is a
+ * relative path. e.g. FullBackup.DATABASE_TREE_TOKEN.
+ * @param path Relative path of the file within its semantic domain.
+ * @param mode Access mode of the file system entity, e.g. 0660.
+ * @param mtime Last modification time of the file system entity.
+ */
+ void doRestoreFile(in ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime,
+ int token, IBackupManager callbackBinder);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index dc60e24..17f8adb 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -179,10 +179,18 @@
throws IOException;
/**
+ * @hide
+ */
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime)
+ throws IOException {
+ // empty stub implementation
+ }
+
+ /**
* Package-private, used only for dispatching an extra step during full backup
*/
void onSaveApk(BackupDataOutput data) {
- if (DEBUG) Log.v(TAG, "--- base onSaveApk() ---");
}
// ----- Core implementation -----
@@ -203,6 +211,7 @@
private class BackupServiceBinder extends IBackupAgent.Stub {
private static final String TAG = "BackupServiceBinder";
+ @Override
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState,
@@ -236,6 +245,7 @@
}
}
+ @Override
public void doRestore(ParcelFileDescriptor data, int appVersionCode,
ParcelFileDescriptor newState,
int token, IBackupManager callbackBinder) throws RemoteException {
@@ -261,5 +271,25 @@
}
}
}
+
+ @Override
+ public void doRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime,
+ int token, IBackupManager callbackBinder) throws RemoteException {
+ long ident = Binder.clearCallingIdentity();
+ try {
+Log.d(TAG, "doRestoreFile() => onRestoreFile()");
+ BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ try {
+ callbackBinder.opComplete(token);
+ } catch (RemoteException e) {
+ // we'll time out anyway, so we're safe
+ }
+ }
+ }
}
}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 9850566..dfb0dd7 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,6 +16,17 @@
package android.app.backup;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+
/**
* Global constant definitions et cetera related to the full-backup-to-fd
* binary format.
@@ -23,18 +34,95 @@
* @hide
*/
public class FullBackup {
- public static String APK_TREE_TOKEN = "a";
- public static String OBB_TREE_TOKEN = "obb";
- public static String ROOT_TREE_TOKEN = "r";
- public static String DATA_TREE_TOKEN = "f";
- public static String DATABASE_TREE_TOKEN = "db";
- public static String SHAREDPREFS_TREE_TOKEN = "sp";
- public static String CACHE_TREE_TOKEN = "c";
+ static final String TAG = "FullBackup";
- public static String FULL_BACKUP_INTENT_ACTION = "fullback";
- public static String FULL_RESTORE_INTENT_ACTION = "fullrest";
- public static String CONF_TOKEN_INTENT_EXTRA = "conftoken";
+ public static final String APK_TREE_TOKEN = "a";
+ public static final String OBB_TREE_TOKEN = "obb";
+ public static final String ROOT_TREE_TOKEN = "r";
+ public static final String DATA_TREE_TOKEN = "f";
+ public static final String DATABASE_TREE_TOKEN = "db";
+ public static final String SHAREDPREFS_TREE_TOKEN = "sp";
+ public static final String CACHE_TREE_TOKEN = "c";
+ public static final String SHARED_STORAGE_TOKEN = "shared";
+
+ public static final String APPS_PREFIX = "apps/";
+ public static final String SHARED_PREFIX = "shared/";
+
+ public static final String FULL_BACKUP_INTENT_ACTION = "fullback";
+ public static final String FULL_RESTORE_INTENT_ACTION = "fullrest";
+ public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken";
+
+ public static final int TYPE_EOF = 0;
+ public static final int TYPE_FILE = 1;
+ public static final int TYPE_DIRECTORY = 2;
+ public static final int TYPE_SYMLINK = 3;
static public native int backupToTar(String packageName, String domain,
String linkdomain, String rootpath, String path, BackupDataOutput output);
+
+ static public void restoreToFile(ParcelFileDescriptor data,
+ long size, int type, long mode, long mtime, File outFile) throws IOException {
+ if (type == FullBackup.TYPE_DIRECTORY) {
+ // Canonically a directory has no associated content, so we don't need to read
+ // anything from the pipe in this case. Just create the directory here and
+ // drop down to the final metadata adjustment.
+ if (outFile != null) outFile.mkdirs();
+ } else {
+ FileOutputStream out = null;
+
+ // Pull the data from the pipe, copying it to the output file, until we're done
+ try {
+ if (outFile != null) {
+ File parent = outFile.getParentFile();
+ if (!parent.exists()) {
+ // in practice this will only be for the default semantic directories,
+ // and using the default mode for those is appropriate.
+ // TODO: support the edge case of apps that have adjusted the
+ // permissions on these core directories
+ parent.mkdirs();
+ }
+ out = new FileOutputStream(outFile);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to create/open file " + outFile.getPath(), e);
+ }
+
+ byte[] buffer = new byte[32 * 1024];
+ final long origSize = size;
+ FileInputStream in = new FileInputStream(data.getFileDescriptor());
+ while (size > 0) {
+ int toRead = (size > buffer.length) ? buffer.length : (int)size;
+ int got = in.read(buffer, 0, toRead);
+ if (got <= 0) {
+ Log.w(TAG, "Incomplete read: expected " + size + " but got "
+ + (origSize - size));
+ break;
+ }
+ if (out != null) {
+ try {
+ out.write(buffer, 0, got);
+ } catch (IOException e) {
+ // Problem writing to the file. Quit copying data and delete
+ // the file, but of course keep consuming the input stream.
+ Log.e(TAG, "Unable to write to file " + outFile.getPath(), e);
+ out.close();
+ out = null;
+ outFile.delete();
+ }
+ }
+ size -= got;
+ }
+ if (out != null) out.close();
+ }
+
+ // Now twiddle the state to match the backup, assuming all went well
+ if (outFile != null) {
+ try {
+ Libcore.os.chmod(outFile.getPath(), (int)mode);
+ } catch (ErrnoException e) {
+ e.rethrowAsIOException();
+ }
+ outFile.setLastModified(mtime);
+ }
+ }
}
diff --git a/core/java/android/app/backup/FullBackupAgent.java b/core/java/android/app/backup/FullBackupAgent.java
index f0a1f2a..4dca593 100644
--- a/core/java/android/app/backup/FullBackupAgent.java
+++ b/core/java/android/app/backup/FullBackupAgent.java
@@ -28,6 +28,9 @@
import libcore.io.StructStat;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
@@ -53,8 +56,12 @@
private String mCacheDir;
private String mLibDir;
+ private File NULL_FILE;
+
@Override
public void onCreate() {
+ NULL_FILE = new File("/dev/null");
+
mPm = getPackageManager();
try {
ApplicationInfo appInfo = mPm.getApplicationInfo(getPackageName(), 0);
@@ -177,7 +184,40 @@
}
}
+ /**
+ * Dummy -- We're never used for restore of an incremental dataset
+ */
@Override
- public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ }
+
+ /**
+ * Restore the described file from the given pipe.
+ */
+ @Override
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String relpath, long mode, long mtime)
+ throws IOException {
+ String basePath = null;
+ File outFile = null;
+
+ if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
+ + " domain=" + domain + " relpath=" + relpath + " mode=" + mode
+ + " mtime=" + mtime);
+
+ // Parse out the semantic domains into the correct physical location
+ if (domain.equals(FullBackup.DATA_TREE_TOKEN)) basePath = mFilesDir;
+ else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) basePath = mDatabaseDir;
+ else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) basePath = mMainDir;
+ else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) basePath = mSharedPrefsDir;
+
+ // Not a supported output location? We need to consume the data
+ // anyway, so send it to /dev/null
+ outFile = (basePath != null) ? new File(basePath, relpath) : null;
+ if (DEBUG) Log.i(TAG, "[" + domain + " : " + relpath + "] mapped to " + outFile.getPath());
+
+ // Now that we've figured out where the data goes, send it on its way
+ FullBackup.restoreToFile(data, size, type, mode, mtime, outFile);
}
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 94e31a8..bac874e 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -147,6 +147,14 @@
boolean allApps, in String[] packageNames);
/**
+ * Restore device content from the data stream passed through the given socket. The
+ * data stream must be in the format emitted by fullBackup().
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void fullRestore(in ParcelFileDescriptor fd);
+
+ /**
* Confirm that the requested full backup/restore operation can proceed. The system will
* not actually perform the operation described to fullBackup() / fullRestore() unless the
* UI calls back into the Backup Manager to confirm, passing the correct token. At
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 854d410..dca53a8 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -113,8 +113,13 @@
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, boolean forceCompat) {
int compatFlags = 0;
+ // We can't rely on the application always setting
+ // FLAG_RESIZEABLE_FOR_SCREENS so will compute it based on various input.
+ boolean anyResizeable = false;
+
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
compatFlags |= LARGE_SCREENS;
+ anyResizeable = true;
if (!forceCompat) {
// If we aren't forcing the app into compatibility mode, then
// assume if it supports large screens that we should allow it
@@ -123,9 +128,13 @@
}
}
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
- compatFlags |= XLARGE_SCREENS | EXPANDABLE;
+ anyResizeable = true;
+ if (!forceCompat) {
+ compatFlags |= XLARGE_SCREENS | EXPANDABLE;
+ }
}
if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+ anyResizeable = true;
compatFlags |= EXPANDABLE;
}
@@ -160,7 +169,7 @@
if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) != 0) {
if ((compatFlags&EXPANDABLE) != 0) {
supportsScreen = true;
- } else if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) == 0) {
+ } else if (!anyResizeable) {
compatFlags |= ALWAYS_COMPAT;
}
}
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index b6aca2b..9c09e81 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -24,8 +24,8 @@
import java.util.Iterator;
import java.util.Map;
-import java.util.Set;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -43,7 +43,7 @@
private StringBuilder mWhereClause = null; // lazily created
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
- private boolean mStrictProjectionMap;
+ private boolean mStrict;
public SQLiteQueryBuilder() {
mDistinct = false;
@@ -145,10 +145,37 @@
}
/**
+ * Need to keep this to not break the build until ContactsProvider2 has been changed to
+ * use the new API
+ * TODO: Remove this
* @hide
*/
public void setStrictProjectionMap(boolean flag) {
- mStrictProjectionMap = flag;
+ }
+
+ /**
+ * When set, the selection is verified against malicious arguments.
+ * When using this class to create a statement using
+ * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
+ * non-numeric limits will raise an exception. If a projection map is specified, fields
+ * not in that map will be ignored.
+ * If this class is used to execute the statement directly using
+ * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
+ * or
+ * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
+ * additionally also parenthesis escaping selection are caught.
+ *
+ * To summarize: To get maximum protection against malicious third party apps (for example
+ * content provider consumers), make sure to do the following:
+ * <ul>
+ * <li>Set this value to true</li>
+ * <li>Use a projection map</li>
+ * <li>Use one of the query overloads instead of getting the statement as a sql string</li>
+ * </ul>
+ * By default, this value is false.
+ */
+ public void setStrict(boolean flag) {
+ mStrict = flag;
}
/**
@@ -217,13 +244,6 @@
}
}
- private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) {
- if (!TextUtils.isEmpty(clause)) {
- s.append(name);
- DatabaseUtils.appendEscapedSQLString(s, clause);
- }
- }
-
/**
* Add the names that are non-null in columns to s, separating
* them with commas.
@@ -320,6 +340,19 @@
return null;
}
+ if (mStrict && selection != null && selection.length() > 0) {
+ // Validate the user-supplied selection to detect syntactic anomalies
+ // in the selection string that could indicate a SQL injection attempt.
+ // The idea is to ensure that the selection clause is a valid SQL expression
+ // by compiling it twice: once wrapped in parentheses and once as
+ // originally specified. An attacker cannot create an expression that
+ // would escape the SQL expression while maintaining balanced parentheses
+ // in both the wrapped and original forms.
+ String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
+ having, sortOrder, limit);
+ validateSql(db, sqlForValidation); // will throw if query is invalid
+ }
+
String sql = buildQuery(
projectionIn, selection, groupBy, having,
sortOrder, limit);
@@ -329,7 +362,20 @@
}
return db.rawQueryWithFactory(
mFactory, sql, selectionArgs,
- SQLiteDatabase.findEditTable(mTables));
+ SQLiteDatabase.findEditTable(mTables)); // will throw if query is invalid
+ }
+
+ /**
+ * Verifies that a SQL statement is valid by compiling it.
+ * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
+ */
+ private void validateSql(SQLiteDatabase db, String sql) {
+ db.lock(sql);
+ try {
+ new SQLiteCompiledSql(db, sql).releaseSqlStatement();
+ } finally {
+ db.unlock();
+ }
}
/**
@@ -541,7 +587,7 @@
continue;
}
- if (!mStrictProjectionMap &&
+ if (!mStrict &&
( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
/* A column alias already exist */
projection[i] = userColumn;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 419288b..c72c4b0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -22,7 +22,6 @@
import android.os.RemoteException;
import java.net.InetAddress;
-import java.net.UnknownHostException;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -40,8 +39,9 @@
* state of the available networks</li>
* </ol>
*/
-public class ConnectivityManager
-{
+public class ConnectivityManager {
+ private static final String TAG = "ConnectivityManager";
+
/**
* A change in network connectivity has occurred. A connection has either
* been established or lost. The NetworkInfo for the affected network is
@@ -109,7 +109,7 @@
* The lookup key for an int that provides information about
* our connection to the internet at large. 0 indicates no connection,
* 100 indicates a great connection. Retrieve it with
- * {@link android.content.Intent@getIntExtra(String)}.
+ * {@link android.content.Intent#getIntExtra(String, int)}.
* {@hide}
*/
public static final String EXTRA_INET_CONDITION = "inetCondition";
@@ -120,13 +120,12 @@
* <p>
* If an application uses the network in the background, it should listen
* for this broadcast and stop using the background data if the value is
- * false.
+ * {@code false}.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
"android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
-
/**
* Broadcast Action: The network connection may not be good
* uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
@@ -255,7 +254,7 @@
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
- private IConnectivityManager mService;
+ private final IConnectivityManager mService;
static public boolean isNetworkTypeValid(int networkType) {
return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
@@ -284,6 +283,15 @@
}
}
+ /** {@hide} */
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ try {
+ return mService.getActiveNetworkInfoForUid(uid);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
@@ -300,7 +308,7 @@
}
}
- /** @hide */
+ /** {@hide} */
public LinkProperties getActiveLinkProperties() {
try {
return mService.getActiveLinkProperties();
@@ -309,7 +317,7 @@
}
}
- /** @hide */
+ /** {@hide} */
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkProperties(networkType);
@@ -479,19 +487,11 @@
}
/**
- * Don't allow use of default constructor.
- */
- @SuppressWarnings({"UnusedDeclaration"})
- private ConnectivityManager() {
- }
-
- /**
* {@hide}
*/
public ConnectivityManager(IConnectivityManager service) {
if (service == null) {
- throw new IllegalArgumentException(
- "ConnectivityManager() cannot be constructed with null service");
+ throw new IllegalArgumentException("missing IConnectivityManager");
}
mService = service;
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 8be492c..647a60a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -33,13 +33,11 @@
int getNetworkPreference();
NetworkInfo getActiveNetworkInfo();
-
+ NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
-
NetworkInfo[] getAllNetworkInfo();
LinkProperties getActiveLinkProperties();
-
LinkProperties getLinkProperties(int networkType);
boolean setRadios(boolean onOff);
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
new file mode 100644
index 0000000..9230151
--- /dev/null
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** {@hide} */
+oneway interface INetworkPolicyListener {
+
+ void onRulesChanged(int uid, int uidRules);
+
+}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index d9351ee..c1f3530 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -16,6 +16,8 @@
package android.net;
+import android.net.INetworkPolicyListener;
+
/**
* Interface that creates and modifies network policy rules.
*
@@ -26,6 +28,11 @@
void setUidPolicy(int uid, int policy);
int getUidPolicy(int uid);
+ boolean isUidForeground(int uid);
+
+ void registerListener(INetworkPolicyListener listener);
+ void unregisterListener(INetworkPolicyListener listener);
+
// TODO: build API to surface stats details for settings UI
}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 5f5e11c..537750a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -74,7 +74,9 @@
/** IP traffic not available. */
DISCONNECTED,
/** Attempt to connect failed. */
- FAILED
+ FAILED,
+ /** Access to this network is blocked. */
+ BLOCKED
}
/**
@@ -96,6 +98,7 @@
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+ stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
}
private int mNetworkType;
@@ -138,6 +141,23 @@
mIsRoaming = false;
}
+ /** {@hide} */
+ public NetworkInfo(NetworkInfo source) {
+ if (source != null) {
+ mNetworkType = source.mNetworkType;
+ mSubtype = source.mSubtype;
+ mTypeName = source.mTypeName;
+ mSubtypeName = source.mSubtypeName;
+ mState = source.mState;
+ mDetailedState = source.mDetailedState;
+ mReason = source.mReason;
+ mExtraInfo = source.mExtraInfo;
+ mIsFailover = source.mIsFailover;
+ mIsRoaming = source.mIsRoaming;
+ mIsAvailable = source.mIsAvailable;
+ }
+ }
+
/**
* Reports the type of network (currently mobile or Wi-Fi) to which the
* info in this object pertains.
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 1913aa7..dd7c1b0 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.os.RemoteException;
+import java.io.PrintWriter;
+
/**
* Manager for creating and modifying network policy rules.
*
@@ -28,12 +30,13 @@
/** No specific network policy, use system default. */
public static final int POLICY_NONE = 0x0;
- /** Reject network usage when application in background. */
- public static final int POLICY_REJECT_BACKGROUND = 0x1;
- /** Reject network usage on paid network connections. */
- public static final int POLICY_REJECT_PAID = 0x2;
- /** Application should conserve data. */
- public static final int POLICY_CONSERVE_DATA = 0x4;
+ /** Reject network usage on paid networks when application in background. */
+ public static final int POLICY_REJECT_PAID_BACKGROUND = 0x1;
+
+ /** All network traffic should be allowed. */
+ public static final int RULE_ALLOW_ALL = 0x0;
+ /** Reject traffic on paid networks. */
+ public static final int RULE_REJECT_PAID = 0x1;
private INetworkPolicyManager mService;
@@ -51,9 +54,8 @@
/**
* Set policy flags for specific UID.
*
- * @param policy {@link #POLICY_NONE} or combination of
- * {@link #POLICY_REJECT_BACKGROUND}, {@link #POLICY_REJECT_PAID},
- * or {@link #POLICY_CONSERVE_DATA}.
+ * @param policy {@link #POLICY_NONE} or combination of flags like
+ * {@link #POLICY_REJECT_PAID_BACKGROUND}.
*/
public void setUidPolicy(int uid, int policy) {
try {
@@ -69,5 +71,23 @@
return POLICY_NONE;
}
}
+
+ /** {@hide} */
+ public static void dumpPolicy(PrintWriter fout, int policy) {
+ fout.write("[");
+ if ((policy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+ fout.write("REJECT_PAID_BACKGROUND");
+ }
+ fout.write("]");
+ }
+
+ /** {@hide} */
+ public static void dumpRules(PrintWriter fout, int rules) {
+ fout.write("[");
+ if ((rules & RULE_REJECT_PAID) != 0) {
+ fout.write("REJECT_PAID");
+ }
+ fout.write("]");
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 017e5e3..c9db697 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -982,10 +982,21 @@
*/
static final int HORIZONTAL_DIRECTION_MASK = 0xC0000000;
+ /*
+ * Array of horizontal direction flags for mapping attribute "horizontalDirection" to correct
+ * flag value.
+ * {@hide}
+ */
private static final int[] HORIZONTAL_DIRECTION_FLAGS = { HORIZONTAL_DIRECTION_LTR,
HORIZONTAL_DIRECTION_RTL, HORIZONTAL_DIRECTION_INHERIT, HORIZONTAL_DIRECTION_LOCALE};
/**
+ * Default horizontalDirection.
+ * {@hide}
+ */
+ private static final int HORIZONTAL_DIRECTION_DEFAULT = HORIZONTAL_DIRECTION_INHERIT;
+
+ /**
* View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
* should add all focusable Views regardless if they are focusable in touch mode.
*/
@@ -2442,7 +2453,7 @@
public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
- mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
+ mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | HORIZONTAL_DIRECTION_INHERIT;
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
}
@@ -2641,12 +2652,18 @@
}
break;
case com.android.internal.R.styleable.View_horizontalDirection:
- final int layoutDirection = a.getInt(attr, 0);
- if (layoutDirection != 0) {
- viewFlagValues |= HORIZONTAL_DIRECTION_FLAGS[layoutDirection];
- viewFlagMasks |= HORIZONTAL_DIRECTION_MASK;
- }
- break;
+ // Clear any HORIZONTAL_DIRECTION flag already set
+ viewFlagValues &= ~HORIZONTAL_DIRECTION_MASK;
+ // Set the HORIZONTAL_DIRECTION flags depending on the value of the attribute
+ final int horizontalDirection = a.getInt(attr, -1);
+ if (horizontalDirection != -1) {
+ viewFlagValues |= HORIZONTAL_DIRECTION_FLAGS[horizontalDirection];
+ } else {
+ // Set to default (HORIZONTAL_DIRECTION_INHERIT)
+ viewFlagValues |= HORIZONTAL_DIRECTION_DEFAULT;
+ }
+ viewFlagMasks |= HORIZONTAL_DIRECTION_MASK;
+ break;
case com.android.internal.R.styleable.View_drawingCacheQuality:
final int cacheQuality = a.getInt(attr, 0);
if (cacheQuality != 0) {
@@ -8513,10 +8530,14 @@
mPrivateFlags &= ~AWAKEN_SCROLL_BARS_ON_ATTACH;
}
jumpDrawablesToCurrentState();
+ resolveHorizontalDirection();
+ }
- // We are supposing here that the parent directionality will be resolved before its children
- // View horizontalDirection public attribute resolution to an internal var.
- // Resolving the layout direction. LTR is set initially.
+ /**
+ * Resolving the layout direction. LTR is set initially.
+ * We are supposing here that the parent directionality will be resolved before its children
+ */
+ private void resolveHorizontalDirection() {
mPrivateFlags2 &= ~RESOLVED_LAYOUT_RTL;
switch (getHorizontalDirection()) {
case HORIZONTAL_DIRECTION_INHERIT:
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 1004b5f..57cda97 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -199,6 +199,9 @@
mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
}
+ public boolean fullScreenExited() {
+ return (mLayout == null);
+ }
private final WebChromeClient.CustomViewCallback mCallback =
new WebChromeClient.CustomViewCallback() {
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
index 25921bc..ef1906c 100644
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ b/core/java/android/webkit/HTML5VideoInline.java
@@ -12,10 +12,15 @@
*/
public class HTML5VideoInline extends HTML5VideoView{
- // Due to the fact that SurfaceTexture consume a lot of memory, we make it
- // as static. m_textureNames is the texture bound with this SurfaceTexture.
+ // Due to the fact that the decoder consume a lot of memory, we make the
+ // surface texture as singleton. But the GL texture (m_textureNames)
+ // associated with the surface texture can be used for showing the screen
+ // shot when paused, so they are not singleton.
private static SurfaceTexture mSurfaceTexture = null;
- private static int[] mTextureNames;
+ private int[] mTextureNames;
+ // Every time when the VideoLayer Id change, we need to recreate the
+ // SurfaceTexture in order to delete the old video's decoder memory.
+ private static int mVideoLayerUsingSurfaceTexture = -1;
// Video control FUNCTIONS:
@Override
@@ -28,11 +33,12 @@
HTML5VideoInline(int videoLayerId, int position,
boolean autoStart) {
init(videoLayerId, position, autoStart);
+ mTextureNames = null;
}
@Override
public void decideDisplayMode() {
- mPlayer.setTexture(getSurfaceTextureInstance());
+ mPlayer.setTexture(getSurfaceTexture(getVideoLayerId()));
}
// Normally called immediately after setVideoURI. But for full screen,
@@ -52,31 +58,38 @@
// Inline Video specific FUNCTIONS:
@Override
- public SurfaceTexture getSurfaceTexture() {
+ public SurfaceTexture getSurfaceTexture(int videoLayerId) {
+ // Create the surface texture.
+ if (videoLayerId != mVideoLayerUsingSurfaceTexture
+ || mSurfaceTexture == null) {
+ if (mTextureNames == null) {
+ mTextureNames = new int[1];
+ GLES20.glGenTextures(1, mTextureNames, 0);
+ }
+ mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
+ }
+ mVideoLayerUsingSurfaceTexture = videoLayerId;
return mSurfaceTexture;
}
+ public boolean surfaceTextureDeleted() {
+ return (mSurfaceTexture == null);
+ }
+
@Override
public void deleteSurfaceTexture() {
mSurfaceTexture = null;
+ mVideoLayerUsingSurfaceTexture = -1;
return;
}
- // SurfaceTexture is a singleton here , too
- private SurfaceTexture getSurfaceTextureInstance() {
- // Create the surface texture.
- if (mSurfaceTexture == null)
- {
- mTextureNames = new int[1];
- GLES20.glGenTextures(1, mTextureNames, 0);
- mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
- }
- return mSurfaceTexture;
- }
-
@Override
public int getTextureName() {
- return mTextureNames[0];
+ if (mTextureNames != null) {
+ return mTextureNames[0];
+ } else {
+ return 0;
+ }
}
private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java
index c05498a..5983a44 100644
--- a/core/java/android/webkit/HTML5VideoView.java
+++ b/core/java/android/webkit/HTML5VideoView.java
@@ -287,7 +287,7 @@
return false;
}
- public SurfaceTexture getSurfaceTexture() {
+ public SurfaceTexture getSurfaceTexture(int videoLayerId) {
return null;
}
@@ -315,4 +315,14 @@
// Only used in HTML5VideoFullScreen
}
+ public boolean surfaceTextureDeleted() {
+ // Only meaningful for HTML5VideoInline
+ return false;
+ }
+
+ public boolean fullScreenExited() {
+ // Only meaningful for HTML5VideoFullScreen
+ return false;
+ }
+
}
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index 7d8669bf..d0237b5 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -106,12 +106,14 @@
public static void setBaseLayer(int layer) {
// Don't do this for full screen mode.
if (mHTML5VideoView != null
- && !mHTML5VideoView.isFullScreenMode()) {
+ && !mHTML5VideoView.isFullScreenMode()
+ && !mHTML5VideoView.surfaceTextureDeleted()) {
mBaseLayer = layer;
- SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture();
- int textureName = mHTML5VideoView.getTextureName();
int currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
+ SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture(currentVideoLayerId);
+ int textureName = mHTML5VideoView.getTextureName();
+
if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) {
int playerState = mHTML5VideoView.getCurrentState();
if (mHTML5VideoView.getPlayerBuffering())
@@ -171,14 +173,12 @@
boolean backFromFullScreenMode = false;
if (mHTML5VideoView != null) {
currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
- if (mHTML5VideoView instanceof HTML5VideoFullScreen) {
- backFromFullScreenMode = true;
- }
+ backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
}
if (backFromFullScreenMode
- || currentVideoLayerId != videoLayerId
- || mHTML5VideoView.getSurfaceTexture() == null) {
+ || currentVideoLayerId != videoLayerId
+ || mHTML5VideoView.surfaceTextureDeleted()) {
// Here, we handle the case when switching to a new video,
// either inside a WebView or across WebViews
// For switching videos within a WebView or across the WebView,
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index e41dd1c..0573d88 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -906,7 +906,7 @@
// scaleAll(), we need to post a Runnable to ensure requestLayout().
// Additionally, only update the text wrap scale if the width changed.
mWebView.post(new PostScale(w != ow &&
- !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview));
+ !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview, w < ow));
}
private class PostScale implements Runnable {
@@ -915,10 +915,14 @@
// it could be changed between the time this callback is initiated and
// the time it's actually run.
final boolean mInZoomOverviewBeforeSizeChange;
+ final boolean mInPortraitMode;
- public PostScale(boolean updateTextWrap, boolean inZoomOverview) {
+ public PostScale(boolean updateTextWrap,
+ boolean inZoomOverview,
+ boolean inPortraitMode) {
mUpdateTextWrap = updateTextWrap;
mInZoomOverviewBeforeSizeChange = inZoomOverview;
+ mInPortraitMode = inPortraitMode;
}
public void run() {
@@ -927,10 +931,10 @@
// still want to send the notification over to webkit.
// Keep overview mode unchanged when rotating.
float newScale = mActualScale;
- if (mWebView.getSettings().getUseWideViewPort()) {
- final float zoomOverviewScale = getZoomOverviewScale();
- newScale = (mInZoomOverviewBeforeSizeChange) ?
- zoomOverviewScale : Math.max(mActualScale, zoomOverviewScale);
+ if (mWebView.getSettings().getUseWideViewPort() &&
+ mInPortraitMode &&
+ mInZoomOverviewBeforeSizeChange) {
+ newScale = getZoomOverviewScale();
}
setZoomScale(newScale, mUpdateTextWrap, true);
// update the zoom buttons as the scale can be changed
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 9933d68..586ece8 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -93,6 +93,7 @@
private boolean mClearingFocus;
private int mMaxWidth;
private boolean mVoiceButtonEnabled;
+ private CharSequence mUserQuery;
private SearchableInfo mSearchable;
private Bundle mAppSearchData;
@@ -372,6 +373,7 @@
mQueryTextView.setText(query);
if (query != null) {
mQueryTextView.setSelection(query.length());
+ mUserQuery = query;
}
// If the query is not empty and submit is requested, submit the query
@@ -885,6 +887,7 @@
private void onTextChanged(CharSequence newText) {
CharSequence text = mQueryTextView.getText();
+ mUserQuery = text;
boolean hasText = !TextUtils.isEmpty(text);
if (isSubmitButtonEnabled()) {
updateSubmitButton(hasText);
@@ -1124,7 +1127,7 @@
if (data != null) {
intent.setData(data);
}
- intent.putExtra(SearchManager.USER_QUERY, query);
+ intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
if (query != null) {
intent.putExtra(SearchManager.QUERY, query);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 18c3b24e..5886c64 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8787,10 +8787,22 @@
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
- mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle));
+ boolean allowText = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.allow_action_menu_item_text_with_icon);
+
+ mode.setTitle(allowText ?
+ mContext.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
mode.setSubtitle(null);
+ int selectAllIconId = 0; // No icon by default
+ if (!allowText) {
+ // Provide an icon, text will not be displayed on smaller screens.
+ selectAllIconId = styledAttributes.getResourceId(
+ R.styleable.Theme_actionModeSelectAllDrawable, 0);
+ }
+
menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+ setIcon(selectAllIconId).
setAlphabeticShortcut('a').
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index beacf75..479788d 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
@@ -103,6 +104,12 @@
// TODO Support checkable action items
}
+ private void updateTextButtonVisibility() {
+ boolean visible = !TextUtils.isEmpty(mTextButton.getText());
+ visible = visible && (mImageButton.getDrawable() == null || mItemData.showsTextAsAction());
+ mTextButton.setVisibility(visible ? VISIBLE : GONE);
+ }
+
public void setIcon(Drawable icon) {
mImageButton.setImageDrawable(icon);
if (icon != null) {
@@ -111,9 +118,9 @@
mImageButton.setVisibility(GONE);
}
- mTextButton.setVisibility(icon == null || mItemData.showsTextAsAction() ? VISIBLE : GONE);
+ updateTextButtonVisibility();
}
-
+
public boolean hasText() {
return mTextButton.getVisibility() != GONE;
}
@@ -128,10 +135,9 @@
// populate accessibility description with title
setContentDescription(title);
- if (mImageButton.getDrawable() == null || mItemData.showsTextAsAction()) {
- mTextButton.setText(mTitle);
- mTextButton.setVisibility(VISIBLE);
- }
+ mTextButton.setText(mTitle);
+
+ updateTextButtonVisibility();
}
public boolean showsIcon() {
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index c82323e..deed1c5 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -15,26 +15,26 @@
*/
package com.android.internal.widget;
-import com.android.internal.R;
-import com.android.internal.view.menu.ActionMenuPresenter;
-import com.android.internal.view.menu.ActionMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup.LayoutParams;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuPresenter;
+import com.android.internal.view.menu.ActionMenuView;
+import com.android.internal.view.menu.MenuBuilder;
+
/**
* @hide
*/
@@ -130,26 +130,23 @@
mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
- if (mTitle != null) {
- mTitleView.setText(mTitle);
- if (mTitleStyleRes != 0) {
- mTitleView.setTextAppearance(mContext, mTitleStyleRes);
- }
+ if (mTitleStyleRes != 0) {
+ mTitleView.setTextAppearance(mContext, mTitleStyleRes);
}
- if (mSubtitle != null) {
- mSubtitleView.setText(mSubtitle);
- if (mSubtitleStyleRes != 0) {
- mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
- }
- mSubtitleView.setVisibility(VISIBLE);
+ if (mSubtitleStyleRes != 0) {
+ mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
}
- } else {
- mTitleView.setText(mTitle);
- mSubtitleView.setText(mSubtitle);
- mSubtitleView.setVisibility(mSubtitle != null ? VISIBLE : GONE);
- if (mTitleLayout.getParent() == null) {
- addView(mTitleLayout);
- }
+ }
+
+ mTitleView.setText(mTitle);
+ mSubtitleView.setText(mSubtitle);
+
+ final boolean hasTitle = !TextUtils.isEmpty(mTitle);
+ final boolean hasSubtitle = !TextUtils.isEmpty(mSubtitle);
+ mSubtitleView.setVisibility(hasSubtitle ? VISIBLE : GONE);
+ mTitleLayout.setVisibility(hasTitle || hasSubtitle ? VISIBLE : GONE);
+ if (mTitleLayout.getParent() == null) {
+ addView(mTitleLayout);
}
}
@@ -228,6 +225,7 @@
mAnimateInOnLayout = false;
}
+ @Override
public boolean showOverflowMenu() {
if (mMenuPresenter != null) {
return mMenuPresenter.showOverflowMenu();
@@ -235,6 +233,7 @@
return false;
}
+ @Override
public boolean hideOverflowMenu() {
if (mMenuPresenter != null) {
return mMenuPresenter.hideOverflowMenu();
@@ -242,6 +241,7 @@
return false;
}
+ @Override
public boolean isOverflowMenuShowing() {
if (mMenuPresenter != null) {
return mMenuPresenter.isOverflowMenuShowing();
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 0dc0422..b3666cb 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -30,6 +30,7 @@
import android.os.SystemClock;
import android.os.storage.IMountService;
import android.provider.Settings;
+import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -47,7 +48,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Utilities for the lock patten and its settings.
+ * Utilities for the lock pattern and its settings.
*/
public class LockPatternUtils {
@@ -397,6 +398,7 @@
raf.close();
DevicePolicyManager dpm = getDevicePolicyManager();
if (pattern != null) {
+ KeyStore.getInstance().password(patternToString(pattern));
setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern
@@ -488,6 +490,9 @@
// Update the encryption password.
updateEncryptionPassword(password);
+ // Update the keystore password
+ KeyStore.getInstance().password(password);
+
int computedQuality = computePasswordQuality(password);
setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality));
if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
@@ -646,7 +651,7 @@
* @param password the gesture pattern.
* @return the hash of the pattern in a byte array.
*/
- public byte[] passwordToHash(String password) {
+ public byte[] passwordToHash(String password) {
if (password == null) {
return null;
}
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index e539cd2..e2832ed 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -42,7 +42,7 @@
#include <SkiaColorFilter.h>
#include <Rect.h>
-#include "TextLayout.h"
+#include <TextLayout.h>
namespace android {
@@ -419,7 +419,7 @@
static void renderText(OpenGLRenderer* renderer, const jchar* text, int count,
jfloat x, jfloat y, int flags, SkPaint* paint) {
-#if 0 // TODO: replace "0" by "RTL_USE_HARFBUZZ" when renderer->drawGlyphs() is implemented
+#if RTL_USE_HARFBUZZ
sp<TextLayoutCacheValue> value = gTextLayoutCache.getValue(
paint, text, 0, count, count, flags);
if (value == NULL) {
@@ -431,7 +431,8 @@
#endif
const jchar* glyphArray = value->getGlyphs();
int glyphCount = value->getGlyphsCount();
- renderer->drawGlyphs((const char*) glyphArray, 0, glyphCount << 1, x, y, paint);
+ int bytesCount = glyphCount * sizeof(jchar);
+ renderer->drawText((const char*) glyphArray, bytesCount, glyphCount, x, y, paint);
#else
const jchar *workText;
jchar* buffer = NULL;
@@ -446,7 +447,7 @@
static void renderTextRun(OpenGLRenderer* renderer, const jchar* text,
jint start, jint count, jint contextCount, jfloat x, jfloat y,
int flags, SkPaint* paint) {
-#if 0 // TODO: replace "0" by "RTL_USE_HARFBUZZ" when renderer->drawGlyphs() is implemented
+#if RTL_USE_HARFBUZZ
sp<TextLayoutCacheValue> value = gTextLayoutCache.getValue(
paint, text, start, count, contextCount, flags);
if (value == NULL) {
@@ -458,7 +459,8 @@
#endif
const jchar* glyphArray = value->getGlyphs();
int glyphCount = value->getGlyphsCount();
- renderer->drawGlyphs((const char*) glyphArray, 0, glyphCount << 1, x, y, paint);
+ int bytesCount = glyphCount * sizeof(jchar);
+ renderer->drawText((const char*) glyphArray, bytesCount, glyphCount, x, y, paint);
#else
uint8_t rtl = flags & 0x1;
if (rtl) {
diff --git a/core/res/res/drawable-hdpi/ic_menu_selectall_holo_dark.png b/core/res/res/drawable-hdpi/ic_menu_selectall_holo_dark.png
new file mode 100644
index 0000000..5579443
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_selectall_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_selectall_holo_light.png b/core/res/res/drawable-hdpi/ic_menu_selectall_holo_light.png
new file mode 100644
index 0000000..6674914
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_selectall_holo_light.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_selectall_holo_dark.png b/core/res/res/drawable-mdpi/ic_menu_selectall_holo_dark.png
new file mode 100644
index 0000000..caec299
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_selectall_holo_dark.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_selectall_holo_light.png b/core/res/res/drawable-mdpi/ic_menu_selectall_holo_light.png
new file mode 100644
index 0000000..434f5d1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_selectall_holo_light.png
Binary files differ
diff --git a/core/res/res/layout/am_compat_mode_dialog.xml b/core/res/res/layout/am_compat_mode_dialog.xml
new file mode 100644
index 0000000..a8d39cf
--- /dev/null
+++ b/core/res/res/layout/am_compat_mode_dialog.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:layout_marginLeft="40dp" android:layout_marginRight="40dp"
+ android:layout_marginTop="15dp" android:layout_marginBottom="15dp"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:orientation="horizontal" android:baselineAligned="true">
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:layout_marginLeft="10dp" android:layout_marginRight="10dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="18sp"
+ android:text="@string/screen_compat_mode_scale"
+ />
+ <Switch
+ android:id="@+id/compat_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginRight="10dp"
+ />
+ </LinearLayout>
+
+ <View android:layout_width="wrap_content" android:layout_height="1dp"
+ android:layout_marginTop="10dp" android:layout_marginBottom="10dp"
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <CheckBox android:id="@+id/ask_checkbox"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:text="@string/screen_compat_mode_show"
+ />
+ <TextView
+ android:id="@+id/reask_hint"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:gravity="center"
+ android:visibility="invisible"
+ android:text="@string/screen_compat_mode_hint" />
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e325b8d..db76211 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -619,6 +619,8 @@
<attr name="actionModeCopyDrawable" format="reference" />
<!-- Drawable to use for the Paste action button in Contextual Action Bar -->
<attr name="actionModePasteDrawable" format="reference" />
+ <!-- Drawable to use for the Select all action button in Contextual Action Bar -->
+ <attr name="actionModeSelectAllDrawable" format="reference" />
<!-- Drawable to use for the Share action button in WebView selection action modes -->
<attr name="actionModeShareDrawable" format="reference" />
<!-- Drawable to use for the Find action button in WebView selection action modes -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 41a566c..4c3cfc1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1684,6 +1684,7 @@
<public type="attr" name="layout_rowWeight" />
<public type="attr" name="layout_columnSpan" />
<public type="attr" name="layout_columnWeight" />
+ <public type="attr" name="actionModeSelectAllDrawable" />
<public type="attr" name="isAuxiliary" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 816546b..b8a4443 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2360,6 +2360,12 @@
<string name="launch_warning_replace"><xliff:g id="app_name">%1$s</xliff:g> is now running.</string>
<!-- [CHAR LIMIT=50] Title of the alert when application launches on top of another. -->
<string name="launch_warning_original"><xliff:g id="app_name">%1$s</xliff:g> was originally launched.</string>
+ <!-- [CHAR LIMIT=50] Compat mode dialog: compat mode switch label. -->
+ <string name="screen_compat_mode_scale">Scale</string>
+ <!-- [CHAR LIMIT=50] Compat mode dialog: compat mode switch label. -->
+ <string name="screen_compat_mode_show">Always show</string>
+ <!-- [CHAR LIMIT=200] Compat mode dialog: hint to re-enable compat mode dialog. -->
+ <string name="screen_compat_mode_hint">Re-enable this with Settings > Applications > Manage applications.</string>
<!-- Text of the alert that is displayed when an application has violated StrictMode. -->
<string name="smv_application">The application <xliff:g id="application">%1$s</xliff:g>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 0a614b2..4f39da4 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -267,6 +267,7 @@
<item name="actionModeCutDrawable">@android:drawable/ic_menu_cut_holo_dark</item>
<item name="actionModeCopyDrawable">@android:drawable/ic_menu_copy_holo_dark</item>
<item name="actionModePasteDrawable">@android:drawable/ic_menu_paste_holo_dark</item>
+ <item name="actionModeSelectAllDrawable">@android:drawable/ic_menu_selectall_holo_dark</item>
<item name="actionModeShareDrawable">@android:drawable/ic_menu_share_holo_dark</item>
<item name="actionModeFindDrawable">@android:drawable/ic_menu_find_holo_dark</item>
<item name="actionModeWebSearchDrawable">@android:drawable/ic_menu_search</item>
@@ -396,6 +397,7 @@
<item name="actionModeCutDrawable">@android:drawable/ic_menu_cut_holo_light</item>
<item name="actionModeCopyDrawable">@android:drawable/ic_menu_copy_holo_light</item>
<item name="actionModePasteDrawable">@android:drawable/ic_menu_paste_holo_light</item>
+ <item name="actionModeSelectAllDrawable">@android:drawable/ic_menu_selectall_holo_light</item>
<item name="actionModeShareDrawable">@android:drawable/ic_menu_share_holo_light</item>
<item name="actionModeFindDrawable">@android:drawable/ic_menu_find_holo_light</item>
<item name="actionModeWebSearchDrawable">@android:drawable/ic_menu_search_holo_light</item>
@@ -1029,6 +1031,7 @@
<item name="actionModeCutDrawable">@android:drawable/ic_menu_cut_holo_dark</item>
<item name="actionModeCopyDrawable">@android:drawable/ic_menu_copy_holo_dark</item>
<item name="actionModePasteDrawable">@android:drawable/ic_menu_paste_holo_dark</item>
+ <item name="actionModeSelectAllDrawable">@android:drawable/ic_menu_selectall_holo_dark</item>
<item name="actionModeShareDrawable">@android:drawable/ic_menu_share_holo_dark</item>
<item name="actionModeFindDrawable">@android:drawable/ic_menu_find_holo_dark</item>
<item name="actionModeWebSearchDrawable">@android:drawable/ic_menu_search_holo_dark</item>
@@ -1316,6 +1319,7 @@
<item name="actionModeCutDrawable">@android:drawable/ic_menu_cut_holo_light</item>
<item name="actionModeCopyDrawable">@android:drawable/ic_menu_copy_holo_light</item>
<item name="actionModePasteDrawable">@android:drawable/ic_menu_paste_holo_light</item>
+ <item name="actionModeSelectAllDrawable">@android:drawable/ic_menu_selectall_holo_light</item>
<item name="actionModeShareDrawable">@android:drawable/ic_menu_share_holo_light</item>
<item name="actionModeFindDrawable">@android:drawable/ic_menu_find_holo_light</item>
<item name="actionModeWebSearchDrawable">@android:drawable/ic_menu_search_holo_light</item>
diff --git a/core/tests/bluetoothtests/Android.mk b/core/tests/bluetoothtests/Android.mk
new file mode 100644
index 0000000..4a1d18c
--- /dev/null
+++ b/core/tests/bluetoothtests/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := BluetoothTests
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/tests/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml
new file mode 100644
index 0000000..96db035
--- /dev/null
+++ b/core/tests/bluetoothtests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.bluetooth.tests" >
+
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+ <application >
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="android.bluetooth.BluetoothTestRunner"
+ android:targetPackage="com.android.bluetooth.tests"
+ android:label="Bluetooth Tests" />
+
+</manifest>
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java
similarity index 100%
rename from core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java
rename to core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
similarity index 98%
rename from core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
rename to core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
index 7f13791..abd7d9a 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
@@ -120,6 +120,9 @@
*/
public void testEnablePan() {
int iterations = BluetoothTestRunner.sEnablePanIterations;
+ if (iterations == 0) {
+ return;
+ }
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
mTestUtils.disable(adapter);
mTestUtils.enable(adapter);
@@ -170,6 +173,9 @@
*/
public void testAcceptPair() {
int iterations = BluetoothTestRunner.sPairIterations;
+ if (iterations == 0) {
+ return;
+ }
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
mTestUtils.disable(adapter);
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java
similarity index 100%
rename from core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
rename to core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
similarity index 100%
rename from core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
rename to core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 6dc5c21..6084dd2 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1230,9 +1230,4 @@
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.frameworks.coretests"
android:label="Frameworks Core Tests" />
-
- <instrumentation android:name="android.bluetooth.BluetoothTestRunner"
- android:targetPackage="com.android.frameworks.coretests"
- android:label="Bluetooth Tests" />
-
</manifest>
diff --git a/core/tests/coretests/src/android/bluetooth/AtParserTest.java b/core/tests/coretests/src/android/bluetooth/AtParserTest.java
deleted file mode 100644
index c5aa52b..0000000
--- a/core/tests/coretests/src/android/bluetooth/AtParserTest.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-import android.bluetooth.AtCommandHandler;
-import android.bluetooth.AtCommandResult;
-import android.bluetooth.AtParser;
-
-import java.util.*;
-import junit.framework.*;
-
-public class AtParserTest extends TestCase {
-
- /* An AtCommandHandler instrumented for testing purposes
- */
- private class HandlerTest extends AtCommandHandler {
- boolean mBasicCalled, mActionCalled, mReadCalled, mTestCalled,
- mSetCalled;
- int mBasicReturn, mActionReturn, mReadReturn, mTestReturn, mSetReturn;
- Object[] mSetArgs;
- String mBasicArgs;
-
- HandlerTest() {
- this(AtCommandResult.ERROR, AtCommandResult.ERROR,
- AtCommandResult.ERROR, AtCommandResult.ERROR,
- AtCommandResult.ERROR);
- }
-
- HandlerTest(int a, int b, int c, int d, int e) {
- mBasicReturn = a;
- mActionReturn = b;
- mReadReturn = c;
- mSetReturn = d;
- mTestReturn = e;
- reset();
- }
- public void reset() {
- mBasicCalled = false;
- mActionCalled = false;
- mReadCalled = false;
- mSetCalled = false;
- mTestCalled = false;
- mSetArgs = null;
- mBasicArgs = null;
- }
- public boolean wasCalled() { // helper
- return mBasicCalled || mActionCalled || mReadCalled ||
- mTestCalled || mSetCalled;
- }
- @Override
- public AtCommandResult handleBasicCommand(String args) {
- mBasicCalled = true;
- mBasicArgs = args;
- return new AtCommandResult(mBasicReturn);
- }
- @Override
- public AtCommandResult handleActionCommand() {
- mActionCalled = true;
- return new AtCommandResult(mActionReturn);
- }
- @Override
- public AtCommandResult handleReadCommand() {
- mReadCalled = true;
- return new AtCommandResult(mReadReturn);
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- mSetCalled = true;
- mSetArgs = args;
- return new AtCommandResult(mSetReturn);
- }
- @Override
- public AtCommandResult handleTestCommand() {
- mTestCalled = true;
- return new AtCommandResult(mTestReturn);
- }
- }
-
- private AtParser mParser;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mParser = new AtParser();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
-
- /* Test that the right method is being called
- */
-/* public void testBasic1() throws Exception {
- HandlerTest D = new HandlerTest(0, 1, 1, 1, 1);
- HandlerTest A = new HandlerTest(0, 1, 1, 1, 1);
- mParser.register('D', D);
- mParser.register('A', A);
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process(" A T D = ? T 1 2 3 4 ").toStrings()));
- assertTrue(D.mBasicCalled);
- assertFalse(D.mActionCalled);
- assertFalse(D.mTestCalled);
- assertFalse(D.mSetCalled);
- assertFalse(D.mReadCalled);
- assertFalse(A.wasCalled());
- assertEquals("=?T1234", D.mBasicArgs);
- }
-*/
- /* Test some crazy strings
- *//*
- public void testBasic2() throws Exception {
- HandlerTest A = new HandlerTest(0, 1, 1, 1, 1);
- mParser.register('A', A);
-
- assertTrue(Arrays.equals(
- new String[]{},
- mParser.process(" ").toStrings()));
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process(" a T a t \"\" 1 2 3 a 4 ")
- .toStrings()));
- assertEquals("T\"\"123A4", A.mBasicArgs);
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process(" a T a t \"foo BaR12Z\" 1 2 3 a 4 ")
- .toStrings()));
- assertEquals("T\"foo BaR12Z\"123A4", A.mBasicArgs);
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("ATA\"").toStrings()));
- assertEquals("\"\"", A.mBasicArgs);
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("ATA\"a").toStrings()));
- assertEquals("\"a\"", A.mBasicArgs);
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("ATa\" ").toStrings()));
- assertEquals("\" \"", A.mBasicArgs);
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("ATA \"one \" two \"t hr ee ")
- .toStrings()));
- assertEquals("\"one \"TWO\"t hr ee \"", A.mBasicArgs);
- }*/
-
- /* Simple extended commands
- *//*
- public void testExt1() throws Exception {
- HandlerTest A = new HandlerTest(1, 0, 0, 0, 0);
- mParser.register("+A", A);
-
- assertTrue(Arrays.equals(
- new String[]{"ERROR"},
- mParser.process("AT+B").toStrings()));
- assertFalse(A.wasCalled());
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+A").toStrings()));
- assertTrue(A.mActionCalled);
- A.mActionCalled = false;
- assertFalse(A.wasCalled());
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+A=").toStrings()));
- assertTrue(A.mSetCalled);
- A.mSetCalled = false;
- assertFalse(A.wasCalled());
- assertEquals(1, A.mSetArgs.length);
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+A=?").toStrings()));
- assertTrue(A.mTestCalled);
- A.mTestCalled = false;
- assertFalse(A.wasCalled());
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+A?").toStrings()));
- assertTrue(A.mReadCalled);
- A.mReadCalled = false;
- assertFalse(A.wasCalled());
- A.reset();
- }
-*/
-
-
- /* Test chained commands
- *//*
- public void testChain1() throws Exception {
- HandlerTest A = new HandlerTest(0, 1, 1, 1, 1);
- HandlerTest B = new HandlerTest(1, 0, 0, 0, 0);
- HandlerTest C = new HandlerTest(1, 1, 1, 1, 1);
- mParser.register('A', A);
- mParser.register("+B", B);
- mParser.register("+C", C);
-
- assertTrue(Arrays.equals(
- new String[]{"ERROR"},
- mParser.process("AT+B;+C").toStrings()));
- assertTrue(B.mActionCalled);
- assertTrue(C.mActionCalled);
- B.reset();
- C.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"ERROR"},
- mParser.process("AT+C;+B").toStrings()));
- assertFalse(B.wasCalled());
- assertTrue(C.mActionCalled);
- B.reset();
- C.reset();
- }*/
-
- /* Test Set command
- *//*
- public void testSet1() throws Exception {
- HandlerTest A = new HandlerTest(1, 1, 1, 0, 1);
- mParser.register("+AAAA", A);
- Object[] expectedResult;
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+AAAA=1").toStrings()));
- expectedResult = new Object[]{(Integer)1};
- assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+AAAA=1,2,3").toStrings()));
- expectedResult = new Object[]{(Integer)1, (Integer)2, (Integer)3};
- assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+AAAA=3,0,0,1").toStrings()));
- expectedResult = new Object[]{(Integer)3, (Integer)0, (Integer)0,
- (Integer)1};
- assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+AAAA=\"foo\",1,\"b,ar").toStrings()));
- expectedResult = new Object[]{"\"foo\"", 1, "\"b,ar\""};
- assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+AAAA=").toStrings()));
- expectedResult = new Object[]{""};
- assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+AAAA=,").toStrings()));
- expectedResult = new Object[]{"", ""};
- assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+AAAA=,,,").toStrings()));
- expectedResult = new Object[]{"", "", "", ""};
- assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("AT+AAAA=,1,,\"foo\",").toStrings()));
- expectedResult = new Object[]{"", 1, "", "\"foo\"", ""};
- assertEquals(5, A.mSetArgs.length);
- assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
- A.reset();
- }*/
-
- /* Test repeat command "A/"
- *//*
- public void testRepeat() throws Exception {
- HandlerTest A = new HandlerTest(0, 0, 0, 0, 0);
- mParser.register('A', A);
-
- // Try repeated command on fresh parser
- assertTrue(Arrays.equals(
- new String[]{},
- mParser.process("A/").toStrings()));
- assertFalse(A.wasCalled());
- A.reset();
-
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("ATA").toStrings()));
- assertTrue(A.mBasicCalled);
- assertEquals("", A.mBasicArgs);
- A.reset();
-
- // Now repeat the command
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("A/").toStrings()));
- assertTrue(A.mBasicCalled);
- assertEquals("", A.mBasicArgs);
- A.reset();
-
- // Multiple repeats
- assertTrue(Arrays.equals(
- new String[]{"OK"},
- mParser.process("A/").toStrings()));
- assertTrue(A.mBasicCalled);
- assertEquals("", A.mBasicArgs);
- A.reset();
-
- }*/
-}
diff --git a/include/media/AudioRecord.h b/include/media/AudioRecord.h
index baab2e8..605680a 100644
--- a/include/media/AudioRecord.h
+++ b/include/media/AudioRecord.h
@@ -130,7 +130,7 @@
* sampleRate: Track sampling rate in Hz.
* format: Audio format (e.g AUDIO_FORMAT_PCM_16_BIT for signed
* 16 bits per sample).
- * channels: Channel mask: see audio_channels_t.
+ * channelMask: Channel mask: see audio_channels_t.
* frameCount: Total size of track PCM buffer in frames. This defines the
* latency of the track.
* flags: A bitmask of acoustic values from enum record_flags. It enables
@@ -151,7 +151,7 @@
AudioRecord(int inputSource,
uint32_t sampleRate = 0,
int format = 0,
- uint32_t channels = AUDIO_CHANNEL_IN_MONO,
+ uint32_t channelMask = AUDIO_CHANNEL_IN_MONO,
int frameCount = 0,
uint32_t flags = 0,
callback_t cbf = 0,
@@ -177,7 +177,7 @@
status_t set(int inputSource = 0,
uint32_t sampleRate = 0,
int format = 0,
- uint32_t channels = AUDIO_CHANNEL_IN_MONO,
+ uint32_t channelMask = AUDIO_CHANNEL_IN_MONO,
int frameCount = 0,
uint32_t flags = 0,
callback_t cbf = 0,
@@ -348,8 +348,8 @@
bool processAudioBuffer(const sp<ClientRecordThread>& thread);
status_t openRecord_l(uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
audio_io_handle_t input);
@@ -364,10 +364,10 @@
uint32_t mFrameCount;
audio_track_cblk_t* mCblk;
- uint8_t mFormat;
+ uint32_t mFormat;
uint8_t mChannelCount;
uint8_t mInputSource;
- uint8_t mReserved;
+ uint8_t mReserved[2];
status_t mStatus;
uint32_t mLatency;
@@ -382,7 +382,7 @@
uint32_t mNewPosition;
uint32_t mUpdatePeriod;
uint32_t mFlags;
- uint32_t mChannels;
+ uint32_t mChannelMask;
audio_io_handle_t mInput;
int mSessionId;
};
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index de928da..df30e8c 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -69,8 +69,8 @@
MUTE = 0x00000001
};
uint32_t flags;
- int channelCount;
int format;
+ int channelCount; // will be removed in the future, do not use
size_t frameCount;
size_t size;
union {
@@ -129,7 +129,7 @@
* sampleRate: Track sampling rate in Hz.
* format: Audio format (e.g AUDIO_FORMAT_PCM_16_BIT for signed
* 16 bits per sample).
- * channels: Channel mask: see audio_channels_t.
+ * channelMask: Channel mask: see audio_channels_t.
* frameCount: Total size of track PCM buffer in frames. This defines the
* latency of the track.
* flags: Reserved for future use.
@@ -143,7 +143,7 @@
AudioTrack( int streamType,
uint32_t sampleRate = 0,
int format = 0,
- int channels = 0,
+ int channelMask = 0,
int frameCount = 0,
uint32_t flags = 0,
callback_t cbf = 0,
@@ -163,7 +163,7 @@
AudioTrack( int streamType,
uint32_t sampleRate = 0,
int format = 0,
- int channels = 0,
+ int channelMask = 0,
const sp<IMemory>& sharedBuffer = 0,
uint32_t flags = 0,
callback_t cbf = 0,
@@ -187,7 +187,7 @@
status_t set(int streamType =-1,
uint32_t sampleRate = 0,
int format = 0,
- int channels = 0,
+ int channelMask = 0,
int frameCount = 0,
uint32_t flags = 0,
callback_t cbf = 0,
@@ -438,8 +438,8 @@
bool processAudioBuffer(const sp<AudioTrackThread>& thread);
status_t createTrack_l(int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
const sp<IMemory>& sharedBuffer,
@@ -459,11 +459,12 @@
uint32_t mFrameCount;
audio_track_cblk_t* mCblk;
+ uint32_t mFormat;
uint8_t mStreamType;
- uint8_t mFormat;
uint8_t mChannelCount;
uint8_t mMuted;
- uint32_t mChannels;
+ uint8_t mReserved;
+ uint32_t mChannelMask;
status_t mStatus;
uint32_t mLatency;
diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h
index d8fdc27..4037c46 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -48,8 +48,8 @@
pid_t pid,
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
const sp<IMemory>& sharedBuffer,
@@ -61,8 +61,8 @@
pid_t pid,
int input,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
int *sessionId,
@@ -73,7 +73,7 @@
*/
virtual uint32_t sampleRate(int output) const = 0;
virtual int channelCount(int output) const = 0;
- virtual int format(int output) const = 0;
+ virtual uint32_t format(int output) const = 0;
virtual size_t frameCount(int output) const = 0;
virtual uint32_t latency(int output) const = 0;
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 1827c3e..072329d 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -82,7 +82,7 @@
// 16 bit because data is converted to 16 bit before being stored in buffer
uint8_t frameSize;
- uint8_t channelCount;
+ uint8_t pad1;
uint16_t bufferTimeoutMs; // Maximum cumulated timeout before restarting audioflinger
uint16_t waitTimeMs; // Cumulated wait time
@@ -90,6 +90,7 @@
volatile int32_t flags;
// Cache line boundary (32 bytes)
+
audio_track_cblk_t();
uint32_t stepUser(uint32_t frameCount);
bool stepServer(uint32_t frameCount);
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 7183688..9058cae 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -32,6 +32,8 @@
* preclude the use of hardware crypto.
*/
public class KeyStore {
+
+ // ResponseCodes
public static final int NO_ERROR = 1;
public static final int LOCKED = 2;
public static final int UNINITIALIZED = 3;
@@ -43,6 +45,9 @@
public static final int UNDEFINED_ACTION = 9;
public static final int WRONG_PASSWORD = 10;
+ // States
+ public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
+
private static final LocalSocketAddress sAddress = new LocalSocketAddress(
"keystore", LocalSocketAddress.Namespace.RESERVED);
@@ -54,31 +59,35 @@
return new KeyStore();
}
- public int test() {
+ public State state() {
execute('t');
- return mError;
+ switch (mError) {
+ case NO_ERROR: return State.UNLOCKED;
+ case LOCKED: return State.LOCKED;
+ case UNINITIALIZED: return State.UNINITIALIZED;
+ default: throw new AssertionError(mError);
+ }
}
- public byte[] get(byte[] key) {
+ private byte[] get(byte[] key) {
ArrayList<byte[]> values = execute('g', key);
return (values == null || values.isEmpty()) ? null : values.get(0);
}
- public String get(String key) {
- byte[] value = get(getBytes(key));
- return (value == null) ? null : toString(value);
+ public byte[] get(String key) {
+ return get(getBytes(key));
}
- public boolean put(byte[] key, byte[] value) {
+ private boolean put(byte[] key, byte[] value) {
execute('i', key, value);
return mError == NO_ERROR;
}
- public boolean put(String key, String value) {
- return put(getBytes(key), getBytes(value));
+ public boolean put(String key, byte[] value) {
+ return put(getBytes(key), value);
}
- public boolean delete(byte[] key) {
+ private boolean delete(byte[] key) {
execute('d', key);
return mError == NO_ERROR;
}
@@ -87,7 +96,7 @@
return delete(getBytes(key));
}
- public boolean contains(byte[] key) {
+ private boolean contains(byte[] key) {
execute('e', key);
return mError == NO_ERROR;
}
@@ -118,19 +127,11 @@
return mError == NO_ERROR;
}
- public boolean password(byte[] oldPassword, byte[] newPassword) {
- execute('p', oldPassword, newPassword);
+ private boolean password(byte[] password) {
+ execute('p', password);
return mError == NO_ERROR;
}
- public boolean password(String oldPassword, String newPassword) {
- return password(getBytes(oldPassword), getBytes(newPassword));
- }
-
- public boolean password(byte[] password) {
- return password(password, password);
- }
-
public boolean password(String password) {
return password(getBytes(password));
}
@@ -140,7 +141,7 @@
return mError == NO_ERROR;
}
- public boolean unlock(byte[] password) {
+ private boolean unlock(byte[] password) {
execute('u', password);
return mError == NO_ERROR;
}
@@ -149,6 +150,11 @@
return unlock(getBytes(password));
}
+ public boolean isEmpty() {
+ execute('z');
+ return mError == KEY_NOT_FOUND;
+ }
+
public int getLastError() {
return mError;
}
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index 6630a4f..4582aa0 100755
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -20,6 +20,9 @@
import android.security.KeyStore;
import android.test.ActivityUnitTestCase;
import android.test.suitebuilder.annotation.MediumTest;
+import java.nio.charset.Charsets;
+import java.util.Arrays;
+import java.util.HashSet;
/**
* Junit / Instrumentation test case for KeyStore class
@@ -31,16 +34,15 @@
@MediumTest
public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
private static final String TEST_PASSWD = "12345678";
- private static final String TEST_EMPTY_PASSWD = "";
- private static final String TEST_SHORT_PASSWD = "short";
private static final String TEST_PASSWD2 = "87654321";
private static final String TEST_KEYNAME = "testkey";
private static final String TEST_KEYNAME1 = "testkey1";
private static final String TEST_KEYNAME2 = "testkey2";
- private static final String TEST_KEYVALUE = "test value";
+ private static final byte[] TEST_KEYVALUE = "test value".getBytes(Charsets.UTF_8);
// "Hello, World" in Chinese
- private static final String TEST_I18N = "\u4F60\u597D, \u4E16\u754C";
+ private static final String TEST_I18N_KEY = "\u4F60\u597D, \u4E16\u754C";
+ private static final byte[] TEST_I18N_VALUE = TEST_I18N_KEY.getBytes(Charsets.UTF_8);
private KeyStore mKeyStore = null;
@@ -51,8 +53,10 @@
@Override
protected void setUp() throws Exception {
mKeyStore = KeyStore.getInstance();
- if (mKeyStore.test() != KeyStore.UNINITIALIZED) mKeyStore.reset();
- assertEquals(KeyStore.UNINITIALIZED, mKeyStore.test());
+ if (mKeyStore.state() != KeyStore.State.UNINITIALIZED) {
+ mKeyStore.reset();
+ }
+ assertEquals(KeyStore.State.UNINITIALIZED, mKeyStore.state());
super.setUp();
}
@@ -62,21 +66,13 @@
super.tearDown();
}
- public void testTest() throws Exception {
- assertEquals(KeyStore.UNINITIALIZED, mKeyStore.test());
+ public void teststate() throws Exception {
+ assertEquals(KeyStore.State.UNINITIALIZED, mKeyStore.state());
}
public void testPassword() throws Exception {
- //assertFalse(mKeyStore.password(TEST_EMPTY_PASSWD));
- //assertFalse(mKeyStore.password(TEST_SHORT_PASSWD));
-
assertTrue(mKeyStore.password(TEST_PASSWD));
- assertEquals(KeyStore.NO_ERROR, mKeyStore.test());
-
- assertFalse(mKeyStore.password(TEST_PASSWD2, TEST_PASSWD2));
- //assertFalse(mKeyStore.password(TEST_PASSWD, TEST_SHORT_PASSWD));
-
- assertTrue(mKeyStore.password(TEST_PASSWD, TEST_PASSWD2));
+ assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
}
public void testPut() throws Exception {
@@ -87,11 +83,11 @@
}
public void testI18n() throws Exception {
- assertFalse(mKeyStore.put(TEST_I18N, TEST_I18N));
- assertFalse(mKeyStore.contains(TEST_I18N));
- mKeyStore.password(TEST_I18N);
- assertTrue(mKeyStore.put(TEST_I18N, TEST_I18N));
- assertTrue(mKeyStore.contains(TEST_I18N));
+ assertFalse(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE));
+ assertFalse(mKeyStore.contains(TEST_I18N_KEY));
+ mKeyStore.password(TEST_I18N_KEY);
+ assertTrue(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE));
+ assertTrue(mKeyStore.contains(TEST_I18N_KEY));
}
public void testDelete() throws Exception {
@@ -114,33 +110,46 @@
}
public void testSaw() throws Exception {
- String[] results = mKeyStore.saw(TEST_KEYNAME);
- assertEquals(0, results.length);
+ String[] emptyResult = mKeyStore.saw(TEST_KEYNAME);
+ assertNotNull(emptyResult);
+ assertEquals(0, emptyResult.length);
mKeyStore.password(TEST_PASSWD);
mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE);
mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE);
- results = mKeyStore.saw(TEST_KEYNAME);
- assertEquals(2, results.length);
+ String[] results = mKeyStore.saw(TEST_KEYNAME);
+ assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
+ TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
+ new HashSet(Arrays.asList(results)));
}
public void testLock() throws Exception {
assertFalse(mKeyStore.lock());
mKeyStore.password(TEST_PASSWD);
- assertEquals(KeyStore.NO_ERROR, mKeyStore.test());
+ assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
assertTrue(mKeyStore.lock());
- assertEquals(KeyStore.LOCKED, mKeyStore.test());
+ assertEquals(KeyStore.State.LOCKED, mKeyStore.state());
}
public void testUnlock() throws Exception {
mKeyStore.password(TEST_PASSWD);
- assertEquals(KeyStore.NO_ERROR, mKeyStore.test());
+ assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
mKeyStore.lock();
assertFalse(mKeyStore.unlock(TEST_PASSWD2));
assertTrue(mKeyStore.unlock(TEST_PASSWD));
}
+
+ public void testIsEmpty() throws Exception {
+ assertTrue(mKeyStore.isEmpty());
+ mKeyStore.password(TEST_PASSWD);
+ assertTrue(mKeyStore.isEmpty());
+ mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE);
+ assertFalse(mKeyStore.isEmpty());
+ mKeyStore.reset();
+ assertTrue(mKeyStore.isEmpty());
+ }
}
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index f8582d8..afab26a 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -1151,6 +1151,7 @@
void DisplayListRenderer::drawText(const char* text, int bytesCount, int count,
float x, float y, SkPaint* paint) {
+ if (count <= 0) return;
addOp(DisplayList::DrawText);
addText(text, bytesCount);
addInt(count);
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 1ca0a19..9bf3de8 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -35,6 +35,7 @@
#define DEFAULT_TEXT_CACHE_WIDTH 1024
#define DEFAULT_TEXT_CACHE_HEIGHT 256
+// We should query these values from the GL context
#define MAX_TEXT_CACHE_WIDTH 2048
#define MAX_TEXT_CACHE_HEIGHT 2048
@@ -58,8 +59,7 @@
}
for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
- CachedGlyphInfo* glyph = mCachedGlyphs.valueAt(i);
- delete glyph;
+ delete mCachedGlyphs.valueAt(i);
}
}
@@ -134,48 +134,49 @@
}
-Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) {
+Font::CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) {
CachedGlyphInfo* cachedGlyph = NULL;
- ssize_t index = mCachedGlyphs.indexOfKey(utfChar);
+ ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
if (index >= 0) {
cachedGlyph = mCachedGlyphs.valueAt(index);
} else {
- cachedGlyph = cacheGlyph(paint, utfChar);
+ cachedGlyph = cacheGlyph(paint, textUnit);
}
// Is the glyph still in texture cache?
if (!cachedGlyph->mIsValid) {
- const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar);
+ const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit);
updateGlyphCache(paint, skiaGlyph, cachedGlyph);
}
return cachedGlyph;
}
-void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
- renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap,
+ render(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap,
bitmapW, bitmapH, NULL);
} else {
- renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, 0, 0, NULL);
+ render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL,
+ 0, 0, NULL);
}
}
-void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, Rect *bounds) {
if (bounds == NULL) {
LOGE("No return rectangle provided to measure text");
return;
}
bounds->set(1e6, -1e6, -1e6, 1e6);
- renderUTF(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds);
+ render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds);
}
#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
-void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH,Rect *bounds) {
if (numGlyphs == 0 || text == NULL || len == 0) {
@@ -195,14 +196,14 @@
text += start;
while (glyphsLeft > 0) {
- int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
+ glyph_t glyph = GET_GLYPH(text);
// Reached the end of the string
- if (utfChar < 0) {
+ if (IS_END_OF_STRING(glyph)) {
break;
}
- CachedGlyphInfo* cachedGlyph = getCachedUTFChar(paint, utfChar);
+ CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
penX += SkAutoKern_AdjustF(prevRsbDelta, cachedGlyph->mLsbDelta);
prevRsbDelta = cachedGlyph->mRsbDelta;
@@ -268,11 +269,11 @@
mState->mUploadTexture = true;
}
-Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, int32_t glyph) {
+Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) {
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
- const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph);
+ const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph);
newGlyph->mGlyphIndex = skiaGlyph.fID;
newGlyph->mIsValid = false;
@@ -672,7 +673,7 @@
uint32_t remainingCapacity = getRemainingCacheCapacity();
uint32_t precacheIdx = 0;
while (remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) {
- mCurrentFont->getCachedUTFChar(paint, (int32_t) mLatinPrecache[precacheIdx]);
+ mCurrentFont->getCachedGlyph(paint, (int32_t) mLatinPrecache[precacheIdx]);
remainingCapacity = getRemainingCacheCapacity();
precacheIdx ++;
}
@@ -714,7 +715,7 @@
}
Rect bounds;
- mCurrentFont->measureUTF(paint, text, startIndex, len, numGlyphs, &bounds);
+ mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds);
uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
@@ -725,7 +726,7 @@
int penX = radius - bounds.left;
int penY = radius - bounds.bottom;
- mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, penX, penY,
+ mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
dataBuffer, paddedWidth, paddedHeight);
blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
@@ -755,7 +756,7 @@
mDrawn = false;
mBounds = bounds;
mClip = clip;
- mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, x, y);
+ mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y);
mBounds = NULL;
if (mCurrentQuadIndex != 0) {
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 95f714f..24ed6fa 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -33,8 +33,32 @@
namespace android {
namespace uirenderer {
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+#if RENDER_TEXT_AS_GLYPHS
+ typedef uint16_t glyph_t;
+ #define GET_METRICS(paint, glyph) paint->getGlyphMetrics(glyph)
+ #define GET_GLYPH(text) nextGlyph((const uint16_t**) &text)
+ #define IS_END_OF_STRING(glyph) false
+#else
+ typedef SkUnichar glyph_t;
+ #define GET_METRICS(paint, glyph) paint->getUnicharMetrics(glyph)
+ #define GET_GLYPH(text) SkUTF16_NextUnichar((const uint16_t**) &text)
+ #define IS_END_OF_STRING(glyph) glyph < 0
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Declarations
+///////////////////////////////////////////////////////////////////////////////
+
class FontRenderer;
+///////////////////////////////////////////////////////////////////////////////
+// Font
+///////////////////////////////////////////////////////////////////////////////
+
/**
* Represents a font, defined by a Skia font id and a font size. A font is used
* to generate glyphs and cache them in the FontState.
@@ -51,9 +75,9 @@
* Renders the specified string of text.
* If bitmap is specified, it will be used as the render target
*/
- void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
- int numGlyphs, int x, int y,
- uint8_t *bitmap = NULL, uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+ void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ int numGlyphs, int x, int y, uint8_t *bitmap = NULL,
+ uint32_t bitmapW = 0, uint32_t bitmapH = 0);
/**
* Creates a new font associated with the specified font state.
*/
@@ -69,13 +93,12 @@
MEASURE,
};
- void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
- int numGlyphs, int x, int y, RenderMode mode,
- uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
- Rect *bounds);
+ void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
+ uint32_t bitmapW, uint32_t bitmapH, Rect *bounds);
- void measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
- int numGlyphs, Rect *bounds);
+ void measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ int numGlyphs, Rect *bounds);
struct CachedGlyphInfo {
// Has the cache been invalidated?
@@ -107,18 +130,26 @@
Font(FontRenderer* state, uint32_t fontId, float fontSize, int flags, uint32_t italicStyle,
uint32_t scaleX);
- DefaultKeyedVector<int32_t, CachedGlyphInfo*> mCachedGlyphs;
+ // Cache of glyphs
+ DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
void invalidateTextureCache();
- CachedGlyphInfo* cacheGlyph(SkPaint* paint, int32_t glyph);
+ CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph);
void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph);
void measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds);
void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
- uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
- CachedGlyphInfo* getCachedUTFChar(SkPaint* paint, int32_t utfChar);
+ CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit);
+
+ static glyph_t nextGlyph(const uint16_t** srcPtr) {
+ const uint16_t* src = *srcPtr;
+ glyph_t g = *src++;
+ *srcPtr = src;
+ return g;
+ }
FontRenderer* mState;
uint32_t mFontId;
@@ -128,6 +159,10 @@
uint32_t mScaleX;
};
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+
class FontRenderer {
public:
FontRenderer();
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 6243b01..45f4a42 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2073,11 +2073,6 @@
drawTextDecorations(text, bytesCount, length, oldX, oldY, paint);
}
-void OpenGLRenderer::drawGlyphs(const char* glyphs, int index, int count, float x, float y,
- SkPaint* paint) {
- // TODO
-}
-
void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
if (mSnapshot->isIgnored()) return;
@@ -2230,14 +2225,19 @@
// Handle underline and strike-through
uint32_t flags = paint->getFlags();
if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+ SkPaint paintCopy(*paint);
+#if RENDER_TEXT_AS_GLYPHS
+ paintCopy.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+#endif
+
float underlineWidth = length;
// If length is > 0.0f, we already measured the text for the text alignment
if (length <= 0.0f) {
- underlineWidth = paint->measureText(text, bytesCount);
+ underlineWidth = paintCopy.measureText(text, bytesCount);
}
float offsetX = 0;
- switch (paint->getTextAlign()) {
+ switch (paintCopy.getTextAlign()) {
case SkPaint::kCenter_Align:
offsetX = underlineWidth * 0.5f;
break;
@@ -2249,8 +2249,7 @@
}
if (underlineWidth > 0.0f) {
- const float textSize = paint->getTextSize();
- // TODO: Support stroke width < 1.0f when we have AA lines
+ const float textSize = paintCopy.getTextSize();
const float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
const float left = x - offsetX;
@@ -2280,10 +2279,9 @@
points[currentPoint++] = top;
}
- SkPaint linesPaint(*paint);
- linesPaint.setStrokeWidth(strokeWidth);
+ paintCopy.setStrokeWidth(strokeWidth);
- drawLines(&points[0], pointsCount, &linesPaint);
+ drawLines(&points[0], pointsCount, &paintCopy);
}
}
}
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index e2dbba0..549d6e9 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -123,8 +123,6 @@
virtual void drawPoints(float* points, int count, SkPaint* paint);
virtual void drawText(const char* text, int bytesCount, int count, float x, float y,
SkPaint* paint);
- virtual void drawGlyphs(const char* glyphs, int index, int count, float x, float y,
- SkPaint* paint);
virtual void resetShader();
virtual void setupShader(SkiaShader* shader);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 2d8b6f3..7c10518 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -28,6 +28,9 @@
// If turned on, layers drawn inside FBOs are optimized with regions
#define RENDER_LAYERS_AS_REGIONS 1
+// If turned on, text is interpreted as glyphs instead of UTF-16
+#define RENDER_TEXT_AS_GLYPHS 1
+
/**
* Debug level for app developers.
*/
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index d46686d..28dba13 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -73,7 +73,6 @@
text = str.string();
}
- // TODO: Should take into account fake bold and text skew
bool operator<(const ShadowText& rhs) const {
LTE_INT(len) {
LTE_INT(radius) {
diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp
index e15875f..f933199f 100644
--- a/libs/utils/BackupHelpers.cpp
+++ b/libs/utils/BackupHelpers.cpp
@@ -503,6 +503,16 @@
needExtended = true;
}
+ // Non-7bit-clean path also means needing pax extended format
+ if (!needExtended) {
+ for (size_t i = 0; i < filepath.length(); i++) {
+ if ((filepath[i] & 0x80) != 0) {
+ needExtended = true;
+ break;
+ }
+ }
+ }
+
int err = 0;
struct stat64 s;
if (lstat64(filepath.string(), &s) != 0) {
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 446e3df..f6c4cc7 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -88,7 +88,7 @@
int inputSource,
uint32_t sampleRate,
int format,
- uint32_t channels,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
callback_t cbf,
@@ -97,7 +97,7 @@
int sessionId)
: mStatus(NO_INIT), mSessionId(0)
{
- mStatus = set(inputSource, sampleRate, format, channels,
+ mStatus = set(inputSource, sampleRate, format, channelMask,
frameCount, flags, cbf, user, notificationFrames, sessionId);
}
@@ -121,7 +121,7 @@
int inputSource,
uint32_t sampleRate,
int format,
- uint32_t channels,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
callback_t cbf,
@@ -131,7 +131,7 @@
int sessionId)
{
- LOGV("set(): sampleRate %d, channels %d, frameCount %d",sampleRate, channels, frameCount);
+ LOGV("set(): sampleRate %d, channelMask %d, frameCount %d",sampleRate, channelMask, frameCount);
AutoMutex lock(mLock);
@@ -156,14 +156,14 @@
return BAD_VALUE;
}
- if (!audio_is_input_channel(channels)) {
+ if (!audio_is_input_channel(channelMask)) {
return BAD_VALUE;
}
- int channelCount = popcount(channels);
+ int channelCount = popcount(channelMask);
audio_io_handle_t input = AudioSystem::getInput(inputSource,
- sampleRate, format, channels, (audio_in_acoustics_t)flags);
+ sampleRate, format, channelMask, (audio_in_acoustics_t)flags);
if (input == 0) {
LOGE("Could not get audio input for record source %d", inputSource);
return BAD_VALUE;
@@ -190,7 +190,7 @@
mSessionId = sessionId;
// create the IAudioRecord
- status = openRecord_l(sampleRate, format, channelCount,
+ status = openRecord_l(sampleRate, format, channelMask,
frameCount, flags, input);
if (status != NO_ERROR) {
return status;
@@ -209,7 +209,7 @@
// Update buffer size in case it has been limited by AudioFlinger during track creation
mFrameCount = mCblk->frameCount;
mChannelCount = (uint8_t)channelCount;
- mChannels = channels;
+ mChannelMask = channelMask;
mActive = 0;
mCbf = cbf;
mNotificationFrames = notificationFrames;
@@ -437,8 +437,8 @@
// must be called with mLock held
status_t AudioRecord::openRecord_l(
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
audio_io_handle_t input)
@@ -451,7 +451,7 @@
sp<IAudioRecord> record = audioFlinger->openRecord(getpid(), input,
sampleRate, format,
- channelCount,
+ channelMask,
frameCount,
((uint16_t)flags) << 16,
&mSessionId,
@@ -589,7 +589,7 @@
{
mInput = AudioSystem::getInput(mInputSource,
mCblk->sampleRate,
- mFormat, mChannels,
+ mFormat, mChannelMask,
(audio_in_acoustics_t)mFlags);
return mInput;
}
@@ -756,7 +756,7 @@
// if the new IAudioRecord is created, openRecord_l() will modify the
// following member variables: mAudioRecord, mCblkMemory and mCblk.
// It will also delete the strong references on previous IAudioRecord and IMemory
- result = openRecord_l(cblk->sampleRate, mFormat, mChannelCount,
+ result = openRecord_l(cblk->sampleRate, mFormat, mChannelMask,
mFrameCount, mFlags, getInput_l());
if (result == NO_ERROR) {
result = mAudioRecord->start();
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 7520ed9..ea44f87 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -87,7 +87,7 @@
int streamType,
uint32_t sampleRate,
int format,
- int channels,
+ int channelMask,
int frameCount,
uint32_t flags,
callback_t cbf,
@@ -96,7 +96,7 @@
int sessionId)
: mStatus(NO_INIT)
{
- mStatus = set(streamType, sampleRate, format, channels,
+ mStatus = set(streamType, sampleRate, format, channelMask,
frameCount, flags, cbf, user, notificationFrames,
0, false, sessionId);
}
@@ -105,7 +105,7 @@
int streamType,
uint32_t sampleRate,
int format,
- int channels,
+ int channelMask,
const sp<IMemory>& sharedBuffer,
uint32_t flags,
callback_t cbf,
@@ -114,7 +114,7 @@
int sessionId)
: mStatus(NO_INIT)
{
- mStatus = set(streamType, sampleRate, format, channels,
+ mStatus = set(streamType, sampleRate, format, channelMask,
0, flags, cbf, user, notificationFrames,
sharedBuffer, false, sessionId);
}
@@ -141,7 +141,7 @@
int streamType,
uint32_t sampleRate,
int format,
- int channels,
+ int channelMask,
int frameCount,
uint32_t flags,
callback_t cbf,
@@ -180,8 +180,8 @@
if (format == 0) {
format = AUDIO_FORMAT_PCM_16_BIT;
}
- if (channels == 0) {
- channels = AUDIO_CHANNEL_OUT_STEREO;
+ if (channelMask == 0) {
+ channelMask = AUDIO_CHANNEL_OUT_STEREO;
}
// validate parameters
@@ -195,15 +195,15 @@
flags |= AUDIO_POLICY_OUTPUT_FLAG_DIRECT;
}
- if (!audio_is_output_channel(channels)) {
+ if (!audio_is_output_channel(channelMask)) {
LOGE("Invalid channel mask");
return BAD_VALUE;
}
- uint32_t channelCount = popcount(channels);
+ uint32_t channelCount = popcount(channelMask);
audio_io_handle_t output = AudioSystem::getOutput(
(audio_stream_type_t)streamType,
- sampleRate,format, channels,
+ sampleRate,format, channelMask,
(audio_policy_output_flags_t)flags);
if (output == 0) {
@@ -222,8 +222,8 @@
// create the IAudioTrack
status_t status = createTrack_l(streamType,
sampleRate,
- format,
- channelCount,
+ (uint32_t)format,
+ (uint32_t)channelMask,
frameCount,
flags,
sharedBuffer,
@@ -245,8 +245,8 @@
mStatus = NO_ERROR;
mStreamType = streamType;
- mFormat = format;
- mChannels = channels;
+ mFormat = (uint32_t)format;
+ mChannelMask = (uint32_t)channelMask;
mChannelCount = channelCount;
mSharedBuffer = sharedBuffer;
mMuted = false;
@@ -681,7 +681,7 @@
audio_io_handle_t AudioTrack::getOutput_l()
{
return AudioSystem::getOutput((audio_stream_type_t)mStreamType,
- mCblk->sampleRate, mFormat, mChannels, (audio_policy_output_flags_t)mFlags);
+ mCblk->sampleRate, mFormat, mChannelMask, (audio_policy_output_flags_t)mFlags);
}
int AudioTrack::getSessionId()
@@ -705,8 +705,8 @@
status_t AudioTrack::createTrack_l(
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
const sp<IMemory>& sharedBuffer,
@@ -767,6 +767,7 @@
}
} else {
// Ensure that buffer alignment matches channelcount
+ int channelCount = popcount(channelMask);
if (((uint32_t)sharedBuffer->pointer() & (channelCount | 1)) != 0) {
LOGE("Invalid buffer alignement: address %p, channelCount %d", sharedBuffer->pointer(), channelCount);
return BAD_VALUE;
@@ -779,7 +780,7 @@
streamType,
sampleRate,
format,
- channelCount,
+ channelMask,
frameCount,
((uint16_t)flags) << 16,
sharedBuffer,
@@ -1164,7 +1165,7 @@
result = createTrack_l(mStreamType,
cblk->sampleRate,
mFormat,
- mChannelCount,
+ mChannelMask,
mFrameCount,
mFlags,
mSharedBuffer,
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 158d2f5..4a12962 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -82,8 +82,8 @@
pid_t pid,
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
const sp<IMemory>& sharedBuffer,
@@ -98,7 +98,7 @@
data.writeInt32(streamType);
data.writeInt32(sampleRate);
data.writeInt32(format);
- data.writeInt32(channelCount);
+ data.writeInt32(channelMask);
data.writeInt32(frameCount);
data.writeInt32(flags);
data.writeStrongBinder(sharedBuffer->asBinder());
@@ -129,8 +129,8 @@
pid_t pid,
int input,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
int *sessionId,
@@ -143,7 +143,7 @@
data.writeInt32(input);
data.writeInt32(sampleRate);
data.writeInt32(format);
- data.writeInt32(channelCount);
+ data.writeInt32(channelMask);
data.writeInt32(frameCount);
data.writeInt32(flags);
int lSessionId = 0;
@@ -186,7 +186,7 @@
return reply.readInt32();
}
- virtual int format(int output) const
+ virtual uint32_t format(int output) const
{
Parcel data, reply;
data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index d51c946..eae93ff 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1164,7 +1164,7 @@
mem = new MemoryBase(cache->getHeap(), 0, cache->size());
*pSampleRate = cache->sampleRate();
*pNumChannels = cache->channelCount();
- *pFormat = cache->format();
+ *pFormat = (int)cache->format();
LOGV("return memory @ %p, sampleRate=%u, channelCount = %d, format = %d", mem->pointer(), *pSampleRate, *pNumChannels, *pFormat);
Exit:
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
index 4c66a2d..7eb6d22 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
@@ -112,12 +112,11 @@
private final int NUM_OF_ITERATIONS=20;
- private float calculateTimeTaken(long beginTime, int numIterations)
+ private int calculateTimeTaken(long beginTime, int numIterations)
throws Exception {
final long duration2 = SystemClock.uptimeMillis();
final long durationToCreateMediaItem = (duration2 - beginTime);
- final float timeTaken1 = (float)durationToCreateMediaItem *
- 1.0f/(float)numIterations;
+ final int timeTaken1 = (int)(durationToCreateMediaItem / numIterations);
return (timeTaken1);
}
@@ -208,7 +207,7 @@
final String[] loggingInfo = new String[3];
final MediaVideoItem[] mediaVideoItem =
new MediaVideoItem[NUM_OF_ITERATIONS];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
long startTime = 0;
/** Time Take for creation of Media Video Item */
@@ -251,7 +250,7 @@
final String[] loggingInfo = new String[3];
final MediaImageItem[] mediaImageItem =
new MediaImageItem[NUM_OF_ITERATIONS];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
long beginTime = SystemClock.uptimeMillis();
createImageItems(mediaImageItem, imageItemFileName, renderingMode,
@@ -296,7 +295,7 @@
final int transitionDuration = 5000;
final int transitionBehavior = Transition.BEHAVIOR_MIDDLE_FAST;
final String[] loggingInfo = new String[3];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final MediaVideoItem[] mediaVideoItem =
new MediaVideoItem[(NUM_OF_ITERATIONS *10) + 1];
@@ -514,7 +513,7 @@
/** 18.Enable Looping for Audio Track.
* */
audioTrack.enableLoop();
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final long beginTime = SystemClock.uptimeMillis();
try {
mVideoEditor.export(outFilename, outHeight, outBitrate,
@@ -557,7 +556,7 @@
mediaVideoItem.setExtractBoundaries(videoItemStartTime,
videoItemEndTime);
- float timeTaken = 0.0f;
+ int timeTaken = 0;
long beginTime = SystemClock.uptimeMillis();
for (int i = 0; i < NUM_OF_ITERATIONS; i++) {
mediaVideoItem.getThumbnail(mediaVideoItem.getWidth() / 2,
@@ -603,7 +602,7 @@
final OverlayFrame overlayFrame[] = new OverlayFrame[NUM_OF_ITERATIONS];
final Bitmap mBitmap = mVideoEditorHelper.getBitmap(overlayFilename,
640, 480);
- float timeTaken = 0.0f;
+ int timeTaken = 0;
long beginTime = SystemClock.uptimeMillis();
for (int i = 0; i < NUM_OF_ITERATIONS; i++) {
overlayFrame[i] = new OverlayFrame(mediaVideoItem, "overlay" + i,
@@ -647,7 +646,7 @@
final int videoProfile = MediaProperties.H264_PROFILE_0_LEVEL_1_3;
final int width = 1080;
final int height = MediaProperties.HEIGHT_720;
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final String[] loggingInfo = new String[1];
final MediaVideoItem mediaVideoItem = new MediaVideoItem(mVideoEditor,
"m0", videoItemFileName1, renderingMode);
@@ -1006,7 +1005,7 @@
final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
final int audioVolume = 50;
final String[] loggingInfo = new String[2];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final MediaVideoItem mediaVideoItem = new MediaVideoItem(mVideoEditor,
"mediaItem1", videoItemFileName1, renderingMode);
@@ -1057,7 +1056,7 @@
final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
final String[] loggingInfo = new String[3];
- float timeTaken = 0.0f;
+ int timeTaken = 0;
final MediaImageItem[] mediaImageItem =
new MediaImageItem[NUM_OF_ITERATIONS];
diff --git a/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml b/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
index 109cfff..a4564e6 100644
--- a/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
+++ b/packages/BackupRestoreConfirmation/res/layout/confirm_backup.xml
@@ -37,6 +37,7 @@
android:layout_marginBottom="30dp" />
<Button android:id="@+id/button_allow"
+ android:filterTouchesWhenObscured="true"
android:text="@string/allow_backup_button_label"
android:layout_below="@id/package_name"
android:layout_height="wrap_content"
diff --git a/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml b/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
index a1f9a4a..ca99ae1 100644
--- a/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
+++ b/packages/BackupRestoreConfirmation/res/layout/confirm_restore.xml
@@ -37,6 +37,7 @@
android:layout_marginBottom="30dp" />
<Button android:id="@+id/button_allow"
+ android:filterTouchesWhenObscured="true"
android:text="@string/allow_restore_button_label"
android:layout_below="@id/package_name"
android:layout_height="wrap_content"
diff --git a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
index 4b42067..52bfc28 100644
--- a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
+++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
@@ -98,6 +98,8 @@
break;
case MSG_RESTORE_PACKAGE: {
+ String name = (String) msg.obj;
+ mStatusView.setText(name);
}
break;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 45bb2b6..0c4ef7d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -55,6 +55,7 @@
*/
public class SettingsBackupAgent extends BackupAgentHelper {
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_BACKUP = DEBUG || true;
private static final String KEY_SYSTEM = "system";
private static final String KEY_SECURE = "secure";
@@ -75,6 +76,9 @@
private static final int STATE_WIFI_CONFIG = 4;
private static final int STATE_SIZE = 5; // The number of state items
+ // Versioning of the 'full backup' format
+ private static final int FULL_BACKUP_VERSION = 1;
+
private static String[] sortedSystemKeys = null;
private static String[] sortedSecureKeys = null;
@@ -109,6 +113,8 @@
private static String mWifiConfigFile;
public void onCreate() {
+ if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked");
+
mSettingsHelper = new SettingsHelper(this);
super.onCreate();
@@ -151,26 +157,32 @@
// representation of the backed-up settings.
String root = getFilesDir().getAbsolutePath();
File stage = new File(root, STAGE_FILE);
- FileOutputStream filestream = new FileOutputStream(stage);
- BufferedOutputStream bufstream = new BufferedOutputStream(filestream);
- DataOutputStream out = new DataOutputStream(bufstream);
+ try {
+ FileOutputStream filestream = new FileOutputStream(stage);
+ BufferedOutputStream bufstream = new BufferedOutputStream(filestream);
+ DataOutputStream out = new DataOutputStream(bufstream);
- out.writeInt(systemSettingsData.length);
- out.write(systemSettingsData);
- out.writeInt(secureSettingsData.length);
- out.write(secureSettingsData);
- out.writeInt(locale.length);
- out.write(locale);
- out.writeInt(wifiSupplicantData.length);
- out.write(wifiSupplicantData);
- out.writeInt(wifiConfigData.length);
- out.write(wifiConfigData);
+ out.writeInt(FULL_BACKUP_VERSION);
- out.flush(); // also flushes downstream
+ out.writeInt(systemSettingsData.length);
+ out.write(systemSettingsData);
+ out.writeInt(secureSettingsData.length);
+ out.write(secureSettingsData);
+ out.writeInt(locale.length);
+ out.write(locale);
+ out.writeInt(wifiSupplicantData.length);
+ out.write(wifiSupplicantData);
+ out.writeInt(wifiConfigData.length);
+ out.write(wifiConfigData);
- // now we're set to emit the tar stream
- FullBackup.backupToTar(getPackageName(), FullBackup.DATA_TREE_TOKEN, null,
- root, stage.getAbsolutePath(), data);
+ out.flush(); // also flushes downstream
+
+ // now we're set to emit the tar stream
+ FullBackup.backupToTar(getPackageName(), FullBackup.DATA_TREE_TOKEN, null,
+ root, stage.getAbsolutePath(), data);
+ } finally {
+ stage.delete();
+ }
}
}
@@ -199,7 +211,7 @@
} else if (KEY_LOCALE.equals(key)) {
byte[] localeData = new byte[size];
data.readEntityData(localeData, 0, size);
- mSettingsHelper.setLocaleData(localeData);
+ mSettingsHelper.setLocaleData(localeData, size);
} else if (KEY_WIFI_CONFIG.equals(key)) {
restoreFileData(mWifiConfigFile, data);
} else {
@@ -208,6 +220,70 @@
}
}
+ @Override
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String relpath, long mode, long mtime)
+ throws IOException {
+ if (DEBUG_BACKUP) Log.d(TAG, "onRestoreFile() invoked");
+ // Our data is actually a blob of flattened settings data identical to that
+ // produced during incremental backups. Just unpack and apply it all in
+ // turn.
+ FileInputStream instream = new FileInputStream(data.getFileDescriptor());
+ DataInputStream in = new DataInputStream(instream);
+
+ int version = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, "Flattened data version " + version);
+ if (version == FULL_BACKUP_VERSION) {
+ // system settings data first
+ int nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of settings data");
+ byte[] buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI);
+
+ // secure settings
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI);
+
+ // locale
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of locale data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ mSettingsHelper.setLocaleData(buffer, nBytes);
+
+ // wifi supplicant
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi supplicant data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ int retainedWifiState = enableWifi(false);
+ restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes);
+ FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR |
+ FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+ Process.myUid(), Process.WIFI_UID);
+ // retain the previous WIFI state.
+ enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED ||
+ retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
+
+ // wifi config
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi config data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ restoreFileData(mWifiConfigFile, buffer, nBytes);
+
+ if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
+ } else {
+ data.close();
+ throw new IOException("Invalid file schema");
+ }
+ }
+
private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException {
long[] stateChecksums = new long[STATE_SIZE];
@@ -287,6 +363,17 @@
}
private void restoreSettings(BackupDataInput data, Uri contentUri) {
+ byte[] settings = new byte[data.getDataSize()];
+ try {
+ data.readEntityData(settings, 0, settings.length);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Couldn't read entity data");
+ return;
+ }
+ restoreSettings(settings, settings.length, contentUri);
+ }
+
+ private void restoreSettings(byte[] settings, int bytes, Uri contentUri) {
if (DEBUG) Log.i(TAG, "restoreSettings: " + contentUri);
String[] whitelist = null;
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
@@ -296,15 +383,8 @@
}
ContentValues cv = new ContentValues(2);
- byte[] settings = new byte[data.getDataSize()];
- try {
- data.readEntityData(settings, 0, settings.length);
- } catch (IOException ioe) {
- Log.e(TAG, "Couldn't read entity data");
- return;
- }
int pos = 0;
- while (pos < settings.length) {
+ while (pos < bytes) {
int length = readInt(settings, pos);
pos += 4;
String settingName = length > 0? new String(settings, pos, length) : null;
@@ -451,13 +531,16 @@
private void restoreFileData(String filename, BackupDataInput data) {
byte[] bytes = new byte[data.getDataSize()];
if (bytes.length <= 0) return;
+ restoreFileData(filename, bytes, bytes.length);
+ }
+
+ private void restoreFileData(String filename, byte[] bytes, int size) {
try {
- data.readEntityData(bytes, 0, bytes.length);
File file = new File(filename);
if (file.exists()) file.delete();
OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
- os.write(bytes);
+ os.write(bytes, 0, size);
os.close();
} catch (IOException ioe) {
Log.w(TAG, "Couldn't restore " + filename);
@@ -506,15 +589,18 @@
private void restoreWifiSupplicant(String filename, BackupDataInput data) {
byte[] bytes = new byte[data.getDataSize()];
if (bytes.length <= 0) return;
+ restoreWifiSupplicant(filename, bytes, bytes.length);
+ }
+
+ private void restoreWifiSupplicant(String filename, byte[] bytes, int size) {
try {
- data.readEntityData(bytes, 0, bytes.length);
File supplicantFile = new File(FILE_WIFI_SUPPLICANT);
if (supplicantFile.exists()) supplicantFile.delete();
copyWifiSupplicantTemplate();
OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
os.write("\n".getBytes());
- os.write(bytes);
+ os.write(bytes, 0, size);
os.close();
} catch (IOException ioe) {
Log.w(TAG, "Couldn't restore " + filename);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 0e75fbc..3e7d86a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -147,7 +147,7 @@
* "ll" is the language code and "cc" is the country code.
* @param data the locale string in bytes.
*/
- void setLocaleData(byte[] data) {
+ void setLocaleData(byte[] data, int size) {
// Check if locale was set by the user:
Configuration conf = mContext.getResources().getConfiguration();
Locale loc = conf.locale;
@@ -157,9 +157,9 @@
if (conf.userSetLocale) return; // Don't change if user set it in the SetupWizard
final String[] availableLocales = mContext.getAssets().getLocales();
- String localeCode = new String(data);
+ String localeCode = new String(data, 0, size);
String language = new String(data, 0, 2);
- String country = data.length > 4 ? new String(data, 3, 2) : "";
+ String country = size > 4 ? new String(data, 3, 2) : "";
loc = null;
for (int i = 0; i < availableLocales.length; i++) {
if (availableLocales[i].equals(localeCode)) {
diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
index ca56c4f..57b6b5e 100644
--- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
@@ -27,6 +27,7 @@
import android.os.CountDownTimer;
import android.os.SystemClock;
+import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.text.InputType;
import android.text.method.DigitsKeyListener;
@@ -231,6 +232,7 @@
mCallback.keyguardDone(true);
mCallback.reportSuccessfulUnlockAttempt();
mStatusView.setInstructionText(null);
+ KeyStore.getInstance().password(entry);
} else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) {
// to avoid accidental lockout, only count attempts that are long enough to be a
// real password. This may require some tweaking.
diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
index 018fe0c..a685497 100644
--- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
@@ -20,6 +20,7 @@
import android.content.res.Configuration;
import android.os.CountDownTimer;
import android.os.SystemClock;
+import android.security.KeyStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -407,6 +408,7 @@
mStatusView.updateStatusLines(true);
mCallback.keyguardDone(true);
mCallback.reportSuccessfulUnlockAttempt();
+ KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern));
} else {
boolean reportFailedAttempt = false;
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 053854f..f806624 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -360,8 +360,8 @@
pid_t pid,
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
const sp<IMemory>& sharedBuffer,
@@ -429,7 +429,7 @@
LOGV("createTrack() lSessionId: %d", lSessionId);
track = thread->createTrack_l(client, streamType, sampleRate, format,
- channelCount, frameCount, sharedBuffer, lSessionId, &lStatus);
+ channelMask, frameCount, sharedBuffer, lSessionId, &lStatus);
// move effect chain to this output thread if an effect on same session was waiting
// for a track to be created
@@ -477,7 +477,7 @@
return thread->channelCount();
}
-int AudioFlinger::format(int output) const
+uint32_t AudioFlinger::format(int output) const
{
Mutex::Autolock _l(mLock);
PlaybackThread *thread = checkPlaybackThread_l(output);
@@ -916,7 +916,7 @@
return (int)mChannelCount;
}
-int AudioFlinger::ThreadBase::format() const
+uint32_t AudioFlinger::ThreadBase::format() const
{
return mFormat;
}
@@ -1002,6 +1002,8 @@
result.append(buffer);
snprintf(buffer, SIZE, "Channel Count: %d\n", mChannelCount);
result.append(buffer);
+ snprintf(buffer, SIZE, "Channel Mask: 0x%08x\n", mChannelMask);
+ result.append(buffer);
snprintf(buffer, SIZE, "Format: %d\n", mFormat);
result.append(buffer);
snprintf(buffer, SIZE, "Frame size: %d\n", mFrameSize);
@@ -1075,7 +1077,7 @@
snprintf(buffer, SIZE, "Output thread %p tracks\n", this);
result.append(buffer);
- result.append(" Name Clien Typ Fmt Chn Session Buf S M F SRate LeftV RighV Serv User Main buf Aux Buf\n");
+ result.append(" Name Clien Typ Fmt Chn mask Session Buf S M F SRate LeftV RighV Serv User Main buf Aux Buf\n");
for (size_t i = 0; i < mTracks.size(); ++i) {
sp<Track> track = mTracks[i];
if (track != 0) {
@@ -1086,7 +1088,7 @@
snprintf(buffer, SIZE, "Output thread %p active tracks\n", this);
result.append(buffer);
- result.append(" Name Clien Typ Fmt Chn Session Buf S M F SRate LeftV RighV Serv User Main buf Aux Buf\n");
+ result.append(" Name Clien Typ Fmt Chn mask Session Buf S M F SRate LeftV RighV Serv User Main buf Aux Buf\n");
for (size_t i = 0; i < mActiveTracks.size(); ++i) {
wp<Track> wTrack = mActiveTracks[i];
if (wTrack != 0) {
@@ -1172,8 +1174,8 @@
const sp<AudioFlinger::Client>& client,
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
@@ -1183,11 +1185,14 @@
status_t lStatus;
if (mType == DIRECT) {
- if (sampleRate != mSampleRate || format != mFormat || channelCount != (int)mChannelCount) {
- LOGE("createTrack_l() Bad parameter: sampleRate %d format %d, channelCount %d for output %p",
- sampleRate, format, channelCount, mOutput);
- lStatus = BAD_VALUE;
- goto Exit;
+ if ((format & AUDIO_FORMAT_MAIN_MASK) == AUDIO_FORMAT_PCM) {
+ if (sampleRate != mSampleRate || format != mFormat || channelMask != mChannelMask) {
+ LOGE("createTrack_l() Bad parameter: sampleRate %d format %d, channelMask 0x%08x \""
+ "for output %p with format %d",
+ sampleRate, format, channelMask, mOutput, mFormat);
+ lStatus = BAD_VALUE;
+ goto Exit;
+ }
}
} else {
// Resampler implementation limits input sampling rate to 2 x output sampling rate.
@@ -1224,7 +1229,7 @@
}
track = new Track(this, client, streamType, sampleRate, format,
- channelCount, frameCount, sharedBuffer, sessionId);
+ channelMask, frameCount, sharedBuffer, sessionId);
if (track->getCblk() == NULL || track->name() < 0) {
lStatus = NO_MEMORY;
goto Exit;
@@ -1373,7 +1378,7 @@
switch (event) {
case AudioSystem::OUTPUT_OPENED:
case AudioSystem::OUTPUT_CONFIG_CHANGED:
- desc.channels = mChannels;
+ desc.channels = mChannelMask;
desc.samplingRate = mSampleRate;
desc.format = mFormat;
desc.frameCount = mFrameCount;
@@ -1393,8 +1398,8 @@
void AudioFlinger::PlaybackThread::readOutputParameters()
{
mSampleRate = mOutput->stream->common.get_sample_rate(&mOutput->stream->common);
- mChannels = mOutput->stream->common.get_channels(&mOutput->stream->common);
- mChannelCount = (uint16_t)popcount(mChannels);
+ mChannelMask = mOutput->stream->common.get_channels(&mOutput->stream->common);
+ mChannelCount = (uint16_t)popcount(mChannelMask);
mFormat = mOutput->stream->common.get_format(&mOutput->stream->common);
mFrameSize = (uint16_t)audio_stream_frame_size(&mOutput->stream->common);
mFrameCount = mOutput->stream->common.get_buffer_size(&mOutput->stream->common) / mFrameSize;
@@ -1804,7 +1809,7 @@
AudioMixer::FORMAT, (void *)track->format());
mAudioMixer->setParameter(
AudioMixer::TRACK,
- AudioMixer::CHANNEL_COUNT, (void *)track->channelCount());
+ AudioMixer::CHANNEL_MASK, (void *)track->channelMask());
mAudioMixer->setParameter(
AudioMixer::RESAMPLE,
AudioMixer::SAMPLE_RATE,
@@ -2683,7 +2688,7 @@
this,
mSampleRate,
mFormat,
- mChannelCount,
+ mChannelMask,
frameCount);
if (outputTrack->cblk() != NULL) {
thread->setStreamVolume(AUDIO_STREAM_CNT, 1.0f);
@@ -2751,8 +2756,8 @@
const wp<ThreadBase>& thread,
const sp<Client>& client,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
const sp<IMemory>& sharedBuffer,
@@ -2772,6 +2777,7 @@
// LOGD("Creating track with %d buffers @ %d bytes", bufferCount, bufferSize);
size_t size = sizeof(audio_track_cblk_t);
+ uint8_t channelCount = popcount(channelMask);
size_t bufferSize = frameCount*channelCount*sizeof(int16_t);
if (sharedBuffer == 0) {
size += bufferSize;
@@ -2786,7 +2792,8 @@
// clear all buffers
mCblk->frameCount = frameCount;
mCblk->sampleRate = sampleRate;
- mCblk->channelCount = (uint8_t)channelCount;
+ mChannelCount = channelCount;
+ mChannelMask = channelMask;
if (sharedBuffer == 0) {
mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
@@ -2810,7 +2817,8 @@
// clear all buffers
mCblk->frameCount = frameCount;
mCblk->sampleRate = sampleRate;
- mCblk->channelCount = (uint8_t)channelCount;
+ mChannelCount = channelCount;
+ mChannelMask = channelMask;
mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
// Force underrun condition to avoid false underrun callback until first data is
@@ -2877,7 +2885,11 @@
}
int AudioFlinger::ThreadBase::TrackBase::channelCount() const {
- return (int)mCblk->channelCount;
+ return (const int)mChannelCount;
+}
+
+uint32_t AudioFlinger::ThreadBase::TrackBase::channelMask() const {
+ return mChannelMask;
}
void* AudioFlinger::ThreadBase::TrackBase::getBuffer(uint32_t offset, uint32_t frames) const {
@@ -2889,9 +2901,9 @@
if (bufferStart < mBuffer || bufferStart > bufferEnd || bufferEnd > mBufferEnd ||
((unsigned long)bufferStart & (unsigned long)(cblk->frameSize - 1))) {
LOGE("TrackBase::getBuffer buffer out of range:\n start: %p, end %p , mBuffer %p mBufferEnd %p\n \
- server %d, serverBase %d, user %d, userBase %d, channelCount %d",
+ server %d, serverBase %d, user %d, userBase %d",
bufferStart, bufferEnd, mBuffer, mBufferEnd,
- cblk->server, cblk->serverBase, cblk->user, cblk->userBase, cblk->channelCount);
+ cblk->server, cblk->serverBase, cblk->user, cblk->userBase);
return 0;
}
@@ -2906,12 +2918,12 @@
const sp<Client>& client,
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId)
- : TrackBase(thread, client, sampleRate, format, channelCount, frameCount, 0, sharedBuffer, sessionId),
+ : TrackBase(thread, client, sampleRate, format, channelMask, frameCount, 0, sharedBuffer, sessionId),
mMute(false), mSharedBuffer(sharedBuffer), mName(-1), mMainBuffer(NULL), mAuxBuffer(NULL),
mAuxEffectId(0), mHasVolumeController(false)
{
@@ -2931,7 +2943,7 @@
mStreamType = streamType;
// NOTE: audio_track_cblk_t::frameSize for 8 bit PCM data is based on a sample size of
// 16 bit because data is converted to 16 bit before being stored in buffer by AudioTrack
- mCblk->frameSize = audio_is_linear_pcm(format) ? channelCount * sizeof(int16_t) : sizeof(int8_t);
+ mCblk->frameSize = audio_is_linear_pcm(format) ? mChannelCount * sizeof(int16_t) : sizeof(int8_t);
}
}
@@ -2979,12 +2991,12 @@
void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size)
{
- snprintf(buffer, size, " %05d %05d %03u %03u %03u %05u %04u %1d %1d %1d %05u %05u %05u 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ snprintf(buffer, size, " %05d %05d %03u %03u 0x%08x %05u %04u %1d %1d %1d %05u %05u %05u 0x%08x 0x%08x 0x%08x 0x%08x\n",
mName - AudioMixer::TRACK0,
(mClient == NULL) ? getpid() : mClient->pid(),
mStreamType,
mFormat,
- mCblk->channelCount,
+ mChannelMask,
mSessionId,
mFrameCount,
mState,
@@ -3219,21 +3231,21 @@
const wp<ThreadBase>& thread,
const sp<Client>& client,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
int sessionId)
: TrackBase(thread, client, sampleRate, format,
- channelCount, frameCount, flags, 0, sessionId),
+ channelMask, frameCount, flags, 0, sessionId),
mOverflow(false)
{
if (mCblk != NULL) {
LOGV("RecordTrack constructor, size %d", (int)mBufferEnd - (int)mBuffer);
if (format == AUDIO_FORMAT_PCM_16_BIT) {
- mCblk->frameSize = channelCount * sizeof(int16_t);
+ mCblk->frameSize = mChannelCount * sizeof(int16_t);
} else if (format == AUDIO_FORMAT_PCM_8_BIT) {
- mCblk->frameSize = channelCount * sizeof(int8_t);
+ mCblk->frameSize = mChannelCount * sizeof(int8_t);
} else {
mCblk->frameSize = sizeof(int8_t);
}
@@ -3313,10 +3325,10 @@
void AudioFlinger::RecordThread::RecordTrack::dump(char* buffer, size_t size)
{
- snprintf(buffer, size, " %05d %03u %03u %05d %04u %01d %05u %08x %08x\n",
+ snprintf(buffer, size, " %05d %03u 0x%08x %05d %04u %01d %05u %08x %08x\n",
(mClient == NULL) ? getpid() : mClient->pid(),
mFormat,
- mCblk->channelCount,
+ mChannelMask,
mSessionId,
mFrameCount,
mState,
@@ -3332,10 +3344,10 @@
const wp<ThreadBase>& thread,
DuplicatingThread *sourceThread,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount)
- : Track(thread, NULL, AUDIO_STREAM_CNT, sampleRate, format, channelCount, frameCount, NULL, 0),
+ : Track(thread, NULL, AUDIO_STREAM_CNT, sampleRate, format, channelMask, frameCount, NULL, 0),
mActive(false), mSourceThread(sourceThread)
{
@@ -3346,8 +3358,10 @@
mCblk->volume[0] = mCblk->volume[1] = 0x1000;
mOutBuffer.frameCount = 0;
playbackThread->mTracks.add(this);
- LOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, mCblk->frameCount %d, mCblk->sampleRate %d, mCblk->channelCount %d mBufferEnd %p",
- mCblk, mBuffer, mCblk->buffers, mCblk->frameCount, mCblk->sampleRate, mCblk->channelCount, mBufferEnd);
+ LOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, " \
+ "mCblk->frameCount %d, mCblk->sampleRate %d, mChannelMask 0x%08x mBufferEnd %p",
+ mCblk, mBuffer, mCblk->buffers,
+ mCblk->frameCount, mCblk->sampleRate, mChannelMask, mBufferEnd);
} else {
LOGW("Error creating output track on thread %p", playbackThread);
}
@@ -3382,7 +3396,7 @@
{
Buffer *pInBuffer;
Buffer inBuffer;
- uint32_t channelCount = mCblk->channelCount;
+ uint32_t channelCount = mChannelCount;
bool outputBufferFull = false;
inBuffer.frameCount = frames;
inBuffer.i16 = data;
@@ -3667,8 +3681,8 @@
pid_t pid,
int input,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
int *sessionId,
@@ -3717,7 +3731,7 @@
}
// create new record track. The record track uses one track in mHardwareMixerThread by convention.
recordTrack = new RecordThread::RecordTrack(thread, client, sampleRate,
- format, channelCount, frameCount, flags, lSessionId);
+ format, channelMask, frameCount, flags, lSessionId);
}
if (recordTrack->getCblk() == NULL) {
// remove local strong reference to Client before deleting the RecordTrack so that the Client
@@ -4065,7 +4079,7 @@
if (mActiveTrack != 0) {
result.append("Active Track:\n");
- result.append(" Clien Fmt Chn Session Buf S SRate Serv User\n");
+ result.append(" Clien Fmt Chn mask Session Buf S SRate Serv User\n");
mActiveTrack->dump(buffer, SIZE);
result.append(buffer);
@@ -4219,7 +4233,7 @@
switch (event) {
case AudioSystem::INPUT_OPENED:
case AudioSystem::INPUT_CONFIG_CHANGED:
- desc.channels = mChannels;
+ desc.channels = mChannelMask;
desc.samplingRate = mSampleRate;
desc.format = mFormat;
desc.frameCount = mFrameCount;
@@ -4242,8 +4256,8 @@
mResampler = 0;
mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common);
- mChannels = mInput->stream->common.get_channels(&mInput->stream->common);
- mChannelCount = (uint16_t)popcount(mChannels);
+ mChannelMask = mInput->stream->common.get_channels(&mInput->stream->common);
+ mChannelCount = (uint16_t)popcount(mChannelMask);
mFormat = mInput->stream->common.get_format(&mInput->stream->common);
mFrameSize = (uint16_t)audio_stream_frame_size(&mInput->stream->common);
mInputBytes = mInput->stream->common.get_buffer_size(&mInput->stream->common);
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 0698dcb..f3371bf 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -76,8 +76,8 @@
pid_t pid,
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
const sp<IMemory>& sharedBuffer,
@@ -87,7 +87,7 @@
virtual uint32_t sampleRate(int output) const;
virtual int channelCount(int output) const;
- virtual int format(int output) const;
+ virtual uint32_t format(int output) const;
virtual size_t frameCount(int output) const;
virtual uint32_t latency(int output) const;
@@ -189,8 +189,8 @@
pid_t pid,
int input,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
int *sessionId,
@@ -301,8 +301,8 @@
TrackBase(const wp<ThreadBase>& thread,
const sp<Client>& client,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
const sp<IMemory>& sharedBuffer,
@@ -329,12 +329,14 @@
virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0;
virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
- int format() const {
+ uint32_t format() const {
return mFormat;
}
int channelCount() const ;
+ uint32_t channelMask() const;
+
int sampleRate() const;
void* getBuffer(uint32_t offset, uint32_t frames) const;
@@ -360,9 +362,11 @@
// we don't really need a lock for these
int mState;
int mClientTid;
- uint8_t mFormat;
+ uint32_t mFormat;
uint32_t mFlags;
int mSessionId;
+ uint8_t mChannelCount;
+ uint32_t mChannelMask;
};
class ConfigEvent {
@@ -375,7 +379,7 @@
uint32_t sampleRate() const;
int channelCount() const;
- int format() const;
+ uint32_t format() const;
size_t frameCount() const;
void wakeUp() { mWaitWorkCV.broadcast(); }
void exit();
@@ -406,10 +410,10 @@
sp<AudioFlinger> mAudioFlinger;
uint32_t mSampleRate;
size_t mFrameCount;
- uint32_t mChannels;
+ uint32_t mChannelMask;
uint16_t mChannelCount;
uint16_t mFrameSize;
- int mFormat;
+ uint32_t mFormat;
Condition mParamCond;
Vector<String8> mNewParameters;
status_t mParamStatus;
@@ -442,8 +446,8 @@
const sp<Client>& client,
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId);
@@ -530,8 +534,8 @@
OutputTrack( const wp<ThreadBase>& thread,
DuplicatingThread *sourceThread,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount);
~OutputTrack();
@@ -583,8 +587,8 @@
const sp<AudioFlinger::Client>& client,
int streamType,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
@@ -829,8 +833,8 @@
RecordTrack(const wp<ThreadBase>& thread,
const sp<Client>& client,
uint32_t sampleRate,
- int format,
- int channelCount,
+ uint32_t format,
+ uint32_t channelMask,
int frameCount,
uint32_t flags,
int sessionId);
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index 50dcda7..6e9319d 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -26,6 +26,10 @@
#include <utils/Errors.h>
#include <utils/Log.h>
+#include <cutils/bitops.h>
+
+#include <system/audio.h>
+
#include "AudioMixer.h"
namespace android {
@@ -61,6 +65,7 @@
t->channelCount = 2;
t->enabled = 0;
t->format = 16;
+ t->channelMask = AUDIO_CHANNEL_OUT_STEREO;
t->buffer.raw = 0;
t->bufferProvider = 0;
t->hook = 0;
@@ -180,13 +185,18 @@
switch (target) {
case TRACK:
- if (name == CHANNEL_COUNT) {
- if ((uint32_t(valueInt) <= MAX_NUM_CHANNELS) && (valueInt)) {
- if (mState.tracks[ mActiveTrack ].channelCount != valueInt) {
- mState.tracks[ mActiveTrack ].channelCount = valueInt;
- LOGV("setParameter(TRACK, CHANNEL_COUNT, %d)", valueInt);
+ if (name == CHANNEL_MASK) {
+ uint32_t mask = (uint32_t)value;
+ if (mState.tracks[ mActiveTrack ].channelMask != mask) {
+ uint8_t channelCount = popcount(mask);
+ if ((channelCount <= MAX_NUM_CHANNELS) && (channelCount)) {
+ mState.tracks[ mActiveTrack ].channelMask = mask;
+ mState.tracks[ mActiveTrack ].channelCount = channelCount;
+ LOGV("setParameter(TRACK, CHANNEL_MASK, %x)", mask);
invalidateState(1<<mActiveTrack);
+ return NO_ERROR;
}
+ } else {
return NO_ERROR;
}
}
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index 88408a7..75c9170 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -61,7 +61,7 @@
// set Parameter names
// for target TRACK
- CHANNEL_COUNT = 0x4000,
+ CHANNEL_MASK = 0x4000,
FORMAT = 0x4001,
MAIN_BUFFER = 0x4002,
AUX_BUFFER = 0x4003,
@@ -150,6 +150,7 @@
uint8_t enabled : 1;
uint8_t reserved0 : 3;
uint8_t format;
+ uint32_t channelMask;
AudioBufferProvider* bufferProvider;
mutable AudioBufferProvider::Buffer buffer;
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 455b4c2..cd58b9b9 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -39,6 +39,7 @@
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -46,6 +47,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -74,12 +76,16 @@
import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -398,6 +404,13 @@
break;
}
+ case MSG_RUN_FULL_RESTORE:
+ {
+ FullRestoreParams params = (FullRestoreParams)msg.obj;
+ (new PerformFullRestoreTask(params.fd, params.observer, params.latch)).run();
+ break;
+ }
+
case MSG_RUN_CLEAR:
{
ClearParams params = (ClearParams)msg.obj;
@@ -1236,7 +1249,7 @@
Slog.d(TAG, "awaiting agent for " + app);
// success; wait for the agent to arrive
- // only wait 10 seconds for the clear data to happen
+ // only wait 10 seconds for the bind to happen
long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
while (mConnecting && mConnectedAgent == null
&& (System.currentTimeMillis() < timeoutMark)) {
@@ -1677,6 +1690,7 @@
public void run() {
final List<PackageInfo> packagesToBackup;
+ Slog.i(TAG, "--- Performing full-dataset restore ---");
sendStartBackup();
// doAllApps supersedes the package set if any
@@ -1779,7 +1793,9 @@
// Version 1:
// package name
// package's versionCode
- // boolean: "1" if archive includes .apk, "0" otherwise
+ // platform versionCode
+ // getInstallerPackageName() for this package (maybe empty)
+ // boolean: "1" if archive includes .apk; any other string means not
// number of signatures == N
// N*: signature byte array in ascii format per Signature.toCharsString()
StringBuilder builder = new StringBuilder(4096);
@@ -1788,6 +1804,11 @@
printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
printer.println(pkg.packageName);
printer.println(Integer.toString(pkg.versionCode));
+ printer.println(Integer.toString(Build.VERSION.SDK_INT));
+
+ String installerName = mPackageManager.getInstallerPackageName(pkg.packageName);
+ printer.println((installerName != null) ? installerName : "");
+
printer.println(withApk ? "1" : "0");
if (pkg.signatures == null) {
printer.println("0");
@@ -1861,6 +1882,896 @@
}
+ // ----- Full restore from a file/socket -----
+
+ // Description of a file in the restore datastream
+ static class FileMetadata {
+ String packageName; // name of the owning app
+ String installerPackageName; // name of the market-type app that installed the owner
+ int type; // e.g. FullBackup.TYPE_DIRECTORY
+ String domain; // e.g. FullBackup.DATABASE_TREE_TOKEN
+ String path; // subpath within the semantic domain
+ long mode; // e.g. 0666 (actually int)
+ long mtime; // last mod time, UTC time_t (actually int)
+ long size; // bytes of content
+ }
+
+ enum RestorePolicy {
+ IGNORE,
+ ACCEPT,
+ ACCEPT_IF_APK
+ }
+
+ class PerformFullRestoreTask implements Runnable {
+ ParcelFileDescriptor mInputFile;
+ IFullBackupRestoreObserver mObserver;
+ AtomicBoolean mLatchObject;
+ IBackupAgent mAgent;
+ String mAgentPackage;
+ ApplicationInfo mTargetApp;
+ ParcelFileDescriptor[] mPipes = null;
+
+ // possible handling states for a given package in the restore dataset
+ final HashMap<String, RestorePolicy> mPackagePolicies
+ = new HashMap<String, RestorePolicy>();
+
+ // installer package names for each encountered app, derived from the manifests
+ final HashMap<String, String> mPackageInstallers = new HashMap<String, String>();
+
+ // Signatures for a given package found in its manifest file
+ final HashMap<String, Signature[]> mManifestSignatures
+ = new HashMap<String, Signature[]>();
+
+ // Packages we've already wiped data on when restoring their first file
+ final HashSet<String> mClearedPackages = new HashSet<String>();
+
+ PerformFullRestoreTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
+ AtomicBoolean latch) {
+ mInputFile = fd;
+ mObserver = observer;
+ mLatchObject = latch;
+ mAgent = null;
+ mAgentPackage = null;
+ mTargetApp = null;
+
+ // Which packages we've already wiped data on. We prepopulate this
+ // with a whitelist of packages known to be unclearable.
+ mClearedPackages.add("android");
+ mClearedPackages.add("com.android.backupconfirm");
+ mClearedPackages.add("com.android.providers.settings");
+ }
+
+ class RestoreFileRunnable implements Runnable {
+ IBackupAgent mAgent;
+ FileMetadata mInfo;
+ ParcelFileDescriptor mSocket;
+ int mToken;
+
+ RestoreFileRunnable(IBackupAgent agent, FileMetadata info,
+ ParcelFileDescriptor socket, int token) throws IOException {
+ mAgent = agent;
+ mInfo = info;
+ mToken = token;
+
+ // This class is used strictly for process-local binder invocations. The
+ // semantics of ParcelFileDescriptor differ in this case; in particular, we
+ // do not automatically get a 'dup'ed descriptor that we can can continue
+ // to use asynchronously from the caller. So, we make sure to dup it ourselves
+ // before proceeding to do the restore.
+ mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor());
+ }
+
+ @Override
+ public void run() {
+ try {
+ mAgent.doRestoreFile(mSocket, mInfo.size, mInfo.type,
+ mInfo.domain, mInfo.path, mInfo.mode, mInfo.mtime,
+ mToken, mBackupManagerBinder);
+ } catch (RemoteException e) {
+ // never happens; this is used strictly for local binder calls
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ Slog.i(TAG, "--- Performing full-dataset restore ---");
+ sendStartRestore();
+
+ try {
+ byte[] buffer = new byte[32 * 1024];
+ FileInputStream instream = new FileInputStream(mInputFile.getFileDescriptor());
+
+ boolean didRestore;
+ do {
+ didRestore = restoreOneFile(instream, buffer);
+ } while (didRestore);
+
+ if (DEBUG) Slog.v(TAG, "Done consuming input tarfile");
+ } finally {
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
+
+ try {
+ mInputFile.close();
+ } catch (IOException e) {
+ /* nothing we can do about this */
+ }
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.clear();
+ }
+ synchronized (mLatchObject) {
+ mLatchObject.set(true);
+ mLatchObject.notifyAll();
+ }
+ sendEndRestore();
+ mWakelock.release();
+ if (DEBUG) Slog.d(TAG, "Full restore pass complete.");
+ }
+ }
+
+ boolean restoreOneFile(InputStream instream, byte[] buffer) {
+ FileMetadata info;
+ try {
+ info = readTarHeaders(instream);
+ if (info != null) {
+ if (DEBUG) {
+ dumpFileMetadata(info);
+ }
+
+ final String pkg = info.packageName;
+ if (!pkg.equals(mAgentPackage)) {
+ // okay, change in package; set up our various
+ // bookkeeping if we haven't seen it yet
+ if (!mPackagePolicies.containsKey(pkg)) {
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+
+ // Clean up the previous agent relationship if necessary,
+ // and let the observer know we're considering a new app.
+ if (mAgent != null) {
+ if (DEBUG) Slog.d(TAG, "Saw new package; tearing down old one");
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
+ mTargetApp = null;
+ mAgentPackage = null;
+ }
+ }
+
+ if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
+ mPackagePolicies.put(pkg, readAppManifest(info, instream));
+ mPackageInstallers.put(pkg, info.installerPackageName);
+ // We've read only the manifest content itself at this point,
+ // so consume the footer before looping around to the next
+ // input file
+ skipTarPadding(info.size, instream);
+ sendOnRestorePackage(pkg);
+ } else {
+ // Non-manifest, so it's actual file data. Is this a package
+ // we're ignoring?
+ boolean okay = true;
+ RestorePolicy policy = mPackagePolicies.get(pkg);
+ switch (policy) {
+ case IGNORE:
+ okay = false;
+ break;
+
+ case ACCEPT_IF_APK:
+ // If we're in accept-if-apk state, then the first file we
+ // see MUST be the apk.
+ if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
+ if (DEBUG) Slog.d(TAG, "APK file; installing");
+ // Try to install the app.
+ String installerName = mPackageInstallers.get(pkg);
+ okay = installApk(info, installerName, instream);
+ // good to go; promote to ACCEPT
+ mPackagePolicies.put(pkg, (okay)
+ ? RestorePolicy.ACCEPT
+ : RestorePolicy.IGNORE);
+ // At this point we've consumed this file entry
+ // ourselves, so just strip the tar footer and
+ // go on to the next file in the input stream
+ skipTarPadding(info.size, instream);
+ return true;
+ } else {
+ // File data before (or without) the apk. We can't
+ // handle it coherently in this case so ignore it.
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ okay = false;
+ }
+ break;
+
+ case ACCEPT:
+ if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
+ if (DEBUG) Slog.d(TAG, "apk present but ACCEPT");
+ // we can take the data without the apk, so we
+ // *want* to do so. skip the apk by declaring this
+ // one file not-okay without changing the restore
+ // policy for the package.
+ okay = false;
+ }
+ break;
+
+ default:
+ // Something has gone dreadfully wrong when determining
+ // the restore policy from the manifest. Ignore the
+ // rest of this package's data.
+ Slog.e(TAG, "Invalid policy from manifest");
+ okay = false;
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ break;
+ }
+
+ // If the policy is satisfied, go ahead and set up to pipe the
+ // data to the agent.
+ if (DEBUG && okay && mAgent != null) {
+ Slog.i(TAG, "Reusing existing agent instance");
+ }
+ if (okay && mAgent == null) {
+ if (DEBUG) Slog.d(TAG, "Need to launch agent for " + pkg);
+
+ try {
+ mTargetApp = mPackageManager.getApplicationInfo(pkg, 0);
+
+ // If we haven't sent any data to this app yet, we probably
+ // need to clear it first. Check that.
+ if (!mClearedPackages.contains(pkg)) {
+ // apps with their own full backup agents are
+ // responsible for coherently managing a full
+ // restore.
+ if (mTargetApp.fullBackupAgentName == null) {
+ if (DEBUG) Slog.d(TAG, "Clearing app data preparatory to full restore");
+ clearApplicationDataSynchronous(pkg);
+ } else {
+ if (DEBUG) Slog.d(TAG, "full backup agent ("
+ + mTargetApp.fullBackupAgentName + ") => no clear");
+ }
+ mClearedPackages.add(pkg);
+ } else {
+ if (DEBUG) Slog.d(TAG, "We've initialized this app already; no clear required");
+ }
+
+ // All set; now set up the IPC and launch the agent
+ setUpPipes();
+ mAgent = bindToAgentSynchronous(mTargetApp,
+ IApplicationThread.BACKUP_MODE_RESTORE_FULL);
+ mAgentPackage = pkg;
+ } catch (IOException e) {
+ // fall through to error handling
+ } catch (NameNotFoundException e) {
+ // fall through to error handling
+ }
+
+ if (mAgent == null) {
+ if (DEBUG) Slog.d(TAG, "Unable to create agent for " + pkg);
+ okay = false;
+ tearDownPipes();
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+ }
+
+ // Sanity check: make sure we never give data to the wrong app. This
+ // should never happen but a little paranoia here won't go amiss.
+ if (okay && !pkg.equals(mAgentPackage)) {
+ Slog.e(TAG, "Restoring data for " + pkg
+ + " but agent is for " + mAgentPackage);
+ okay = false;
+ }
+
+ // At this point we have an agent ready to handle the full
+ // restore data as well as a pipe for sending data to
+ // that agent. Tell the agent to start reading from the
+ // pipe.
+ if (okay) {
+ boolean agentSuccess = true;
+ long toCopy = info.size;
+ final int token = generateToken();
+ try {
+ if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ + info.path);
+ prepareOperationTimeout(token,
+ TIMEOUT_FULL_BACKUP_INTERVAL);
+ // fire up the app's agent listening on the socket. If
+ // the agent is running in the system process we can't
+ // just invoke it asynchronously, so we provide a thread
+ // for it here.
+ if (mTargetApp.processName.equals("system")) {
+ Slog.d(TAG, "system process agent - spinning a thread");
+ RestoreFileRunnable runner = new RestoreFileRunnable(
+ mAgent, info, mPipes[0], token);
+ new Thread(runner).start();
+ } else {
+ mAgent.doRestoreFile(mPipes[0], info.size, info.type,
+ info.domain, info.path, info.mode, info.mtime,
+ token, mBackupManagerBinder);
+ }
+ } catch (IOException e) {
+ // couldn't dup the socket for a process-local restore
+ Slog.d(TAG, "Couldn't establish restore");
+ agentSuccess = false;
+ okay = false;
+ } catch (RemoteException e) {
+ // whoops, remote agent went away. We'll eat the content
+ // ourselves, then, and not copy it over.
+ Slog.e(TAG, "Agent crashed during full restore");
+ agentSuccess = false;
+ okay = false;
+ }
+
+ // Copy over the data if the agent is still good
+ if (okay) {
+ boolean pipeOkay = true;
+ FileOutputStream pipe = new FileOutputStream(
+ mPipes[1].getFileDescriptor());
+ if (DEBUG) Slog.d(TAG, "Piping data to agent");
+ while (toCopy > 0) {
+ int toRead = (toCopy > buffer.length)
+ ? buffer.length : (int)toCopy;
+ int nRead = instream.read(buffer, 0, toRead);
+ if (nRead <= 0) break;
+ toCopy -= nRead;
+
+ // send it to the output pipe as long as things
+ // are still good
+ if (pipeOkay) {
+ try {
+ pipe.write(buffer, 0, nRead);
+ } catch (IOException e) {
+ Slog.e(TAG,
+ "Failed to write to restore pipe", e);
+ pipeOkay = false;
+ }
+ }
+ }
+
+ // done sending that file! Now we just need to consume
+ // the delta from info.size to the end of block.
+ skipTarPadding(info.size, instream);
+
+ // and now that we've sent it all, wait for the remote
+ // side to acknowledge receipt
+ agentSuccess = waitUntilOperationComplete(token);
+ }
+
+ // okay, if the remote end failed at any point, deal with
+ // it by ignoring the rest of the restore on it
+ if (!agentSuccess) {
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
+ mAgent = null;
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+ }
+
+ // Problems setting up the agent communication, or an already-
+ // ignored package: skip to the next tar stream entry by
+ // reading and discarding this file.
+ if (!okay) {
+ if (DEBUG) Slog.d(TAG, "[discarding file content]");
+ long bytesToConsume = (info.size + 511) & ~511;
+ while (bytesToConsume > 0) {
+ int toRead = (bytesToConsume > buffer.length)
+ ? buffer.length : (int)bytesToConsume;
+ long nRead = instream.read(buffer, 0, toRead);
+ if (nRead <= 0) break;
+ bytesToConsume -= nRead;
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "io exception on restore socket read", e);
+ // treat as EOF
+ info = null;
+ }
+
+ return (info != null);
+ }
+
+ void setUpPipes() throws IOException {
+ mPipes = ParcelFileDescriptor.createPipe();
+ }
+
+ void tearDownPipes() {
+ if (mPipes != null) {
+ if (mPipes[0] != null) {
+ try {
+ mPipes[0].close();
+ mPipes[0] = null;
+ mPipes[1].close();
+ mPipes[1] = null;
+ } catch (IOException e) {
+ Slog.w(TAG, "Couldn't close agent pipes", e);
+ }
+ }
+ mPipes = null;
+ }
+ }
+
+ void tearDownAgent(ApplicationInfo app) {
+ if (mAgent != null) {
+ try {
+ // unbind and tidy up even on timeout or failure, just in case
+ mActivityManager.unbindBackupAgent(app);
+
+ // The agent was running with a stub Application object, so shut it down.
+ // !!! We hardcode the confirmation UI's package name here rather than use a
+ // manifest flag! TODO something less direct.
+ if (app.uid != Process.SYSTEM_UID
+ && !app.packageName.equals("com.android.backupconfirm")) {
+ if (DEBUG) Slog.d(TAG, "Killing host process");
+ mActivityManager.killApplicationProcess(app.processName, app.uid);
+ } else {
+ if (DEBUG) Slog.d(TAG, "Not killing after full restore");
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Lost app trying to shut down");
+ }
+ mAgent = null;
+ }
+ }
+
+ class RestoreInstallObserver extends IPackageInstallObserver.Stub {
+ final AtomicBoolean mDone = new AtomicBoolean();
+ int mResult;
+
+ public void reset() {
+ synchronized (mDone) {
+ mDone.set(false);
+ }
+ }
+
+ public void waitForCompletion() {
+ synchronized (mDone) {
+ while (mDone.get() == false) {
+ try {
+ mDone.wait();
+ } catch (InterruptedException e) { }
+ }
+ }
+ }
+
+ int getResult() {
+ return mResult;
+ }
+
+ @Override
+ public void packageInstalled(String packageName, int returnCode)
+ throws RemoteException {
+ synchronized (mDone) {
+ mResult = returnCode;
+ mDone.set(true);
+ mDone.notifyAll();
+ }
+ }
+ }
+ final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
+
+ boolean installApk(FileMetadata info, String installerPackage, InputStream instream) {
+ boolean okay = true;
+
+ if (DEBUG) Slog.d(TAG, "Installing from backup: " + info.packageName);
+
+ // The file content is an .apk file. Copy it out to a staging location and
+ // attempt to install it.
+ File apkFile = new File(mDataDir, info.packageName);
+ try {
+ FileOutputStream apkStream = new FileOutputStream(apkFile);
+ byte[] buffer = new byte[32 * 1024];
+ long size = info.size;
+ while (size > 0) {
+ long toRead = (buffer.length < size) ? buffer.length : size;
+ int didRead = instream.read(buffer, 0, (int)toRead);
+ apkStream.write(buffer, 0, didRead);
+ size -= didRead;
+ }
+ apkStream.close();
+
+ // make sure the installer can read it
+ apkFile.setReadable(true, false);
+
+ // Now install it
+ Uri packageUri = Uri.fromFile(apkFile);
+ mInstallObserver.reset();
+ mPackageManager.installPackage(packageUri, mInstallObserver,
+ PackageManager.INSTALL_REPLACE_EXISTING, installerPackage);
+ mInstallObserver.waitForCompletion();
+
+ if (mInstallObserver.getResult() != PackageManager.INSTALL_SUCCEEDED) {
+ // The only time we continue to accept install of data even if the
+ // apk install failed is if we had already determined that we could
+ // accept the data regardless.
+ if (mPackagePolicies.get(info.packageName) != RestorePolicy.ACCEPT) {
+ okay = false;
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to transcribe restored apk for install");
+ okay = false;
+ } finally {
+ apkFile.delete();
+ }
+
+ return okay;
+ }
+
+ // Given an actual file content size, consume the post-content padding mandated
+ // by the tar format.
+ void skipTarPadding(long size, InputStream instream) throws IOException {
+ long partial = (size + 512) % 512;
+ if (partial > 0) {
+ byte[] buffer = new byte[512];
+ instream.read(buffer, 0, 512 - (int)partial);
+ }
+ }
+
+ // Returns a policy constant; takes a buffer arg to reduce memory churn
+ RestorePolicy readAppManifest(FileMetadata info, InputStream instream)
+ throws IOException {
+ // Fail on suspiciously large manifest files
+ if (info.size > 64 * 1024) {
+ throw new IOException("Restore manifest too big; corrupt? size=" + info.size);
+ }
+ byte[] buffer = new byte[(int) info.size];
+ int nRead = 0;
+ while (nRead < info.size) {
+ nRead += instream.read(buffer, nRead, (int)info.size - nRead);
+ }
+
+ RestorePolicy policy = RestorePolicy.IGNORE;
+ String[] str = new String[1];
+ int offset = 0;
+
+ try {
+ offset = extractLine(buffer, offset, str);
+ int version = Integer.parseInt(str[0]);
+ if (version == BACKUP_MANIFEST_VERSION) {
+ offset = extractLine(buffer, offset, str);
+ String manifestPackage = str[0];
+ // TODO: handle <original-package>
+ if (manifestPackage.equals(info.packageName)) {
+ offset = extractLine(buffer, offset, str);
+ version = Integer.parseInt(str[0]); // app version
+ offset = extractLine(buffer, offset, str);
+ int platformVersion = Integer.parseInt(str[0]);
+ offset = extractLine(buffer, offset, str);
+ info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
+ offset = extractLine(buffer, offset, str);
+ boolean hasApk = str[0].equals("1");
+ offset = extractLine(buffer, offset, str);
+ int numSigs = Integer.parseInt(str[0]);
+ Signature[] sigs = null;
+ if (numSigs > 0) {
+ sigs = new Signature[numSigs];
+ for (int i = 0; i < numSigs; i++) {
+ offset = extractLine(buffer, offset, str);
+ sigs[i] = new Signature(str[0]);
+ }
+
+ // Okay, got the manifest info we need...
+ try {
+ // Verify signatures against any installed version; if they
+ // don't match, then we fall though and ignore the data. The
+ // signatureMatch() method explicitly ignores the signature
+ // check for packages installed on the system partition, because
+ // such packages are signed with the platform cert instead of
+ // the app developer's cert, so they're different on every
+ // device.
+ PackageInfo pkgInfo = mPackageManager.getPackageInfo(
+ info.packageName, PackageManager.GET_SIGNATURES);
+ if (signaturesMatch(sigs, pkgInfo)) {
+ if (pkgInfo.versionCode >= version) {
+ Slog.i(TAG, "Sig + version match; taking data");
+ policy = RestorePolicy.ACCEPT;
+ } else {
+ // The data is from a newer version of the app than
+ // is presently installed. That means we can only
+ // use it if the matching apk is also supplied.
+ Slog.d(TAG, "Data version " + version
+ + " is newer than installed version "
+ + pkgInfo.versionCode + " - requiring apk");
+ policy = RestorePolicy.ACCEPT_IF_APK;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // Okay, the target app isn't installed. We can process
+ // the restore properly only if the dataset provides the
+ // apk file and we can successfully install it.
+ if (DEBUG) Slog.i(TAG, "Package " + info.packageName
+ + " not installed; requiring apk in dataset");
+ policy = RestorePolicy.ACCEPT_IF_APK;
+ }
+
+ if (policy == RestorePolicy.ACCEPT_IF_APK && !hasApk) {
+ Slog.i(TAG, "Cannot restore package " + info.packageName
+ + " without the matching .apk");
+ }
+ } else {
+ Slog.i(TAG, "Missing signature on backed-up package "
+ + info.packageName);
+ }
+ } else {
+ Slog.i(TAG, "Expected package " + info.packageName
+ + " but restore manifest claims " + manifestPackage);
+ }
+ } else {
+ Slog.i(TAG, "Unknown restore manifest version " + version
+ + " for package " + info.packageName);
+ }
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
+ }
+
+ return policy;
+ }
+
+ // Builds a line from a byte buffer starting at 'offset', and returns
+ // the index of the next unconsumed data in the buffer.
+ int extractLine(byte[] buffer, int offset, String[] outStr) throws IOException {
+ final int end = buffer.length;
+ if (offset >= end) throw new IOException("Incomplete data");
+
+ int pos;
+ for (pos = offset; pos < end; pos++) {
+ byte c = buffer[pos];
+ // at LF we declare end of line, and return the next char as the
+ // starting point for the next time through
+ if (c == '\n') {
+ break;
+ }
+ }
+ outStr[0] = new String(buffer, offset, pos - offset);
+ pos++; // may be pointing an extra byte past the end but that's okay
+ return pos;
+ }
+
+ void dumpFileMetadata(FileMetadata info) {
+ if (DEBUG) {
+ StringBuilder b = new StringBuilder(128);
+
+ // mode string
+ b.append((info.type == FullBackup.TYPE_DIRECTORY) ? 'd' : '-');
+ b.append(((info.mode & 0400) != 0) ? 'r' : '-');
+ b.append(((info.mode & 0200) != 0) ? 'w' : '-');
+ b.append(((info.mode & 0100) != 0) ? 'x' : '-');
+ b.append(((info.mode & 0040) != 0) ? 'r' : '-');
+ b.append(((info.mode & 0020) != 0) ? 'w' : '-');
+ b.append(((info.mode & 0010) != 0) ? 'x' : '-');
+ b.append(((info.mode & 0004) != 0) ? 'r' : '-');
+ b.append(((info.mode & 0002) != 0) ? 'w' : '-');
+ b.append(((info.mode & 0001) != 0) ? 'x' : '-');
+ b.append(String.format(" %9d ", info.size));
+
+ Date stamp = new Date(info.mtime);
+ b.append(new SimpleDateFormat("MMM dd kk:mm:ss ").format(stamp));
+
+ b.append(info.packageName);
+ b.append(" :: ");
+ b.append(info.domain);
+ b.append(" :: ");
+ b.append(info.path);
+
+ Slog.i(TAG, b.toString());
+ }
+ }
+ // Consume a tar file header block [sequence] and accumulate the relevant metadata
+ FileMetadata readTarHeaders(InputStream instream) throws IOException {
+ byte[] block = new byte[512];
+ FileMetadata info = null;
+
+ boolean gotHeader = readTarHeader(instream, block);
+ if (gotHeader) {
+ // okay, presume we're okay, and extract the various metadata
+ info = new FileMetadata();
+ info.size = extractRadix(block, 124, 12, 8);
+ info.mtime = extractRadix(block, 136, 12, 8);
+ info.mode = extractRadix(block, 100, 8, 8);
+
+ info.path = extractString(block, 345, 155); // prefix
+ String path = extractString(block, 0, 100);
+ if (path.length() > 0) {
+ if (info.path.length() > 0) info.path += '/';
+ info.path += path;
+ }
+
+ // tar link indicator field: 1 byte at offset 156 in the header.
+ int typeChar = block[156];
+ if (typeChar == 'x') {
+ // pax extended header, so we need to read that
+ gotHeader = readPaxExtendedHeader(instream, info);
+ if (gotHeader) {
+ // and after a pax extended header comes another real header -- read
+ // that to find the real file type
+ gotHeader = readTarHeader(instream, block);
+ }
+ if (!gotHeader) throw new IOException("Bad or missing pax header");
+
+ typeChar = block[156];
+ }
+
+ switch (typeChar) {
+ case '0': info.type = FullBackup.TYPE_FILE; break;
+ case '5': info.type = FullBackup.TYPE_DIRECTORY; break;
+ case 0: {
+ // presume EOF
+ return null;
+ }
+ default: {
+ Slog.e(TAG, "Unknown tar entity type: " + typeChar);
+ throw new IOException("Unknown entity type " + typeChar);
+ }
+ }
+
+ // Parse out the path
+ //
+ // first: apps/shared/unrecognized
+ if (FullBackup.SHARED_PREFIX.regionMatches(0,
+ info.path, 0, FullBackup.SHARED_PREFIX.length())) {
+ // File in shared storage. !!! TODO: implement this.
+ info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
+ info.domain = FullBackup.SHARED_STORAGE_TOKEN;
+ } else if (FullBackup.APPS_PREFIX.regionMatches(0,
+ info.path, 0, FullBackup.APPS_PREFIX.length())) {
+ // App content! Parse out the package name and domain
+
+ // strip the apps/ prefix
+ info.path = info.path.substring(FullBackup.APPS_PREFIX.length());
+
+ // extract the package name
+ int slash = info.path.indexOf('/');
+ if (slash < 0) throw new IOException("Illegal semantic path in " + info.path);
+ info.packageName = info.path.substring(0, slash);
+ info.path = info.path.substring(slash+1);
+
+ // if it's a manifest we're done, otherwise parse out the domains
+ if (!info.path.equals(BACKUP_MANIFEST_FILENAME)) {
+ slash = info.path.indexOf('/');
+ if (slash < 0) throw new IOException("Illegal semantic path in non-manifest " + info.path);
+ info.domain = info.path.substring(0, slash);
+ // validate that it's one of the domains we understand
+ if (!info.domain.equals(FullBackup.APK_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.DATA_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.DATABASE_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.ROOT_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.OBB_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
+ throw new IOException("Unrecognized domain " + info.domain);
+ }
+
+ info.path = info.path.substring(slash + 1);
+ }
+ }
+ }
+ return info;
+ }
+
+ boolean readTarHeader(InputStream instream, byte[] block) throws IOException {
+ int nRead = instream.read(block, 0, 512);
+ if (nRead > 0 && nRead != 512) {
+ // if we read only a partial block, then things are
+ // clearly screwed up. terminate the restore.
+ throw new IOException("Partial header block: " + nRead);
+ }
+ return (nRead > 0);
+ }
+
+ // overwrites 'info' fields based on the pax extended header
+ boolean readPaxExtendedHeader(InputStream instream, FileMetadata info)
+ throws IOException {
+ // We should never see a pax extended header larger than this
+ if (info.size > 32*1024) {
+ Slog.w(TAG, "Suspiciously large pax header size " + info.size
+ + " - aborting");
+ throw new IOException("Sanity failure: pax header size " + info.size);
+ }
+
+ // read whole blocks, not just the content size
+ int numBlocks = (int)((info.size + 511) >> 9);
+ byte[] data = new byte[numBlocks * 512];
+ int nRead = instream.read(data);
+ if (nRead != data.length) {
+ return false;
+ }
+
+ final int contentSize = (int) info.size;
+ int offset = 0;
+ do {
+ // extract the line at 'offset'
+ int eol = offset+1;
+ while (eol < contentSize && data[eol] != ' ') eol++;
+ if (eol >= contentSize) {
+ // error: we just hit EOD looking for the end of the size field
+ throw new IOException("Invalid pax data");
+ }
+ // eol points to the space between the count and the key
+ int linelen = (int) extractRadix(data, offset, eol - offset, 10);
+ int key = eol + 1; // start of key=value
+ eol = offset + linelen - 1; // trailing LF
+ int value;
+ for (value = key+1; data[value] != '=' && value <= eol; value++);
+ if (value > eol) {
+ throw new IOException("Invalid pax declaration");
+ }
+
+ // pax requires that key/value strings be in UTF-8
+ String keyStr = new String(data, key, value-key, "UTF-8");
+ // -1 to strip the trailing LF
+ String valStr = new String(data, value+1, eol-value-1, "UTF-8");
+
+ if ("path".equals(keyStr)) {
+ info.path = valStr;
+ } else if ("size".equals(keyStr)) {
+ info.size = Long.parseLong(valStr);
+ } else {
+ if (DEBUG) Slog.i(TAG, "Unhandled pax key: " + key);
+ }
+
+ offset += linelen;
+ } while (offset < contentSize);
+
+ return true;
+ }
+
+ long extractRadix(byte[] data, int offset, int maxChars, int radix)
+ throws IOException {
+ long value = 0;
+ final int end = offset + maxChars;
+ for (int i = offset; i < end; i++) {
+ final byte b = data[i];
+ if (b == 0 || b == ' ') break;
+ if (b < '0' || b > ('0' + radix - 1)) {
+ throw new IOException("Invalid number in header");
+ }
+ value = radix * value + (b - '0');
+ }
+ return value;
+ }
+
+ String extractString(byte[] data, int offset, int maxChars) throws IOException {
+ final int end = offset + maxChars;
+ int eos = offset;
+ // tar string fields can end with either NUL or SPC
+ while (eos < end && data[eos] != 0 && data[eos] != ' ') eos++;
+ return new String(data, offset, eos-offset, "US-ASCII");
+ }
+
+ void sendStartRestore() {
+ if (mObserver != null) {
+ try {
+ mObserver.onStartRestore();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: startRestore");
+ mObserver = null;
+ }
+ }
+ }
+
+ void sendOnRestorePackage(String name) {
+ if (mObserver != null) {
+ try {
+ // TODO: use a more user-friendly name string
+ mObserver.onRestorePackage(name);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: restorePackage");
+ mObserver = null;
+ }
+ }
+ }
+
+ void sendEndRestore() {
+ if (mObserver != null) {
+ try {
+ mObserver.onEndRestore();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: endRestore");
+ mObserver = null;
+ }
+ }
+ }
+ }
+
// ----- Restore handling -----
private boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
@@ -2583,41 +3494,95 @@
mFullConfirmations.put(token, params);
}
- // start up the confirmation UI, making sure the screen lights up
- if (DEBUG) Slog.d(TAG, "Starting confirmation UI, token=" + token);
- try {
- Intent confIntent = new Intent(FullBackup.FULL_BACKUP_INTENT_ACTION);
- confIntent.setClassName("com.android.backupconfirm",
- "com.android.backupconfirm.BackupRestoreConfirmation");
- confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token);
- confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(confIntent);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "Unable to launch full backup confirmation", e);
+ // start up the confirmation UI
+ if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
+ if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
+ Slog.e(TAG, "Unable to launch full backup confirmation");
mFullConfirmations.delete(token);
return;
}
+
+ // make sure the screen is lit for the user interaction
mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
// start the confirmation countdown
- if (DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
- + TIMEOUT_FULL_CONFIRMATION + " millis");
- Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
- token, 0, params);
- mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
+ startConfirmationTimeout(token, params);
// wait for the backup to be performed
if (DEBUG) Slog.d(TAG, "Waiting for full backup completion...");
waitForCompletion(params);
- if (DEBUG) Slog.d(TAG, "...Full backup operation complete!");
} finally {
- Binder.restoreCallingIdentity(oldId);
try {
fd.close();
} catch (IOException e) {
// just eat it
}
+ Binder.restoreCallingIdentity(oldId);
}
+ if (DEBUG) Slog.d(TAG, "Full backup done; returning to caller");
+ }
+
+ public void fullRestore(ParcelFileDescriptor fd) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
+ Slog.i(TAG, "Beginning full restore...");
+
+ long oldId = Binder.clearCallingIdentity();
+
+ try {
+ FullRestoreParams params = new FullRestoreParams(fd);
+ final int token = generateToken();
+ synchronized (mFullConfirmations) {
+ mFullConfirmations.put(token, params);
+ }
+
+ // start up the confirmation UI
+ if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
+ if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
+ Slog.e(TAG, "Unable to launch full restore confirmation");
+ mFullConfirmations.delete(token);
+ return;
+ }
+
+ // make sure the screen is lit for the user interaction
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+
+ // start the confirmation countdown
+ startConfirmationTimeout(token, params);
+
+ // wait for the restore to be performed
+ if (DEBUG) Slog.d(TAG, "Waiting for full restore completion...");
+ waitForCompletion(params);
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Error trying to close fd after full restore: " + e);
+ }
+ Binder.restoreCallingIdentity(oldId);
+ Slog.i(TAG, "Full restore completed");
+ }
+ }
+
+ boolean startConfirmationUi(int token, String action) {
+ try {
+ Intent confIntent = new Intent(action);
+ confIntent.setClassName("com.android.backupconfirm",
+ "com.android.backupconfirm.BackupRestoreConfirmation");
+ confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token);
+ confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(confIntent);
+ } catch (ActivityNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ void startConfirmationTimeout(int token, FullParams params) {
+ if (DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
+ + TIMEOUT_FULL_CONFIRMATION + " millis");
+ Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
+ token, 0, params);
+ mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
}
void waitForCompletion(FullParams params) {
@@ -2661,9 +3626,10 @@
if (allow) {
params.observer = observer;
final int verb = params instanceof FullBackupParams
- ? MSG_RUN_FULL_BACKUP
+ ? MSG_RUN_FULL_BACKUP
: MSG_RUN_FULL_RESTORE;
+ if (DEBUG) Slog.d(TAG, "Sending conf message with verb " + verb);
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(verb, params);
mBackupHandler.sendMessage(msg);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index a564c2d..dd76eb8 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,6 +16,11 @@
package com.android.server;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,11 +31,13 @@
import android.net.DummyDataStateTracker;
import android.net.EthernetDataTracker;
import android.net.IConnectivityManager;
-import android.net.LinkAddress;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
import android.net.LinkProperties;
import android.net.MobileDataStateTracker;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
import android.net.NetworkStateTracker;
import android.net.NetworkUtils;
import android.net.Proxy;
@@ -54,6 +61,7 @@
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
+import android.util.SparseIntArray;
import com.android.internal.telephony.Phone;
import com.android.server.connectivity.Tethering;
@@ -62,13 +70,12 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
-import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.GregorianCalendar;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @hide
@@ -78,6 +85,8 @@
private static final boolean DBG = true;
private static final String TAG = "ConnectivityService";
+ private static final boolean LOGD_RULES = false;
+
// how long to wait before switching back to a radio's default network
private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
// system property that can override the above value
@@ -91,6 +100,9 @@
private Tethering mTethering;
private boolean mTetheringConfigValid = false;
+ /** Currently active network rules by UID. */
+ private SparseIntArray mUidRules = new SparseIntArray();
+
/**
* Sometimes we want to refer to the individual network state
* trackers separately, and sometimes we just want to treat them
@@ -128,6 +140,7 @@
private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true);
private INetworkManagementService mNetd;
+ private INetworkPolicyManager mPolicyManager;
private static final int ENABLED = 1;
private static final int DISABLED = 0;
@@ -250,14 +263,8 @@
}
RadioAttributes[] mRadioAttributes;
- public static synchronized ConnectivityService getInstance(Context context) {
- if (sServiceInstance == null) {
- sServiceInstance = new ConnectivityService(context);
- }
- return sServiceInstance;
- }
-
- private ConnectivityService(Context context) {
+ public ConnectivityService(
+ Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) {
if (DBG) log("ConnectivityService starting up");
HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
@@ -290,9 +297,19 @@
loge("Error setting defaultDns using " + dns);
}
- mContext = context;
+ mContext = checkNotNull(context, "missing Context");
+ mNetd = checkNotNull(netd, "missing INetworkManagementService");
+ mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
- PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ try {
+ mPolicyManager.registerListener(mPolicyListener);
+ } catch (RemoteException e) {
+ // ouch, no rules updates means some processes may never get network
+ Slog.e(TAG, "unable to register INetworkPolicyListener", e);
+ }
+
+ final PowerManager powerManager = (PowerManager) context.getSystemService(
+ Context.POWER_SERVICE);
mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_networkTransitionTimeout);
@@ -536,32 +553,92 @@
}
/**
+ * Check if UID is blocked from using the given {@link NetworkInfo}.
+ */
+ private boolean isNetworkBlocked(NetworkInfo info, int uid) {
+ synchronized (mUidRules) {
+ return isNetworkBlockedLocked(info, uid);
+ }
+ }
+
+ /**
+ * Check if UID is blocked from using the given {@link NetworkInfo}.
+ */
+ private boolean isNetworkBlockedLocked(NetworkInfo info, int uid) {
+ // TODO: expand definition of "paid" network to cover tethered or paid
+ // hotspot use cases.
+ final boolean networkIsPaid = info.getType() != ConnectivityManager.TYPE_WIFI;
+ final int uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+
+ if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) {
+ return true;
+ }
+
+ // no restrictive rules; network is visible
+ return false;
+ }
+
+ /**
* Return NetworkInfo for the active (i.e., connected) network interface.
* It is assumed that at most one network is active at a time. If more
* than one is active, it is indeterminate which will be returned.
* @return the info for the active network, or {@code null} if none is
* active
*/
+ @Override
public NetworkInfo getActiveNetworkInfo() {
- return getNetworkInfo(mActiveDefaultNetwork);
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
}
+ @Override
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ enforceConnectivityInternalPermission();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+
+ @Override
public NetworkInfo getNetworkInfo(int networkType) {
enforceAccessPermission();
- if (ConnectivityManager.isNetworkTypeValid(networkType)) {
- NetworkStateTracker t = mNetTrackers[networkType];
- if (t != null)
- return t.getNetworkInfo();
- }
- return null;
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(networkType, uid);
}
+ private NetworkInfo getNetworkInfo(int networkType, int uid) {
+ NetworkInfo info = null;
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ info = tracker.getNetworkInfo();
+ if (isNetworkBlocked(info, uid)) {
+ // network is blocked; clone and override state
+ info = new NetworkInfo(info);
+ info.setDetailedState(DetailedState.BLOCKED, null, null);
+ }
+ }
+ }
+ return info;
+ }
+
+ @Override
public NetworkInfo[] getAllNetworkInfo() {
enforceAccessPermission();
- NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
+ final int uid = Binder.getCallingUid();
+ final NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
int i = 0;
- for (NetworkStateTracker t : mNetTrackers) {
- if(t != null) result[i++] = t.getNetworkInfo();
+ synchronized (mUidRules) {
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ NetworkInfo info = tracker.getNetworkInfo();
+ if (isNetworkBlockedLocked(info, uid)) {
+ // network is blocked; clone and override state
+ info = new NetworkInfo(info);
+ info.setDetailedState(DetailedState.BLOCKED, null, null);
+ }
+ result[i++] = info;
+ }
+ }
}
return result;
}
@@ -574,15 +651,19 @@
* @return the ip properties for the active network, or {@code null} if
* none is active
*/
+ @Override
public LinkProperties getActiveLinkProperties() {
return getLinkProperties(mActiveDefaultNetwork);
}
+ @Override
public LinkProperties getLinkProperties(int networkType) {
enforceAccessPermission();
- if (ConnectivityManager.isNetworkTypeValid(networkType)) {
- NetworkStateTracker t = mNetTrackers[networkType];
- if (t != null) return t.getLinkProperties();
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ return tracker.getLinkProperties();
+ }
}
return null;
}
@@ -1027,6 +1108,30 @@
}
}
+ private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ @Override
+ public void onRulesChanged(int uid, int uidRules) {
+ // only someone like NPMS should only be calling us
+ // TODO: create permission for modifying data policy
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+
+ if (LOGD_RULES) {
+ Slog.d(TAG, "onRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+ }
+
+ synchronized (mUidRules) {
+ // skip update when we've already applied rules
+ final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ if (oldRules == uidRules) return;
+
+ mUidRules.put(uid, uidRules);
+ }
+
+ // TODO: dispatch into NMS to push rules towards kernel module
+ // TODO: notify UID when it has requested targeted updates
+ }
+ };
+
/**
* @see ConnectivityManager#setMobileDataEnabled(boolean)
*/
@@ -1284,9 +1389,6 @@
}
void systemReady() {
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- mNetd = INetworkManagementService.Stub.asInterface(b);
-
synchronized(this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
@@ -2255,4 +2357,11 @@
}
return networkType;
}
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
}
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 92d76be..78636f3 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -1925,9 +1925,8 @@
// Check for permissions if a particular caller is specified
if (who != null) {
// When checking for a single caller, status is based on caller's request
- ActiveAdmin ap = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_ENCRYPTED_STORAGE);
- return ap.encryptionRequested;
+ ActiveAdmin ap = getActiveAdminUncheckedLocked(who);
+ return ap != null ? ap.encryptionRequested : false;
}
// If no particular caller is specified, return the aggregate set of requests.
diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java
index 54555bb..99c8af6 100644
--- a/services/java/com/android/server/SystemBackupAgent.java
+++ b/services/java/com/android/server/SystemBackupAgent.java
@@ -37,16 +37,25 @@
public class SystemBackupAgent extends BackupAgentHelper {
private static final String TAG = "SystemBackupAgent";
- // These paths must match what the WallpaperManagerService uses
+ // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
+ // are also used in the full-backup file format, so must not change unless steps are
+ // taken to support the legacy backed-up datasets.
+ private static final String WALLPAPER_IMAGE_FILENAME = "wallpaper";
+ private static final String WALLPAPER_INFO_FILENAME = "wallpaper_info.xml";
+
private static final String WALLPAPER_IMAGE_DIR = "/data/data/com.android.settings/files";
- private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/wallpaper";
+ private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/" + WALLPAPER_IMAGE_FILENAME;
+
private static final String WALLPAPER_INFO_DIR = "/data/system";
- private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/wallpaper_info.xml";
+ private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/" + WALLPAPER_INFO_FILENAME;
+
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
if (oldState == null) {
+ // Ah, it's a full backup dataset, being restored piecemeal. Just
+ // pop over to the full restore handling and we're done.
runFullBackup(data);
return;
}
@@ -66,11 +75,18 @@
}
private void runFullBackup(BackupDataOutput output) {
- // Back up the data files directly
- FullBackup.backupToTar(getPackageName(), null, null,
- WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output);
- FullBackup.backupToTar(getPackageName(), null, null,
+ fullWallpaperBackup(output);
+ }
+
+ private void fullWallpaperBackup(BackupDataOutput output) {
+ // Back up the data files directly. We do them in this specific order --
+ // info file followed by image -- because then we need take no special
+ // steps during restore; the restore will happen properly when the individual
+ // files are restored piecemeal.
+ FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null,
WALLPAPER_INFO_DIR, WALLPAPER_INFO, output);
+ FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null,
+ WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output);
}
@Override
@@ -96,4 +112,46 @@
(new File(WALLPAPER_INFO)).delete();
}
}
+
+ @Override
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime)
+ throws IOException {
+ Slog.i(TAG, "Restoring file domain=" + domain + " path=" + path);
+
+ // Bits to indicate postprocessing we may need to perform
+ boolean restoredWallpaper = false;
+
+ File outFile = null;
+ // Various domain+files we understand a priori
+ if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
+ if (path.equals(WALLPAPER_INFO_FILENAME)) {
+ outFile = new File(WALLPAPER_INFO);
+ restoredWallpaper = true;
+ } else if (path.equals(WALLPAPER_IMAGE_FILENAME)) {
+ outFile = new File(WALLPAPER_IMAGE);
+ restoredWallpaper = true;
+ }
+ }
+
+ try {
+ if (outFile == null) {
+ Slog.w(TAG, "Skipping unrecognized system file: [ " + domain + " : " + path + " ]");
+ }
+ FullBackup.restoreToFile(data, size, type, mode, mtime, outFile);
+
+ if (restoredWallpaper) {
+ WallpaperManagerService wallpaper =
+ (WallpaperManagerService)ServiceManager.getService(
+ Context.WALLPAPER_SERVICE);
+ wallpaper.settingsRestored();
+ }
+ } catch (IOException e) {
+ if (restoredWallpaper) {
+ // Make sure we wind up in a good state
+ (new File(WALLPAPER_IMAGE)).delete();
+ (new File(WALLPAPER_INFO)).delete();
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5355d44..4cd601f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,19 +16,6 @@
package com.android.server;
-import com.android.server.accessibility.AccessibilityManagerService;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.net.NetworkPolicyManagerService;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.usb.UsbService;
-import com.android.server.wm.WindowManagerService;
-import com.android.internal.app.ShutdownThread;
-import com.android.internal.os.BinderInternal;
-import com.android.internal.os.SamplingProfilerIntegration;
-
-import dalvik.system.VMRuntime;
-import dalvik.system.Zygote;
-
import android.accounts.AccountManagerService;
import android.app.ActivityManagerNative;
import android.bluetooth.BluetoothAdapter;
@@ -41,25 +28,34 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.media.AudioService;
-import android.os.Build;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.provider.Contacts.People;
import android.provider.Settings;
import android.server.BluetoothA2dpService;
import android.server.BluetoothService;
import android.server.search.SearchManagerService;
import android.util.DisplayMetrics;
import android.util.EventLog;
-import android.util.Log;
import android.util.Slog;
-import android.view.Display;
import android.view.WindowManager;
+import com.android.internal.app.ShutdownThread;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.SamplingProfilerIntegration;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.net.NetworkPolicyManagerService;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.usb.UsbService;
+import com.android.server.wm.WindowManagerService;
+
+import dalvik.system.VMRuntime;
+import dalvik.system.Zygote;
+
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
@@ -120,6 +116,7 @@
LightsService lights = null;
PowerManagerService power = null;
BatteryService battery = null;
+ NetworkManagementService networkManagement = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
IPackageManager pm = null;
@@ -294,16 +291,15 @@
try {
Slog.i(TAG, "NetworkManagement Service");
- ServiceManager.addService(
- Context.NETWORKMANAGEMENT_SERVICE,
- NetworkManagementService.create(context));
+ networkManagement = NetworkManagementService.create(context);
+ ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting NetworkManagement Service", e);
}
try {
Slog.i(TAG, "Connectivity Service");
- connectivity = ConnectivityService.getInstance(context);
+ connectivity = new ConnectivityService(context, networkManagement, networkPolicy);
ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Connectivity Service", e);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index cf5592c..b463e56 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -968,10 +968,12 @@
static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27;
static final int CLEAR_DNS_CACHE = 28;
static final int UPDATE_HTTP_PROXY = 29;
- static final int DISPATCH_FOREGROUND_ACTIVITIES_CHANGED = 30;
- static final int DISPATCH_PROCESS_DIED = 31;
+ static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30;
+ static final int DISPATCH_FOREGROUND_ACTIVITIES_CHANGED = 31;
+ static final int DISPATCH_PROCESS_DIED = 32;
AlertDialog mUidAlert;
+ CompatModeDialog mCompatModeDialog;
final Handler mHandler = new Handler() {
//public Handler() {
@@ -1270,6 +1272,34 @@
sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
}
} break;
+ case SHOW_COMPAT_MODE_DIALOG_MSG: {
+ synchronized (ActivityManagerService.this) {
+ ActivityRecord ar = (ActivityRecord)msg.obj;
+ if (mCompatModeDialog != null) {
+ if (mCompatModeDialog.mAppInfo.packageName.equals(
+ ar.info.applicationInfo.packageName)) {
+ return;
+ }
+ mCompatModeDialog.dismiss();
+ mCompatModeDialog = null;
+ }
+ if (ar != null) {
+ if (mCompatModePackages.getPackageAskCompatModeLocked(
+ ar.packageName)) {
+ int mode = mCompatModePackages.computeCompatModeLocked(
+ ar.info.applicationInfo);
+ if (mode == ActivityManager.COMPAT_MODE_DISABLED
+ || mode == ActivityManager.COMPAT_MODE_ENABLED) {
+ mCompatModeDialog = new CompatModeDialog(
+ ActivityManagerService.this, mContext,
+ ar.info.applicationInfo);
+ mCompatModeDialog.show();
+ }
+ }
+ }
+ }
+ break;
+ }
case DISPATCH_FOREGROUND_ACTIVITIES_CHANGED: {
final ProcessRecord app = (ProcessRecord) msg.obj;
final boolean foregroundActivities = msg.arg1 != 0;
@@ -2112,6 +2142,18 @@
}
}
+ public boolean getPackageAskScreenCompat(String packageName) {
+ synchronized (this) {
+ return mCompatModePackages.getPackageAskCompatModeLocked(packageName);
+ }
+ }
+
+ public void setPackageAskScreenCompat(String packageName, boolean ask) {
+ synchronized (this) {
+ mCompatModePackages.setPackageAskCompatModeLocked(packageName, ask);
+ }
+ }
+
void reportResumedActivityLocked(ActivityRecord r) {
//Slog.i(TAG, "**** REPORT RESUME: " + r);
@@ -3634,6 +3676,7 @@
boolean isRestrictedBackupMode = false;
if (mBackupTarget != null && mBackupAppName.equals(processName)) {
isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE)
+ || (mBackupTarget.backupMode == BackupRecord.RESTORE_FULL)
|| (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL);
}
@@ -7948,8 +7991,14 @@
if (dumpAll) {
pw.println(" mConfigWillChange: " + mMainStack.mConfigWillChange);
if (mCompatModePackages.getPackages().size() > 0) {
- pw.print(" mScreenCompatPackages=");
- pw.println(mCompatModePackages.getPackages());
+ pw.println(" mScreenCompatPackages:");
+ for (Map.Entry<String, Integer> entry
+ : mCompatModePackages.getPackages().entrySet()) {
+ String pkg = entry.getKey();
+ int mode = entry.getValue();
+ pw.print(" "); pw.print(pkg); pw.print(": ");
+ pw.print(mode); pw.println();
+ }
}
}
pw.println(" mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown);
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index d5ac19e..af69307 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -485,6 +485,13 @@
return null;
}
+ final void showAskCompatModeDialogLocked(ActivityRecord r) {
+ Message msg = Message.obtain();
+ msg.what = ActivityManagerService.SHOW_COMPAT_MODE_DIALOG_MSG;
+ msg.obj = r.task.askedCompatMode ? null : r;
+ mService.mHandler.sendMessage(msg);
+ }
+
final boolean realStartActivityLocked(ActivityRecord r,
ProcessRecord app, boolean andResume, boolean checkConfig)
throws RemoteException {
@@ -541,6 +548,7 @@
mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
r.sleeping = false;
r.forceNewConfig = false;
+ showAskCompatModeDialogLocked(r);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r,
System.identityHashCode(r),
r.info, mService.compatibilityInfoForPackageLocked(r.info.applicationInfo),
@@ -1430,6 +1438,7 @@
next.task.taskId, next.shortComponentName);
next.sleeping = false;
+ showAskCompatModeDialogLocked(next);
next.app.thread.scheduleResumeActivity(next,
mService.isNextTransitionForward());
diff --git a/services/java/com/android/server/am/BackupRecord.java b/services/java/com/android/server/am/BackupRecord.java
index 6590b91..7e73106 100644
--- a/services/java/com/android/server/am/BackupRecord.java
+++ b/services/java/com/android/server/am/BackupRecord.java
@@ -26,6 +26,7 @@
public static final int BACKUP_NORMAL = 0;
public static final int BACKUP_FULL = 1;
public static final int RESTORE = 2;
+ public static final int RESTORE_FULL = 3;
final BatteryStatsImpl.Uid.Pkg.Serv stats;
String stringName; // cached toString() output
diff --git a/services/java/com/android/server/am/CompatModeDialog.java b/services/java/com/android/server/am/CompatModeDialog.java
new file mode 100644
index 0000000..0442bda
--- /dev/null
+++ b/services/java/com/android/server/am/CompatModeDialog.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+public class CompatModeDialog extends Dialog {
+ final ActivityManagerService mService;
+ final ApplicationInfo mAppInfo;
+
+ final Switch mCompatEnabled;
+ final CheckBox mAlwaysShow;
+ final View mHint;
+
+ public CompatModeDialog(ActivityManagerService service, Context context,
+ ApplicationInfo appInfo) {
+ super(context, com.android.internal.R.style.Theme_Holo_Dialog_MinWidth);
+ setCancelable(true);
+ setCanceledOnTouchOutside(true);
+ getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_PHONE);
+ getWindow().setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL);
+ mService = service;
+ mAppInfo = appInfo;
+
+ setContentView(com.android.internal.R.layout.am_compat_mode_dialog);
+ mCompatEnabled = (Switch)findViewById(com.android.internal.R.id.compat_checkbox);
+ mCompatEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ synchronized (mService) {
+ mService.mCompatModePackages.setPackageScreenCompatModeLocked(
+ mAppInfo.packageName,
+ mCompatEnabled.isChecked() ? ActivityManager.COMPAT_MODE_ENABLED
+ : ActivityManager.COMPAT_MODE_DISABLED);
+ updateControls();
+ }
+ }
+ });
+ mAlwaysShow = (CheckBox)findViewById(com.android.internal.R.id.ask_checkbox);
+ mAlwaysShow.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ synchronized (mService) {
+ mService.mCompatModePackages.setPackageAskCompatModeLocked(
+ mAppInfo.packageName, mAlwaysShow.isChecked());
+ updateControls();
+ }
+ }
+ });
+ mHint = findViewById(com.android.internal.R.id.reask_hint);
+
+ updateControls();
+ }
+
+ void updateControls() {
+ synchronized (mService) {
+ int mode = mService.mCompatModePackages.computeCompatModeLocked(mAppInfo);
+ mCompatEnabled.setChecked(mode == ActivityManager.COMPAT_MODE_ENABLED);
+ boolean ask = mService.mCompatModePackages.getPackageAskCompatModeLocked(
+ mAppInfo.packageName);
+ mAlwaysShow.setChecked(ask);
+ mHint.setVisibility(ask ? View.INVISIBLE : View.VISIBLE);
+ }
+ }
+}
diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java
index 1faf8da..1277bca 100644
--- a/services/java/com/android/server/am/CompatModePackages.java
+++ b/services/java/com/android/server/am/CompatModePackages.java
@@ -3,8 +3,10 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Map;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -31,7 +33,12 @@
private final ActivityManagerService mService;
private final AtomicFile mFile;
- private final HashSet<String> mPackages = new HashSet<String>();
+ // Compatibility state: no longer ask user to select the mode.
+ public static final int COMPAT_FLAG_DONT_ASK = 1<<0;
+ // Compatibility state: compatibility mode is enabled.
+ public static final int COMPAT_FLAG_ENABLED = 1<<1;
+
+ private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
private static final int MSG_WRITE = 1;
@@ -71,7 +78,15 @@
if ("pkg".equals(tagName)) {
String pkg = parser.getAttributeValue(null, "name");
if (pkg != null) {
- mPackages.add(pkg);
+ String mode = parser.getAttributeValue(null, "mode");
+ int modeInt = 0;
+ if (mode != null) {
+ try {
+ modeInt = Integer.parseInt(mode);
+ } catch (NumberFormatException e) {
+ }
+ }
+ mPackages.put(pkg, modeInt);
}
}
}
@@ -93,17 +108,22 @@
}
}
- public HashSet<String> getPackages() {
+ public HashMap<String, Integer> getPackages() {
return mPackages;
}
+ private int getPackageFlags(String packageName) {
+ Integer flags = mPackages.get(packageName);
+ return flags != null ? flags : 0;
+ }
+
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
return new CompatibilityInfo(ai, mService.mConfiguration.screenLayout,
- mPackages.contains(ai.packageName));
+ (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
}
- private int computeCompatModeLocked(ApplicationInfo ai) {
- boolean enabled = mPackages.contains(ai.packageName);
+ public int computeCompatModeLocked(ApplicationInfo ai) {
+ boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
CompatibilityInfo info = new CompatibilityInfo(ai,
mService.mConfiguration.screenLayout, enabled);
if (info.alwaysSupportsScreen()) {
@@ -116,6 +136,40 @@
: ActivityManager.COMPAT_MODE_DISABLED;
}
+ public boolean getFrontActivityAskCompatModeLocked() {
+ ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null);
+ if (r == null) {
+ return false;
+ }
+ return getPackageAskCompatModeLocked(r.packageName);
+ }
+
+ public boolean getPackageAskCompatModeLocked(String packageName) {
+ return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
+ }
+
+ public void setFrontActivityAskCompatModeLocked(boolean ask) {
+ ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null);
+ if (r != null) {
+ setPackageAskCompatModeLocked(r.packageName, ask);
+ }
+ }
+
+ public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
+ int curFlags = getPackageFlags(packageName);
+ int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK);
+ if (curFlags != newFlags) {
+ if (newFlags != 0) {
+ mPackages.put(packageName, newFlags);
+ } else {
+ mPackages.remove(packageName);
+ }
+ mHandler.removeMessages(MSG_WRITE);
+ Message msg = mHandler.obtainMessage(MSG_WRITE);
+ mHandler.sendMessageDelayed(msg, 10000);
+ }
+ }
+
public int getFrontActivityScreenCompatModeLocked() {
ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null);
if (r == null) {
@@ -161,7 +215,8 @@
private void setPackageScreenCompatModeLocked(ApplicationInfo ai, int mode) {
final String packageName = ai.packageName;
- boolean changed = false;
+ int curFlags = getPackageFlags(packageName);
+
boolean enable;
switch (mode) {
case ActivityManager.COMPAT_MODE_DISABLED:
@@ -171,24 +226,26 @@
enable = true;
break;
case ActivityManager.COMPAT_MODE_TOGGLE:
- enable = !mPackages.contains(packageName);
+ enable = (curFlags&COMPAT_FLAG_ENABLED) == 0;
break;
default:
Slog.w(TAG, "Unknown screen compat mode req #" + mode + "; ignoring");
return;
}
+
+ int newFlags = curFlags;
if (enable) {
- if (!mPackages.contains(packageName)) {
- changed = true;
- mPackages.add(packageName);
- }
+ newFlags |= COMPAT_FLAG_ENABLED;
} else {
- if (mPackages.contains(packageName)) {
- changed = true;
+ newFlags &= ~COMPAT_FLAG_ENABLED;
+ }
+
+ if (newFlags != curFlags) {
+ if (newFlags != 0) {
+ mPackages.put(packageName, newFlags);
+ } else {
mPackages.remove(packageName);
}
- }
- if (changed) {
CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
if (ci.alwaysSupportsScreen()) {
Slog.w(TAG, "Ignoring compat mode change of " + packageName
@@ -241,9 +298,9 @@
}
void saveCompatModes() {
- HashSet<String> pkgs;
+ HashMap<String, Integer> pkgs;
synchronized (mService) {
- pkgs = new HashSet<String>(mPackages);
+ pkgs = new HashMap<String, Integer>(mPackages);
}
FileOutputStream fos = null;
@@ -258,9 +315,14 @@
final IPackageManager pm = AppGlobals.getPackageManager();
final int screenLayout = mService.mConfiguration.screenLayout;
- final Iterator<String> it = pkgs.iterator();
+ final Iterator<Map.Entry<String, Integer>> it = pkgs.entrySet().iterator();
while (it.hasNext()) {
- String pkg = it.next();
+ Map.Entry<String, Integer> entry = it.next();
+ String pkg = entry.getKey();
+ int mode = entry.getValue();
+ if (mode == 0) {
+ continue;
+ }
ApplicationInfo ai = null;
try {
ai = pm.getApplicationInfo(pkg, 0);
@@ -278,6 +340,7 @@
}
out.startTag(null, "pkg");
out.attribute(null, "name", pkg);
+ out.attribute(null, "mode", Integer.toString(mode));
out.endTag(null, "pkg");
}
diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java
index e8c87e1..e61a7f4 100644
--- a/services/java/com/android/server/am/TaskRecord.java
+++ b/services/java/com/android/server/am/TaskRecord.java
@@ -34,6 +34,7 @@
long lastActiveTime; // Last time this task was active, including sleep.
boolean rootWasReset; // True if the intent at the root of the task had
// the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
+ boolean askedCompatMode;// Have asked the user about compat mode for this task.
String stringName; // caching of toString() result.
@@ -112,6 +113,9 @@
pw.print(prefix); pw.print("realActivity=");
pw.println(realActivity.flattenToShortString());
}
+ if (!askedCompatMode) {
+ pw.print(prefix); pw.print("askedCompatMode="); pw.println(askedCompatMode);
+ }
pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
pw.print(" (inactive for ");
pw.print((getInactiveDuration()/1000)); pw.println("s)");
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index d083d01..1ae8284 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -16,11 +16,15 @@
package com.android.server.net;
+import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_BACKGROUND;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static android.net.NetworkPolicyManager.dumpPolicy;
+import static android.net.NetworkPolicyManager.dumpRules;
import android.app.IActivityManager;
import android.app.IProcessObserver;
@@ -28,8 +32,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.os.IPowerManager;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
@@ -37,9 +44,16 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* Service that maintains low-level network policy rules and collects usage
* statistics to drive those rules.
+ * <p>
+ * Derives active rules by combining a given policy with other system status,
+ * and delivers to listeners, such as {@link ConnectivityManager}, for
+ * enforcement.
*/
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String TAG = "NetworkPolicy";
@@ -51,19 +65,27 @@
private Object mRulesLock = new Object();
- private boolean mScreenOn = false;
+ private boolean mScreenOn;
/** Current network policy for each UID. */
private SparseIntArray mUidPolicy = new SparseIntArray();
+ /** Current derived network rules for each UID. */
+ private SparseIntArray mUidRules = new SparseIntArray();
/** Foreground at both UID and PID granularity. */
private SparseBooleanArray mUidForeground = new SparseBooleanArray();
private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
SparseBooleanArray>();
+ private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
+ INetworkPolicyListener>();
+
// TODO: periodically poll network stats and write to disk
// TODO: save/restore policy information from disk
+ // TODO: keep whitelist of system-critical services that should never have
+ // rules enforced, such as system, phone, and radio UIDs.
+
public NetworkPolicyManagerService(
Context context, IActivityManager activityManager, IPowerManager powerManager) {
mContext = checkNotNull(context, "missing context");
@@ -158,12 +180,18 @@
@Override
public void setUidPolicy(int uid, int policy) {
+ // TODO: create permission for modifying data policy
mContext.enforceCallingOrSelfPermission(
UPDATE_DEVICE_STATS, "requires UPDATE_DEVICE_STATS permission");
+ final int oldPolicy;
synchronized (mRulesLock) {
+ oldPolicy = getUidPolicy(uid);
mUidPolicy.put(uid, policy);
+ updateRulesForUidL(uid);
}
+
+ // TODO: consider dispatching BACKGROUND_DATA_SETTING broadcast
}
@Override
@@ -173,6 +201,86 @@
}
}
+ @Override
+ public void registerListener(INetworkPolicyListener listener) {
+ mListeners.register(listener);
+
+ synchronized (mRulesLock) {
+ // dispatch any existing rules to new listeners
+ final int size = mUidRules.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = mUidRules.keyAt(i);
+ final int uidRules = mUidRules.valueAt(i);
+ if (uidRules != RULE_ALLOW_ALL) {
+ try {
+ listener.onRulesChanged(uid, uidRules);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void unregisterListener(INetworkPolicyListener listener) {
+ mListeners.unregister(listener);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, "requires DUMP permission");
+
+ synchronized (mRulesLock) {
+ fout.println("Policy status for known UIDs:");
+
+ final SparseBooleanArray knownUids = new SparseBooleanArray();
+ collectKeys(mUidPolicy, knownUids);
+ collectKeys(mUidForeground, knownUids);
+ collectKeys(mUidRules, knownUids);
+
+ final int size = knownUids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = knownUids.keyAt(i);
+ fout.print(" UID=");
+ fout.print(uid);
+
+ fout.print(" policy=");
+ final int policyIndex = mUidPolicy.indexOfKey(uid);
+ if (policyIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpPolicy(fout, mUidPolicy.valueAt(policyIndex));
+ }
+
+ fout.print(" foreground=");
+ final int foregroundIndex = mUidPidForeground.indexOfKey(uid);
+ if (foregroundIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpSparseBooleanArray(fout, mUidPidForeground.valueAt(foregroundIndex));
+ }
+
+ fout.print(" rules=");
+ final int rulesIndex = mUidRules.indexOfKey(uid);
+ if (rulesIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpRules(fout, mUidRules.valueAt(rulesIndex));
+ }
+
+ fout.println();
+ }
+ }
+ }
+
+ @Override
+ public boolean isUidForeground(int uid) {
+ synchronized (mRulesLock) {
+ // only really in foreground when screen is also on
+ return mUidForeground.get(uid, false) && mScreenOn;
+ }
+ }
+
/**
* Foreground for PID changed; recompute foreground at UID level. If
* changed, will trigger {@link #updateRulesForUidL(int)}.
@@ -223,22 +331,33 @@
}
private void updateRulesForUidL(int uid) {
- // only really in foreground when screen on
- final boolean uidForeground = mUidForeground.get(uid, false) && mScreenOn;
final int uidPolicy = getUidPolicy(uid);
+ final boolean uidForeground = isUidForeground(uid);
- if (LOGD) {
- Log.d(TAG, "updateRulesForUid(uid=" + uid + ") found foreground=" + uidForeground
- + " and policy=" + uidPolicy);
+ // derive active rules based on policy and active state
+ int uidRules = RULE_ALLOW_ALL;
+ if (!uidForeground && (uidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+ // uid in background, and policy says to block paid data
+ uidRules = RULE_REJECT_PAID;
}
- if (!uidForeground && (uidPolicy & POLICY_REJECT_BACKGROUND) != 0) {
- // TODO: build updated rules and push to NMS
- } else if ((uidPolicy & POLICY_REJECT_PAID) != 0) {
- // TODO: build updated rules and push to NMS
- } else {
- // TODO: build updated rules and push to NMS
+ // TODO: only dispatch when rules actually change
+
+ // record rule locally to dispatch to new listeners
+ mUidRules.put(uid, uidRules);
+
+ // dispatch changed rule to existing listeners
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ if (listener != null) {
+ try {
+ listener.onRulesChanged(uid, uidRules);
+ } catch (RemoteException e) {
+ }
+ }
}
+ mListeners.finishBroadcast();
}
private static <T> T checkNotNull(T value, String message) {
@@ -247,4 +366,28 @@
}
return value;
}
+
+ private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
+ final int size = source.size();
+ for (int i = 0; i < size; i++) {
+ target.put(source.keyAt(i), true);
+ }
+ }
+
+ private static void collectKeys(SparseBooleanArray source, SparseBooleanArray target) {
+ final int size = source.size();
+ for (int i = 0; i < size; i++) {
+ target.put(source.keyAt(i), true);
+ }
+ }
+
+ private static void dumpSparseBooleanArray(PrintWriter fout, SparseBooleanArray value) {
+ fout.print("[");
+ final int size = value.size();
+ for (int i = 0; i < size; i++) {
+ fout.print(value.keyAt(i) + "=" + value.valueAt(i));
+ if (i < size - 1) fout.print(",");
+ }
+ fout.print("]");
+ }
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index f8d1426..295d569 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -24,7 +24,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+
<application>
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
new file mode 100644
index 0000000..fe88793
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AbstractFuture;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Future;
+
+/**
+ * {@link ContextWrapper} that can attach listeners for upcoming
+ * {@link Context#sendBroadcast(Intent)}.
+ */
+public class BroadcastInterceptingContext extends ContextWrapper {
+ private static final String TAG = "WatchingContext";
+
+ private final List<BroadcastInterceptor> mInterceptors = Lists.newArrayList();
+
+ public class BroadcastInterceptor extends AbstractFuture<Intent> {
+ private final BroadcastReceiver mReceiver;
+ private final IntentFilter mFilter;
+
+ public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) {
+ mReceiver = receiver;
+ mFilter = filter;
+ }
+
+ public boolean dispatchBroadcast(Intent intent) {
+ if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
+ if (mReceiver != null) {
+ final Context context = BroadcastInterceptingContext.this;
+ mReceiver.onReceive(context, intent);
+ return false;
+ } else {
+ set(intent);
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ public BroadcastInterceptingContext(Context base) {
+ super(base);
+ }
+
+ public Future<Intent> nextBroadcastIntent(String action) {
+ return nextBroadcastIntent(new IntentFilter(action));
+ }
+
+ public Future<Intent> nextBroadcastIntent(IntentFilter filter) {
+ final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter);
+ synchronized (mInterceptors) {
+ mInterceptors.add(interceptor);
+ }
+ return interceptor;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ synchronized (mInterceptors) {
+ mInterceptors.add(new BroadcastInterceptor(receiver, filter));
+ }
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ return registerReceiver(receiver, filter);
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ synchronized (mInterceptors) {
+ final Iterator<BroadcastInterceptor> i = mInterceptors.iterator();
+ while (i.hasNext()) {
+ final BroadcastInterceptor interceptor = i.next();
+ if (receiver.equals(interceptor.mReceiver)) {
+ i.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ synchronized (mInterceptors) {
+ final Iterator<BroadcastInterceptor> i = mInterceptors.iterator();
+ while (i.hasNext()) {
+ final BroadcastInterceptor interceptor = i.next();
+ if (interceptor.dispatchBroadcast(intent)) {
+ i.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent intent) {
+ sendBroadcast(intent);
+ }
+
+ @Override
+ public void removeStickyBroadcast(Intent intent) {
+ // ignored
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
new file mode 100644
index 0000000..cf1171f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+
+import android.app.IActivityManager;
+import android.app.IProcessObserver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
+import android.os.Binder;
+import android.os.IPowerManager;
+import android.test.AndroidTestCase;
+import android.test.mock.MockPackageManager;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+import com.android.server.net.NetworkPolicyManagerService;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+
+import java.util.concurrent.Future;
+
+/**
+ * Tests for {@link NetworkPolicyManagerService}.
+ */
+@LargeTest
+public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
+ private static final String TAG = "NetworkPolicyManagerServiceTest";
+
+ private BroadcastInterceptingContext mServiceContext;
+
+ private IActivityManager mActivityManager;
+ private IPowerManager mPowerManager;
+ private INetworkPolicyListener mPolicyListener;
+
+ private NetworkPolicyManagerService mService;
+ private IProcessObserver mProcessObserver;
+
+ private Binder mStubBinder = new Binder();
+
+ private static final int UID_A = 800;
+ private static final int UID_B = 801;
+
+ private static final int PID_1 = 400;
+ private static final int PID_2 = 401;
+ private static final int PID_3 = 402;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // intercept various broadcasts, and pretend that uids have packages
+ mServiceContext = new BroadcastInterceptingContext(getContext()) {
+ @Override
+ public PackageManager getPackageManager() {
+ return new MockPackageManager() {
+ @Override
+ public String[] getPackagesForUid(int uid) {
+ return new String[] { "com.example" };
+ }
+ };
+ }
+ };
+
+ mActivityManager = createMock(IActivityManager.class);
+ mPowerManager = createMock(IPowerManager.class);
+ mPolicyListener = createMock(INetworkPolicyListener.class);
+
+ mService = new NetworkPolicyManagerService(
+ mServiceContext, mActivityManager, mPowerManager);
+
+ // RemoteCallbackList needs a binder to use as key
+ expect(mPolicyListener.asBinder()).andReturn(mStubBinder).atLeastOnce();
+ replay();
+ mService.registerListener(mPolicyListener);
+ verifyAndReset();
+
+ // catch the registered IProcessObserver during systemReady()
+ final Capture<IProcessObserver> processObserver = new Capture<IProcessObserver>();
+ mActivityManager.registerProcessObserver(capture(processObserver));
+ expectLastCall().atLeastOnce();
+
+ // expect to answer screen status during systemReady()
+ expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
+
+ replay();
+ mService.systemReady();
+ verifyAndReset();
+
+ mProcessObserver = processObserver.getValue();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mServiceContext = null;
+
+ mActivityManager = null;
+ mPowerManager = null;
+ mPolicyListener = null;
+
+ mService = null;
+ mProcessObserver = null;
+
+ super.tearDown();
+ }
+
+ @Suppress
+ public void testPolicyChangeTriggersBroadcast() throws Exception {
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+
+ // change background policy and expect broadcast
+ final Future<Intent> backgroundChanged = mServiceContext.nextBroadcastIntent(
+ ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+
+ mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+
+ backgroundChanged.get();
+ }
+
+ public void testPidForegroundCombined() throws Exception {
+ // push all uid into background
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+ mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false);
+ assertFalse(mService.isUidForeground(UID_A));
+ assertFalse(mService.isUidForeground(UID_B));
+
+ // push one of the shared pids into foreground
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+ assertTrue(mService.isUidForeground(UID_A));
+ assertFalse(mService.isUidForeground(UID_B));
+
+ // and swap another uid into foreground
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+ mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true);
+ assertFalse(mService.isUidForeground(UID_A));
+ assertTrue(mService.isUidForeground(UID_B));
+
+ // push both pid into foreground
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+ assertTrue(mService.isUidForeground(UID_A));
+
+ // pull one out, should still be foreground
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ assertTrue(mService.isUidForeground(UID_A));
+
+ // pull final pid out, should now be background
+ mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+ assertFalse(mService.isUidForeground(UID_A));
+ }
+
+ public void testScreenChangesRules() throws Exception {
+ // push strict policy for foreground uid, verify ALLOW rule
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+ verifyAndReset();
+
+ // now turn screen off and verify REJECT rule
+ expect(mPowerManager.isScreenOn()).andReturn(false).atLeastOnce();
+ expectRulesChanged(UID_A, RULE_REJECT_PAID);
+ replay();
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+ verifyAndReset();
+
+ // and turn screen back on, verify ALLOW rule restored
+ expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+ verifyAndReset();
+ }
+
+ public void testPolicyNone() throws Exception {
+ // POLICY_NONE should RULE_ALLOW in foreground
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ verifyAndReset();
+
+ // POLICY_NONE should RULE_ALLOW in background
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ verifyAndReset();
+ }
+
+ public void testPolicyReject() throws Exception {
+ // POLICY_REJECT should RULE_ALLOW in background
+ expectRulesChanged(UID_A, RULE_REJECT_PAID);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+ verifyAndReset();
+
+ // POLICY_REJECT should RULE_ALLOW in foreground
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ verifyAndReset();
+
+ // POLICY_REJECT should RULE_REJECT in background
+ expectRulesChanged(UID_A, RULE_REJECT_PAID);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ verifyAndReset();
+ }
+
+ public void testPolicyRejectAddRemove() throws Exception {
+ // POLICY_NONE should have RULE_ALLOW in background
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ verifyAndReset();
+
+ // adding POLICY_REJECT should cause RULE_REJECT
+ expectRulesChanged(UID_A, RULE_REJECT_PAID);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND);
+ verifyAndReset();
+
+ // removing POLICY_REJECT should return us to RULE_ALLOW
+ expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+ verifyAndReset();
+ }
+
+ private void expectRulesChanged(int uid, int policy) throws Exception {
+ mPolicyListener.onRulesChanged(eq(uid), eq(policy));
+ expectLastCall().atLeastOnce();
+ }
+
+ private void replay() {
+ EasyMock.replay(mActivityManager, mPowerManager, mPolicyListener);
+ }
+
+ private void verifyAndReset() {
+ EasyMock.verify(mActivityManager, mPowerManager, mPolicyListener);
+ EasyMock.reset(mActivityManager, mPowerManager, mPolicyListener);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
index ca33d32..d1ee4f6 100644
--- a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
@@ -25,14 +25,9 @@
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.AbstractFuture;
-
import android.content.ContentResolver;
import android.content.Context;
-import android.content.ContextWrapper;
import android.content.Intent;
-import android.content.IntentFilter;
import android.net.INetworkManagementEventObserver;
import android.net.NetworkStats;
import android.net.ThrottleManager;
@@ -48,8 +43,6 @@
import android.util.Log;
import android.util.TrustedTime;
-import java.util.Iterator;
-import java.util.List;
import java.util.concurrent.Future;
/**
@@ -66,7 +59,7 @@
private static final String TEST_IFACE = "test0";
- private WatchingContext mWatchingContext;
+ private BroadcastInterceptingContext mWatchingContext;
private INetworkManagementService mMockNMService;
private TrustedTime mMockTime;
@@ -76,7 +69,7 @@
public void setUp() throws Exception {
super.setUp();
- mWatchingContext = new WatchingContext(getContext());
+ mWatchingContext = new BroadcastInterceptingContext(getContext());
mMockNMService = createMock(INetworkManagementService.class);
mMockTime = createMock(TrustedTime.class);
@@ -354,69 +347,4 @@
pollAction.get();
}
-
-
- /**
- * {@link ContextWrapper} that can attach listeners for upcoming
- * {@link Context#sendBroadcast(Intent)}.
- */
- private static class WatchingContext extends ContextWrapper {
- private List<LocalBroadcastReceiver> mReceivers = Lists.newArrayList();
-
- public class LocalBroadcastReceiver extends AbstractFuture<Intent> {
- private IntentFilter mFilter;
-
- public LocalBroadcastReceiver(IntentFilter filter) {
- mFilter = filter;
- }
-
- public boolean dispatchBroadcast(Intent intent) {
- if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
- set(intent);
- return true;
- } else {
- return false;
- }
- }
- }
-
- public WatchingContext(Context base) {
- super(base);
- }
-
- public Future<Intent> nextBroadcastIntent(String action) {
- return nextBroadcastIntent(new IntentFilter(action));
- }
-
- public Future<Intent> nextBroadcastIntent(IntentFilter filter) {
- final LocalBroadcastReceiver receiver = new LocalBroadcastReceiver(filter);
- synchronized (mReceivers) {
- mReceivers.add(receiver);
- }
- return receiver;
- }
-
- @Override
- public void sendBroadcast(Intent intent) {
- synchronized (mReceivers) {
- final Iterator<LocalBroadcastReceiver> i = mReceivers.iterator();
- while (i.hasNext()) {
- final LocalBroadcastReceiver receiver = i.next();
- if (receiver.dispatchBroadcast(intent)) {
- i.remove();
- }
- }
- }
- }
-
- @Override
- public void sendStickyBroadcast(Intent intent) {
- sendBroadcast(intent);
- }
-
- @Override
- public void removeStickyBroadcast(Intent intent) {
- // ignored
- }
- }
}
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 3636baa..e8d4f98 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -62,6 +62,7 @@
*/
public abstract class DataConnection extends StateMachine {
protected static final boolean DBG = true;
+ protected static final boolean VDBG = false;
protected static Object mCountLock = new Object();
protected static int mCount;
@@ -290,7 +291,7 @@
lastFailTime = timeStamp;
AsyncResult.forMessage(connectionCompletedMsg, cause, new Exception());
}
- if (DBG) log("notifyConnection at " + timeStamp + " cause=" + cause);
+ if (DBG) log("notifyConnectionCompleted at " + timeStamp + " cause=" + cause);
connectionCompletedMsg.sendToTarget();
}
@@ -301,12 +302,14 @@
* @param dp is the DisconnectParams.
*/
private void notifyDisconnectCompleted(DisconnectParams dp) {
- if (DBG) log("NotifyDisconnectCompleted");
+ if (VDBG) log("NotifyDisconnectCompleted");
if (dp.onCompletedMsg != null) {
Message msg = dp.onCompletedMsg;
- log(String.format("msg=%s msg.obj=%s", msg.toString(),
+ if (VDBG) {
+ log(String.format("msg=%s msg.obj=%s", msg.toString(),
((msg.obj instanceof String) ? (String) msg.obj : "<no-reason>")));
+ }
AsyncResult.forMessage(msg);
msg.sendToTarget();
}
@@ -472,7 +475,7 @@
result = DataCallState.SetupResult.ERR_RilError;
result.mFailCause = FailCause.fromInt(response.status);
} else {
- log("onSetupConnectionCompleted received DataCallState: " + response);
+ if (DBG) log("onSetupConnectionCompleted received DataCallState: " + response);
cid = response.cid;
// set link properties based on data call response
result = setLinkProperties(response, mLinkProperties);
@@ -558,53 +561,53 @@
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
if (mAc != null) {
- log("Disconnecting to previous connection mAc=" + mAc);
+ if (VDBG) log("Disconnecting to previous connection mAc=" + mAc);
mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
} else {
mAc = new AsyncChannel();
mAc.connected(null, getHandler(), msg.replyTo);
- log("DcDefaultState: FULL_CONNECTION reply connected");
+ if (VDBG) log("DcDefaultState: FULL_CONNECTION reply connected");
mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
AsyncChannel.STATUS_SUCCESSFUL, mId, "hi");
}
break;
}
case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
- log("CMD_CHANNEL_DISCONNECT");
+ if (VDBG) log("CMD_CHANNEL_DISCONNECT");
mAc.disconnect();
break;
}
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- log("CMD_CHANNEL_DISCONNECTED");
+ if (VDBG) log("CMD_CHANNEL_DISCONNECTED");
mAc = null;
break;
}
case DataConnectionAc.REQ_IS_INACTIVE: {
boolean val = getCurrentState() == mInactiveState;
- log("REQ_IS_INACTIVE isInactive=" + val);
+ if (VDBG) log("REQ_IS_INACTIVE isInactive=" + val);
mAc.replyToMessage(msg, DataConnectionAc.RSP_IS_INACTIVE, val ? 1 : 0);
break;
}
case DataConnectionAc.REQ_GET_CID: {
- log("REQ_GET_CID cid=" + cid);
+ if (VDBG) log("REQ_GET_CID cid=" + cid);
mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_CID, cid);
break;
}
case DataConnectionAc.REQ_GET_APNSETTING: {
- log("REQ_GET_APNSETTING apnSetting=" + mApn);
+ if (VDBG) log("REQ_GET_APNSETTING apnSetting=" + mApn);
mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_APNSETTING, mApn);
break;
}
case DataConnectionAc.REQ_GET_LINK_PROPERTIES: {
LinkProperties lp = new LinkProperties(mLinkProperties);
- log("REQ_GET_LINK_PROPERTIES linkProperties" + lp);
+ if (VDBG) log("REQ_GET_LINK_PROPERTIES linkProperties" + lp);
mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_LINK_PROPERTIES, lp);
break;
}
case DataConnectionAc.REQ_SET_LINK_PROPERTIES_HTTP_PROXY: {
ProxyProperties proxy = (ProxyProperties) msg.obj;
- log("REQ_SET_LINK_PROPERTIES_HTTP_PROXY proxy=" + proxy);
+ if (VDBG) log("REQ_SET_LINK_PROPERTIES_HTTP_PROXY proxy=" + proxy);
mLinkProperties.setHttpProxy(proxy);
mAc.replyToMessage(msg, DataConnectionAc.RSP_SET_LINK_PROPERTIES_HTTP_PROXY);
break;
@@ -612,7 +615,7 @@
case DataConnectionAc.REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE: {
DataCallState newState = (DataCallState) msg.obj;
DataConnectionAc.LinkPropertyChangeAction action = updateLinkProperty(newState);
- if (DBG) {
+ if (VDBG) {
log("REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE action="
+ action + " newState=" + newState);
}
@@ -623,18 +626,18 @@
}
case DataConnectionAc.REQ_GET_LINK_CAPABILITIES: {
LinkCapabilities lc = new LinkCapabilities(mCapabilities);
- log("REQ_GET_LINK_CAPABILITIES linkCapabilities" + lc);
+ if (VDBG) log("REQ_GET_LINK_CAPABILITIES linkCapabilities" + lc);
mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_LINK_CAPABILITIES, lc);
break;
}
case DataConnectionAc.REQ_RESET:
- if (DBG) log("DcDefaultState: msg.what=REQ_RESET");
+ if (VDBG) log("DcDefaultState: msg.what=REQ_RESET");
clearSettings();
mAc.replyToMessage(msg, DataConnectionAc.RSP_RESET);
transitionTo(mInactiveState);
break;
case DataConnectionAc.REQ_GET_REFCOUNT: {
- log("REQ_GET_REFCOUNT refCount=" + mRefCount);
+ if (VDBG) log("REQ_GET_REFCOUNT refCount=" + mRefCount);
mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_REFCOUNT, mRefCount);
break;
}
@@ -666,7 +669,8 @@
default:
if (DBG) {
- log("DcDefaultState: shouldn't happen but ignore msg.what=" + msg.what);
+ log("DcDefaultState: shouldn't happen but ignore msg.what=0x" +
+ Integer.toHexString(msg.what));
}
break;
}
@@ -685,13 +689,13 @@
private DisconnectParams mDisconnectParams = null;
public void setEnterNotificationParams(ConnectionParams cp, FailCause cause) {
- log("DcInactiveState: setEnterNoticationParams cp,cause");
+ if (VDBG) log("DcInactiveState: setEnterNoticationParams cp,cause");
mConnectionParams = cp;
mFailCause = cause;
}
public void setEnterNotificationParams(DisconnectParams dp) {
- log("DcInactiveState: setEnterNoticationParams dp");
+ if (VDBG) log("DcInactiveState: setEnterNoticationParams dp");
mDisconnectParams = dp;
}
@@ -707,11 +711,11 @@
* call to isInactive.
*/
if ((mConnectionParams != null) && (mFailCause != null)) {
- log("DcInactiveState: enter notifyConnectCompleted");
+ if (VDBG) log("DcInactiveState: enter notifyConnectCompleted");
notifyConnectCompleted(mConnectionParams, mFailCause);
}
if (mDisconnectParams != null) {
- log("DcInactiveState: enter notifyDisconnectCompleted");
+ if (VDBG) log("DcInactiveState: enter notifyDisconnectCompleted");
notifyDisconnectCompleted(mDisconnectParams);
}
}
@@ -751,7 +755,10 @@
break;
default:
- if (DBG) log("DcInactiveState nothandled msg.what=" + msg.what);
+ if (VDBG) {
+ log("DcInactiveState nothandled msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
retVal = NOT_HANDLED;
break;
}
@@ -856,7 +863,10 @@
break;
default:
- if (DBG) log("DcActivatingState not handled msg.what=" + msg.what);
+ if (VDBG) {
+ log("DcActivatingState not handled msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
retVal = NOT_HANDLED;
break;
}
@@ -873,7 +883,7 @@
private FailCause mFailCause = null;
public void setEnterNotificationParams(ConnectionParams cp, FailCause cause) {
- log("DcInactiveState: setEnterNoticationParams cp,cause");
+ if (VDBG) log("DcInactiveState: setEnterNoticationParams cp,cause");
mConnectionParams = cp;
mFailCause = cause;
}
@@ -887,7 +897,7 @@
* call to isActive.
*/
if ((mConnectionParams != null) && (mFailCause != null)) {
- log("DcActiveState: enter notifyConnectCompleted");
+ if (VDBG) log("DcActiveState: enter notifyConnectCompleted");
notifyConnectCompleted(mConnectionParams, mFailCause);
}
}
@@ -930,7 +940,10 @@
break;
default:
- if (DBG) log("DcActiveState nothandled msg.what=" + msg.what);
+ if (VDBG) {
+ log("DcActiveState not handled msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
retVal = NOT_HANDLED;
break;
}
@@ -972,7 +985,10 @@
break;
default:
- if (DBG) log("DcDisconnectingState not handled msg.what=" + msg.what);
+ if (VDBG) {
+ log("DcDisconnectingState not handled msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
retVal = NOT_HANDLED;
break;
}
@@ -1014,9 +1030,9 @@
break;
default:
- if (DBG) {
- log("DcDisconnectionErrorCreatingConnection not handled msg.what="
- + msg.what);
+ if (VDBG) {
+ log("DcDisconnectionErrorCreatingConnection not handled msg.what=0x"
+ + Integer.toHexString(msg.what));
}
retVal = NOT_HANDLED;
break;
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionAc.java b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
index a0d9b0f..62b90ae 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionAc.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
@@ -28,7 +28,7 @@
* AsyncChannel to a DataConnection
*/
public class DataConnectionAc extends AsyncChannel {
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private String mLogTag;
public DataConnection dataConnection;
@@ -367,7 +367,7 @@
if ((response != null) && (response.what == RSP_RESET)) {
if (DBG) log("restSync ok");
} else {
- if (DBG) log("restSync error response=" + response);
+ log("restSync error response=" + response);
}
}
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 0621cfd..2c04b30 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -2888,7 +2888,7 @@
String s = p.readString();
- if (RILJ_LOGD) riljLog("< iccIO: "
+ if (RILJ_LOGV) riljLog("< iccIO: "
+ " 0x" + Integer.toHexString(sw1)
+ " 0x" + Integer.toHexString(sw2) + " "
+ s);
@@ -3051,7 +3051,7 @@
responseSetupDataCall(Parcel p) {
int ver = p.readInt();
int num = p.readInt();
- if (RILJ_LOGD) riljLog("responseSetupDataCall ver=" + ver + " num=" + num);
+ if (RILJ_LOGV) riljLog("responseSetupDataCall ver=" + ver + " num=" + num);
DataCallState dataCall;
diff --git a/tests/BiDiTests/AndroidManifest.xml b/tests/BiDiTests/AndroidManifest.xml
index e328b5b..b3d8893 100644
--- a/tests/BiDiTests/AndroidManifest.xml
+++ b/tests/BiDiTests/AndroidManifest.xml
@@ -19,7 +19,7 @@
android:versionCode="1"
android:versionName="1.0">
- <application android:label="BiDiTests">
+ <application android:label="BiDiTests" android:hardwareAccelerated="true">
<activity android:name=".BiDiTestActivity"
android:windowSoftInputMode="stateAlwaysHidden">
@@ -73,4 +73,4 @@
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/BiDiTests/res/layout/basic.xml b/tests/BiDiTests/res/layout/basic.xml
index c4807ff..f254e3c 100644
--- a/tests/BiDiTests/res/layout/basic.xml
+++ b/tests/BiDiTests/res/layout/basic.xml
@@ -27,22 +27,21 @@
<Button android:id="@+id/button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:onClick="onButtonClick"
android:text="@string/button_text"
android:textSize="32dip"
/>
<TextView android:id="@+id/textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:textSize="32dip"
- android:text="@string/textview_text"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="32dip"
+ android:text="@string/textview_text"
/>
<EditText android:id="@+id/edittext"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:textSize="32dip"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:textSize="32dip"
/>
</LinearLayout>